对外提供API,通过appId、appSecret、sign秘钥对接口做鉴权

您所在的位置:网站首页 app鉴权 对外提供API,通过appId、appSecret、sign秘钥对接口做鉴权

对外提供API,通过appId、appSecret、sign秘钥对接口做鉴权

2024-04-29 10:09| 来源: 网络整理| 查看: 265

一、背景

在接口开发过程中,我们通常不能暴露一个接口给第三方随便调用,要对第三方发来参数进行校验,看是不是具有访问权限。

名词介绍:

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 params, String key){ StringBuilder sb = new StringBuilder(); Set es = params.entrySet(); Iterator it = es.iterator(); //生成 while (it.hasNext()){ Map.Entry 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 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 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 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 params, String key){ StringBuilder sb = new StringBuilder(); Set es = params.entrySet(); Iterator it = es.iterator(); //生成 while (it.hasNext()){ Map.Entry 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 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 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 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 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

{"appId":"appid","timestamp":"1667384136520","sign":"sign","ids":[接口其它请求]}

 

 

 

 

参考文档:https://blog.csdn.net/it1993/article/details/104682832/



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3