参考:https://blog.csdn.net/yqwang75457/article/details/117815474

1、原理

AK/SK:

AK:Access Key Id,用于标示用户。

SK:Secret Access Key,是用户用于加密认证字符串和用来验证认证字符串的密钥,其中SK必须保密。

通过使用Access Key Id / Secret Access Key加密的方法来验证某个请求的发送者身份。

基本思路:

1.客户端需要在认证服务器中预先设置 access key(AK 或叫 app ID) 和 secure key(SK)。

2.在调用 API 时,客户端需要对参数和 access key 等信息结合 secure key 进行签名生成一个额外的 sign字符串。

3.服务器接收到用户的请求后,系统将使用AK对应的相同的SK和同样的认证机制生成认证字符串,并与用户请求中包含的认证字符串进行比对。如果认证字符串相同,系统认为用户拥有指定的操作权限,并执行相关操作;如果认证字符串不同,系统将忽略该操作并返回错误码。

2、实现

2.1 服务端

注解拦截器,拦截请求

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface IdentifySk {
String value() default "";
}

切面

@Component
@Aspect
public class IdentifyRequest {
@Pointcut("@annotation(com.chinatower.platform.client.aop.IdentifySk)")
public void pointCut() {
} @Before(value = "pointCut()")
public void before(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//获取请求头信息
String ts = request.getHeader("ts");
Long timeStamp = StrUtil.isNotBlank(ts) ? Long.valueOf(ts) : null;
String msgId = request.getHeader("msgId");
String appId = request.getHeader("appId");
String sign = request.getHeader("sign");
boolean res = SignUtil.checkHeaderInfo(timeStamp, msgId, appId, sign);
Validate.isTrue(res,"请求失败,请检查请求头");
}
}

生成sign加密工具类

@Slf4j
public class SHACoderUtil { public static String sha256(String str){
String encodeStr = "";
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(str.getBytes("UTF-8"));
encodeStr = byte2Hex(messageDigest.digest());
} catch (Exception e) {
log.error("sha256加码失败",e);
}
return encodeStr;
} /**
* 将byte转为16进制
* @param bytes
* @return
*/
private static String byte2Hex(byte[] bytes){
StringBuffer stringBuffer = new StringBuffer();
String temp = null;
for (int i=0;i<bytes.length;i++){
temp = Integer.toHexString(bytes[i] & 0xFF);
if (temp.length()==1){
//1得到一位的进行补0操作
stringBuffer.append("0");
}
stringBuffer.append(temp);
}
return stringBuffer.toString();
}
}

校验工具类

public class SignUtil {

    /**
* 按签名算法获取sign
*
* @param appId
* @param appSecret
* @param ts 时间戳
* @param msgId 请求唯一标识
* @return
*/
public static String getSign(String appId, String appSecret, String ts, String msgId) {
String str = IStringUtils.spliceStr("ts=", String.valueOf(ts), "&msgId=", msgId, "&appId=", appId, "&appSecret=", appSecret);
// 对待加密字符串进行加密,得到sign值
return SHACoderUtil.sha256(str);
} /**
* 鉴别 sign ,被调用方
*/
public static boolean checkHeaderInfo(Long ts, String msgId, String appId, String sign) {
if (ts == null || StrUtil.isBlank(msgId) || StrUtil.isBlank(appId) || StrUtil.isBlank(sign)) {
return false;
}
//可以加一个利用redis防止重复请求
//超过20分钟,表示请求无效
if (System.currentTimeMillis() - ts > 20 * 60 * 1000) {
return false;
}
//根据参数加密,验证和传来的sign是否一致
String reSign = getSign(appId, CommonConstant.SK, String.valueOf(ts), msgId);
if (sign.equals(reSign)) {
return true;
}
return false;
} }

使用注解

    @IdentifySk()
@PostMapping("/testSk")
public void testSk() {
System.out.println("ceshi");
}

2.2 客户端

加密工具类,同上SHACoderUtil

生成请求头和sign工具类

public class SignUtil {
/**
* 构建请求头 调用方
*/
public static Map<String, String> requestHeader(String appId, String appSecret) {
String ts = String.valueOf(System.currentTimeMillis());
String msgId = UUID.randomUUID().toString();
Map<String, String> header = new HashMap<>(16);
// 进行接口调用时的时间戳,即当前时间戳(毫秒),服务端会校验时间戳,例如时间差超过20分钟则认为请求无效,防止重复请求的攻击
header.put("ts", ts);
//每个请求提供一个唯一的标识符,服务器能够防止请求被多次使用
header.put("msgId", msgId);
header.put("appId", appId);
String sign = getSign(appId, appSecret, ts, msgId);
header.put("sign", sign);
return header;
}
/**
* 按签名算法获取sign(客户端和服务器端算法一致,都需要用)
*
* @param appId
* @param appSecret
* @param ts 时间戳
* @param msgId 请求唯一标识
* @return
*/
public static String getSign(String appId, String appSecret, String ts, String msgId) {
String str = IStringUtils.spliceStr("ts=", ts, "&msgId=", msgId, "&appId=", appId, "&appSecret=", appSecret);
// 对待加密字符串进行加密,得到sign值
return SHACoderUtil.sha256(str);
}
}

远程请求http工具类

@Slf4j
public class HttpClientUtil { private static final String ENCODING_TYPE = "UTF-8"; public static String doGet(String url, Map<String, String> param) {
log.info("doGet方法,url={}, param={}", url, param);
CloseableHttpClient httpclient = HttpClients.createDefault(); String resultString = "";
CloseableHttpResponse response = null;
try {
// 创建uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key));
}
}
URI uri = builder.build(); HttpGet httpGet = new HttpGet(uri); response = httpclient.execute(httpGet); // 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), ENCODING_TYPE);
}
log.info("doGet方法返回状态={}, 结果={}", response.getStatusLine().getStatusCode(), resultString);
} catch (Exception e) {
log.error("发送get请求失败,原因={}", e.getLocalizedMessage());
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
log.error("关闭流失败,原因={}", e.getMessage());
e.printStackTrace();
}
}
return resultString;
} public static String doPostJson(String url, String json,Integer timeOut) {
log.info("Http 的请求地址是{}, 请求参数是 {}",url,json);
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
HttpPost httpPost = new HttpPost(url);
// 创建请求内容
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
//构建超时等配置信息
RequestConfig config = RequestConfig.custom().setConnectTimeout(timeOut) //连接超时时间
.setConnectionRequestTimeout(timeOut) //从连接池中取的连接的最长时间
.setSocketTimeout(timeOut) //数据传输的超时时间
.setStaleConnectionCheckEnabled(true) //提交请求前测试连接是否可用
.build();
httpPost.setConfig(config);
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), ENCODING_TYPE);
} catch (ConnectTimeoutException e){
// 链接拒绝 ConnectTimeoutException
log.error("发送post json 请求失败,原因={}", e);
resultString = httpTimeOutOrConnect("连接超时", CommonConstant.HTTP_OUT_TIME);
} catch (HttpHostConnectException e){
// 链接拒绝 ConnectTimeoutException
log.error("发送post json 请求失败,原因={}", e);
resultString = httpTimeOutOrConnect("连接拒绝", CommonConstant.HTTP_OUT_TIME);
} catch (SocketTimeoutException e){
// 链接超时
log.error("发送post json 请求失败,原因={}", e);
resultString = httpTimeOutOrConnect("请求超时",CommonConstant.HTTP_OUT_TIME);
} catch (Exception e) {
// 其他异常
log.error("发送post json 请求失败,原因={}", e);
resultString = httpTimeOutOrConnect("请求失败",CommonConstant.HTTP_OUT_TIME_ERROR);
} finally {
try {
if (response != null) {
response.close();
}
} catch (IOException e) {
log.error("关闭流失败, 原因={}", e.getMessage());
e.printStackTrace();
}
}
log.info("Http 请求结果是{}",resultString);
return resultString;
} /**
* 带请求头的的post请求
* @param url
* @param headMap
* @param json
* @param timeOut
* @return
*/
public static String doPostJson(String url, Map<String, String> headMap,String json,Integer timeOut) {
log.info("Http 的请求地址是{},请求头:{}, 请求参数是 {}",url, JSON.toJSONString(headMap),json);
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
HttpPost httpPost = new HttpPost(url);
//请求头
if (headMap != null && !headMap.isEmpty()){
for (String key:headMap.keySet()){
httpPost.addHeader(key,headMap.get(key));
}
}
// 创建请求内容
if (StrUtil.isNotBlank(json)){
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
}
//构建超时等配置信息
RequestConfig config = RequestConfig.custom().setConnectTimeout(timeOut) //连接超时时间
.setConnectionRequestTimeout(timeOut) //从连接池中取的连接的最长时间
.setSocketTimeout(timeOut) //数据传输的超时时间
.setStaleConnectionCheckEnabled(true) //提交请求前测试连接是否可用
.build();
httpPost.setConfig(config);
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), ENCODING_TYPE);
} catch (ConnectTimeoutException e){
// 链接拒绝 ConnectTimeoutException
log.error("发送post json 请求失败,原因={}", e);
resultString = httpTimeOutOrConnect("连接超时", CommonConstant.HTTP_OUT_TIME);
} catch (HttpHostConnectException e){
// 链接拒绝 ConnectTimeoutException
log.error("发送post json 请求失败,原因={}", e);
resultString = httpTimeOutOrConnect("连接拒绝",CommonConstant.HTTP_OUT_TIME);
} catch (SocketTimeoutException e){
// 链接超时
log.error("发送post json 请求失败,原因={}", e);
resultString = httpTimeOutOrConnect("请求超时",CommonConstant.HTTP_OUT_TIME);
} catch (Exception e) {
// 其他异常
log.error("发送post json 请求失败,原因={}", e);
resultString = httpTimeOutOrConnect("请求失败",CommonConstant.HTTP_OUT_TIME_ERROR);
} finally {
try {
if (response != null) {
response.close();
}
} catch (IOException e) {
log.error("关闭流失败, 原因={}", e.getMessage());
e.printStackTrace();
}
}
log.info("Http 请求结果是{}",resultString);
return resultString;
} /**
*
* @return
*/
private static String httpTimeOutOrConnect(String returnMsg, String returnCode){
JSONObject jsonObject = new JSONObject();
jsonObject.put("returnMsg", returnMsg);
jsonObject.put("returnCode", returnCode);
jsonObject.put("errorMsg", returnMsg);
jsonObject.put("errorCode", returnCode);
return jsonObject.toJSONString();
} }

测试请求服务端:

main(){
Map<String, String> headMap = SignUtil.requestHeader(commonProperties.getAk(), commonProperties.getSk());
HttpClientUtil.doPostJson(url, headMap, "ceshi", 60000);
}

AK、SK实现(双方API交互:签名及验证)的更多相关文章

  1. PHP开发API接口签名及验证

    <?php // 设置一个密钥(secret),只有发送方,和接收方知道 /*----发送方和接收方- start ----*/ $secret = "28c8edde3d61a041 ...

  2. 使用CEPH RGW admin ops API 进行用户user AK/SK管理的秘诀

    需求: 云平台面板上需要支持为不同的用户创建不同的RGW 的AK/SK用户秘钥,以完成对象存储的用户隔离,并可以管理bucket和查看bucket容量信息. 分析:查阅CEPH官网文档 S3 API  ...

  3. 034.认证方式 | 基本认证 、Token认证、 AK/SK认证

    认证方式 关于认证: https://www.cnblogs.com/badboyh2o/p/11068779.html https://www.cnblogs.com/badboyh2o/p/110 ...

  4. 前后端分离后API交互如何保证数据安全性

    前后端分离后API交互如何保证数据安全性? 一.前言 前后端分离的开发方式,我们以接口为标准来进行推动,定义好接口,各自开发自己的功能,最后进行联调整合.无论是开发原生的APP还是webapp还是PC ...

  5. AK/SK加密认证

    AK/SK认证的实现 AK/SK概述 1.什么是AKSK ak/sk是一种身份认证方式,常用于系统间接口调用时的身份验证,其中ak为Access Key ID,sk为Secret Access Key ...

  6. 前后端API交互数据加密——AES与RSA混合加密完整实例

    前言 前段时间看到一篇文章讲如何保证API调用时数据的安全性(传送门:https://blog.csdn.net/ityouknow/article/details/80603617),文中讲到利用R ...

  7. 前后端API交互数据加密——AES与RSA混合加密完整实例(转载)

    前言 前段时间看到一篇文章讲如何保证API调用时数据的安全性(传送门:https://blog.csdn.net/ityouknow/article/details/80603617),文中讲到利用R ...

  8. 【WEB API项目实战干货系列】- API登录与身份验证(三)

    上一篇: [WEB API项目实战干货系列]- 接口文档与在线测试(二) 这篇我们主要来介绍我们如何在API项目中完成API的登录及身份认证. 所以这篇会分为两部分, 登录API, API身份验证. ...

  9. WebApi基于Token和签名的验证

    最近一段时间在学习WebApi,涉及到验证部分的一些知识觉得自己并不是太懂,所以来博客园看了几篇博文,发现一篇讲的特别好的,读了几遍茅塞顿开(都闪开,我要装逼了),刚开始读有些地方不理解,所以想了很久 ...

  10. iOS使用Security.framework进行RSA 加密解密签名和验证签名

    iOS 上 Security.framework为我们提供了安全方面相关的api: Security框架提供的RSA在iOS上使用的一些小结 支持的RSA keySize 大小有:512,768,10 ...

随机推荐

  1. Spring Boot 3.1中如何整合Spring Security和Keycloak

    在今年2月14日的时候,Keycloak 团队宣布他们正在弃用大多数 Keycloak 适配器.其中包括Spring Security和Spring Boot的适配器,这意味着今后Keycloak团队 ...

  2. 2022 i春秋冬季赛

    Misc nan's analysis 下载附件之后,打开是一道流量数据包. 开始分析流量,首先看到的是FTP流量 追踪tcp,发现ftp账号密码 先记录一下,接下来发现一个zip文件,选择原始数据, ...

  3. iOS 单元测试之常用框架 OCMock 详解

    一.单元测试 1.1 单元测试的必要性 测试驱动开发并不是一个很新鲜的概念了.在日常开发中,很多时候需要测试,但是这种输出是必须在点击一系列按钮之后才能在屏幕上显示出来的东西.测试的时候,往往是用模拟 ...

  4. 让AI支持游戏制作流程:从游戏设计到发布一个完整的生态系统

    目录 引言 随着游戏产业的快速发展,人工智能(AI)技术在游戏开发中的应用越来越广泛.游戏设计人员可以通过利用AI技术来自动化游戏中的某些流程,提高游戏制作的效率,降低开发成本,同时还可以创造出更加具 ...

  5. AI室内设计:提升效率、消除沟通障碍,满足客户需求

    前言 免费AI绘图工具:https://www.topgpt.one 随着人工智能(AI)技术的不断发展,室内设计行业也开始受益于这一技术的应用.其中,AI绘画工具在室内设计中的应用正日益受到关注.这 ...

  6. Matlab学习1

    Matlab 数据类型 数字 字符和字符串 矩阵 元胞数组 结构体 清空环境变量及命令 cls % 清除Command Windows中的所有命令 clear all % 清除Workspace*中的 ...

  7. 如何在 Windows10 Professional 服务器上搭建自己的 Git 服务器。

    一.简介 以前,在别家的公司,一般早就把源代码管理工具搭建好了,很少有机会自己搭建一套.最近,公司也许要把现在不少的源码进行管理,于是我打算自己搭建源代码管理服务器.说起源代码管理,当然有很多中解决方 ...

  8. 暗黑2能用Java开发?还能生成APP?

    最近烧哥发现个宝藏项目,竟然用Java开发了暗黑2出来. 众所周知,暗黑2是暴雪开发的一款经典游戏,距今虽有20多年,仍然有很多粉丝. 粉丝延续热情的方式有很多,一种是做Mod,比如魔电,对怪物.技能 ...

  9. Hexo博客Next主题阅读次数热度不能读取的问题,报错Counter not initialized! More info at console err msg.

    加入valine在线评论 设置效果: 设置方法: 首先要先去LeanCloud注册一个帐号.然后再创建一个应用. 拿到appid和appkey之后,打开themes/next/_config.yml主 ...

  10. 「学习笔记」FHQ-treap

    FHQ-treap,即无旋 treap,又称分裂合并 treap,支持维护序列,可持久化等特性. FHQ-treap 有两个核心操作,分裂 与 合并.通过这两个操作,在很多情况下可以比旋转 treap ...