对外提供API,通过appId、appSecret、sign秘钥对接口做鉴权
一、背景
在接口开发过程中,我们通常不能暴露一个接口给第三方随便调用,要对第三方发来参数进行校验,看是不是具有访问权限。
名词介绍:
1、appId: 应用id,用户自定义命名,如:*-access-token
2、appSecret:安全密钥,服务端通过uuid生成,然后发送给调用方
3、sign:开发生成签名的工具类,发送给调用方
鉴权步骤:
1、客户端使用提供的工具,传参appId、时间戳、appSecret生成sign签名
2、发送appId、13位系统时间戳、签名、请求参数给服务端
3、服务端收到appId、时间戳、签名参数后,根据appId查询数据库,获取用户appSecret安全密钥。同样根据appId、时间戳、appSecret生成sign,判断用户传参的sign是否相等。
二、具体代码
1、鉴权工具类
package com.test.utils;
import java.security.MessageDigest;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
/**
* @datetime 2022-11-02 上午11:00
* @desc 接口校验工具类
* 生成有序map,签名,验签
* 通过appId、timestamp、appSecret做签名
* @menu
*/
@Slf4j
public class SignUtil {
/**
* 生成签名sign
* 加密前:appId=wx123456789×tamp=1583332804914&key=7214fefff0cf47d7950cb2fc3b5d670a
* 加密后:E2B30D3A5DA59959FA98236944A7D9CA
*/
public static String createSign(SortedMap<String, String> params, String key){
StringBuilder sb = new StringBuilder();
Set<Map.Entry<String, String>> es = params.entrySet();
Iterator<Map.Entry<String,String>> it = es.iterator();
//生成
while (it.hasNext()){
Map.Entry<String,String> entry = it.next();
String k = entry.getKey();
String v = entry.getValue();
if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){
sb.append(k+"="+v+"&");
}
}
sb.append("key=").append(key);
String sign = MD5(sb.toString()).toUpperCase();
return sign;
}
/**
* 校验签名
*/
public static Boolean isCorrectSign(SortedMap<String, String> params, String key){
String sign = createSign(params,key);
String requestSign = params.get("sign").toUpperCase();
log.info("通过用户发送数据获取新签名:{}", sign);
return requestSign.equals(sign);
}
/**
* md5常用工具类
*/
public static String MD5(String data){
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte [] array = md5.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* 生成uuid
*/
public static String generateUUID(){
String uuid = UUID.randomUUID().toString().replaceAll("-","").substring(0,32);
return uuid;
}
public static void main(String[] args) {
//第一步:生成uuid,用作appSecret
// System.out.println(SignUtil.generateUUID());
//第二步:用户端发起请求,生成签名后发送请求
String appSecret = "7214fefff0cf47d7950cb2fc3b5d670a";
String appId = "wx123456789";
String timestamp = "1583332804914";
//生成签名
SortedMap<String, String> sortedMap = new TreeMap<>();
sortedMap.put("appId", appId);
sortedMap.put("timestamp", timestamp);
System.out.println("签名:"+SignUtil.createSign(sortedMap, appSecret));
//第三步:校验签名
//1.校验时间戳
long requestTime = Long.valueOf(timestamp);
// 时间查过20秒,则认为接口为重复调用,返回错误信息
long nowTime = new Date().getTime();
int seconds = (int) ((nowTime - requestTime)/1000);
if(Math.abs(seconds) > 86400) {
System.out.println("访问已过期,请检查服务器时间!");
return;
}
//2.组装参数,
SortedMap<String, String> sortedMap12 = new TreeMap<>();
sortedMap12.put("appId", appId);
sortedMap12.put("timestamp", timestamp);
sortedMap12.put("sign", "");
//3.校验签名
Boolean flag = SignUtil.isCorrectSign(sortedMap12, appSecret);
if(flag){
System.out.println("签名验证通过");
}else {
System.out.println("签名验证未通过");
}
}
}
2、提供给用户的生成签名工具类
import java.security.MessageDigest;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import 上面的工具类包下.SignUtil;
/**
* @datetime 2022-11-02 下午2:45
* @desc
* @menu
*/
public class SignUtilTest {
/**
* 生成签名sign
* 加密前:appId=wx123456789×tamp=1583332804914&key=7214fefff0cf47d7950cb2fc3b5d670a
* 加密后:E2B30D3A5DA59959FA98236944A7D9CA
*/
public static String createSign(SortedMap<String, String> params, String key){
StringBuilder sb = new StringBuilder();
Set<Map.Entry<String, String>> es = params.entrySet();
Iterator<Map.Entry<String,String>> it = es.iterator();
//生成
while (it.hasNext()){
Map.Entry<String,String> entry = it.next();
String k = entry.getKey();
String v = entry.getValue();
if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){
sb.append(k+"="+v+"&");
}
}
sb.append("key=").append(key);
String sign = MD5(sb.toString()).toUpperCase();
return sign;
}
/**
* md5常用工具类
*/
public static String MD5(String data){
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte [] array = md5.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
//第二步:用户端发起请求,生成签名后发送请求
String appSecret = "";
String appId = "";
long nowTime = new Date().getTime();
//生成签名
SortedMap<String, String> sortedMap = new TreeMap<>();
sortedMap.put("appId", appId);
sortedMap.put("timestamp", String.valueOf(nowTime));
System.out.println("appId:"+appId+" 时间戳:"+nowTime+" 签名:"+ SignUtil.createSign(sortedMap, appSecret));
}
}
3、服务端系统对控制层做切面,拦截部分url请求
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
//省略内部使用包
@Slf4j
@Aspect
@Component
public class ExtApiAspect {
/**
* 系统接入管理
*/
@Resource
SystemAccessMapper systemAccessMapper;
/**
* 不拦截路径
*/
private static final Set<String> FILTER_PATHS = Collections.unmodifiableSet(new HashSet<>(
Arrays.asList("/api/extApi/test")));
/**
* 匹配ExtApiController下所有方法
*/
@Pointcut("execution(public * com.test.ExtApiController.*(..))")
public void webLog() {
}
/**
* 1、控制层处理完后返回给用户前,记录日志
* 3、调用处理方法获取结果后,再调用本方法proceed
*/
@Around("webLog()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed();
// 打印下返回给用户数据
log.info("RequestId={}, Response Args={}, Time-Consuming={} ms", RequestThreadLocal.getRequestId(),
JsonUtil.toJSON(result), System.currentTimeMillis() - startTime);
return result;
}
/**
* 2、调用控制层方法前执行
*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
RequestThreadLocal.setRequestId(getUUID());
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//不拦截打印日志的方法
String path = request.getRequestURI().substring(request.getContextPath().length()).replaceAll("[/]+$", "");
if(FILTER_PATHS.contains(path)){
return;
}
//请求参数
Object[] args = joinPoint.getArgs();
//请求的方法参数值 JSON 格式 null不显示
if (args.length > 0) {
for (int i = 0; i < args.length; i++) {
//请求参数类型判断过滤,防止JSON转换报错
if (args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse || args[i] instanceof MultipartFile) {
continue;
}
StringBuilder logInfo = new StringBuilder();
logInfo.append("RequestId=").append(RequestThreadLocal.getRequestId()).append(SystemConstants.LOG_SEPARATOR)
.append(" RequestMethod=").append(request.getRequestURI()).append(SystemConstants.LOG_SEPARATOR)
.append(" Args=").append(args[i].toString());
log.info(logInfo.toString());
JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(args[i]));
if (jsonObject != null) {
String appId = jsonObject.getString("appId");
String timestamp = jsonObject.getString("timestamp");
String sign = jsonObject.getString("sign");
if(StringUtils.isEmpty(appId) || StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(sign)){
throw new CustomException(CodeEnum.EXT_API_PARAMETER_ERROR);
}
//1.时间戳校验,大于一天不处理
long requestTime = Long.valueOf(timestamp);
long nowTime = new Date().getTime();
int seconds = (int) ((nowTime - requestTime)/1000);
if(Math.abs(seconds) > 86400) {
throw new CustomException(CodeEnum.EXT_API_TIMEOUT);
}
LambdaQueryWrapper<SystemAccessDTO> queryWrapper = new LambdaQueryWrapper<>();
//!!!!!安全考虑,省略查询条件
queryWrapper.last(" limit 1");
SystemAccessDTO systemAccessDTO = systemAccessMapper.selectOne(queryWrapper);
if(systemAccessDTO == null){
throw new CustomException(CodeEnum.EXT_API_AUTH_ERROR);
}
//3.校验签名
SortedMap<String, String> sortedMap = new TreeMap<>();
sortedMap.put("appId", appId);
sortedMap.put("timestamp", timestamp);
sortedMap.put("sign", sign);
Boolean flag = SignUtil.isCorrectSign(sortedMap, systemAccessDTO.getAppSecret());
if(flag){
log.info("外部系统接入,信息认证正确"+appId);
}else{
throw new CustomException(CodeEnum.EXT_API_AUTH_ERROR);
}
}
}
}else{
throw new CustomException(CodeEnum.PARAMETER_ERROR);
}
}
/**
* 4、调用控制层方法后,说明校验通过不处理
*/
@After("webLog()")
public void doAfter(JoinPoint joinPoint) throws Throwable {
log.info("外部接口调用鉴权成功" + RequestThreadLocal.getRequestId());
}
/**
* 5、处理完后
*/
@AfterReturning(pointcut = "webLog()", returning = "result")
public void AfterReturning(JoinPoint joinPoint, RestResponse result) {
result.success(RequestThreadLocal.getRequestId());
RequestThreadLocal.remove();
}
/**
* 异常处理
*/
@AfterThrowing("webLog()")
public void afterThrowing() {
RequestThreadLocal.remove();
}
/**
* 生成uuid
*/
public static String getUUID() {
return UUID.randomUUID().toString().trim();
}
}
三、模拟请求
请求post:http://127.0.0.1:8080/test/test
请求json
参考文档:https://blog.csdn.net/it1993/article/details/104682832/
对外提供API,通过appId、appSecret、sign秘钥对接口做鉴权的更多相关文章
- windows服务中对外提供API接口
public class SendMqService { private static bool isExcute = true; private static HttpListener listen ...
- lumen 使用 dingo API 在 phpunit 中 404 的解决方法, 以及鉴权问题
1. phpunit.xml 中添加 dingo 相关配置 <env name="API_STANDARDS_TREE" value="x"/> & ...
- Web API 令牌(秘钥是双方约定的,并不在网络连接上传输)
http://blog.csdn.net/qq289523052/article/details/47750021 秘钥是双方约定的,并不在网络连接上传输 Web API数据传输加密 2015-08- ...
- 项目API接口鉴权流程总结
权益需求对接中,公司跟第三方公司合作,有时我们可能作为甲方,提供接口给对方,有时我们也作为乙方,调对方接口,这就需要API使用签名方法(Sign)对接口进行鉴权.每一次请求都需要在请求中包含签名信息, ...
- TLS握手秘钥套件分析
1.为了弄清楚TLS1.3的内部结构,觉得有必要将TLS的整个结构从新整理一遍,方便后续在做握手协议的形式化分析的时候能够不遗漏每个加密和认证的的环节. TLS1.3不论文在协议内容上还是性能上都较之 ...
- iOS监听模式系列之iOS开发证书、秘钥
补充--iOS开发证书.秘钥 iOS开发过程中如果需要进行真机调试.发布需要注册申请很多证书,对于初学者往往迷惑不解,再加上今天的文章中会牵扯到一些特殊配置,这里就简单的对iOS开发的常用证书和秘钥等 ...
- 微信开发第2章 通过appid appsecret获取accesstoken
通过 appid appsecret是可以获取accesstoken的 ,请不要一直获取,不然会把token的机会浪费掉,获取到后7200秒后失效,建议保存为6000秒到7000秒左右,具体可以查看微 ...
- 【spring cloud】对接口调用者提供API使用的安全验证微服务【这里仅通过代码展示一种设计思想】【后续可以加入redis限流的功能,某段时间某个IP可以访问API几次】
场景: 公司的微服务集群,有些API 会对外提供接口,供其他厂商进行调用.这些公开的API接口,由一个OpenAPI微服务统一提供给大家. 那么所有的调用者在调用公开API接口的时候,需要验证是否有权 ...
- grpc-gateway:grpc对外提供http服务的解决方案
我所在公司的项目是采用基于Restful的微服务架构,随着微服务之间的沟通越来越频繁,就希望可以做成用rpc来做内部的通讯,对外依然用Restful.于是就想到了google的grpc. 使用grpc ...
- How to get API key (APPID)
Python爬虫视频教程零基础小白到scrapy爬虫高手-轻松入门 https://item.taobao.com/item.htm?spm=a1z38n.10677092.0.0.482434a6E ...
随机推荐
- Java 设计模式——观察者模式:从优衣库不使用新疆棉事件看系统的动态响应
背景事件:近日,优衣库宣布不再使用新疆棉花,这一举措引发了广泛的社会讨论.消费者的反应和舆论的压力,让优衣库的决策迅速影响了市场和品牌形象.类似的,许多系统也面临着需要根据外部事件或状态的变化,做出即 ...
- HarmonyOS Next 入门实战 - 导航框架:页面路由、组件导航(Navigation)
页面路由 官方不推荐使用页面路由,这里仅做简单介绍. 页面路由用于标识 @Entry 注解的页面间的跳转. 包引入 import { router } from'@kit.ArkUI'; 页面跳转 r ...
- Consul 学习总结
什么是Consul? Consul是一种服务网络解决方案,使团队能够管理服务之间以及跨本地和多云环境和运行时的安全网络连接.Consul提供服务发现.服务网格(service mesh).流量管理和网 ...
- 金Gien乐道 | 10月热点回顾
收获之秋,中电金信Q4开篇捷报不断 Q4开篇,中电金信迎来多个捷报.公司与青岛财通集团联合打造的核心业务系统(一体化业务平台)一期项目顺利投产上线并平稳运行:中标华南某全国性股份制商业银行新一 ...
- 【金TECH频道】汇聚多元化超级算力,看见更好的“源启”
越来越多的金融机构开始利用大数据和AI技术,提升信贷业务的效率,利用隐私计算打造开放式金融,让客户随时随地获得金融服务:气象领域,高精度计算让我们能准确地预测恶劣的天气,医疗大数据让部分癌症的治愈成为 ...
- NoSQL 述评
作为主库的 nosql 只有 CockroachDB.TiKV 以及 MongoDB(从4.0后事务似乎可用了),CockrouchDB 已经收费,另外 YugabyteDB 也可选,但大家的反馈都不 ...
- zz 为什么我更喜欢 Python 的 Storm ORM
为什么我更喜欢 Python 的 Storm ORM - @emacsway 的博客 很有意思的讨论.可能还是 mapping 比较实用. 另外,文中称赞有加的 Identity Map 并不适合并发 ...
- .net delegate 万能适配
遇到一个技术点,记一下,.net 有一个 Delegate Marshall.GetDelegateForFunctionPointer(IntPtr ptr, Type t) 用来将内存地址映射为一 ...
- Linux安装EasyConnect
首先下载并安装EasyConnect客户端 wget http://download.sangfor.com.cn/download/product/sslvpn/pkg/linux_767/Easy ...
- Shell三元表达式
Shell三元表达式 shell能否实现三元表达式呢?像下面这样: int a = (b == 5) ? c : d; 实现方法: a=$([ "$b" == 5 ] & ...