当前位置: 首页> 教育> 高考 > 网络公司门头照片_广告学是热门还是冷门_怎样制作一个网站_a站

网络公司门头照片_广告学是热门还是冷门_怎样制作一个网站_a站

时间:2025/7/22 6:34:59来源:https://blog.csdn.net/qq_32430463/article/details/144803212 浏览次数:0次
网络公司门头照片_广告学是热门还是冷门_怎样制作一个网站_a站

文章目录

  • 前言
  • 一、实现原理
  • 二、签名规则
  • 三、服务端实现
    • 4.1、创建自定义注解
    • 4.2、创建切面处理类
    • 4.3、对应工具类`RequestUtil`
  • 四、测试
    • 4.1 签名失败测试:
    • 4.2 签名成功测试:
  • 四、总结


前言

对外开放的接口,需要验证请求方发送过来的数据确实是由发送方发起的,并且中途不能被篡改和伪造,所以希望对接口的访问进行签名验证,以验证请求的合法性和完整性,防止数据篡改、伪造或重放攻击,保障接口调用的安全性


一、实现原理

客户端对请求参数进行整理,并且通过MD5的方式(必须是不可逆的签名算法)生成签名标识,后端拿到请求的信息内容,经过同样的算法规则得到签名标识,比对和接收到的签名一致,则验证通过

二、签名规则

该签名规则只是作为参考,实际项目根据需要进行变更,因为规则的复杂程度决定了签名是否可以被伪造的难易程度

参数一般分为3种,分别为:
PathVariable参数
QueryParams参数
Body参数

  • 步骤1:参数排序
    将①和②类型的参数按参数名ASCII码从小到大排序(字典序,从大到小也可以,根据需求约定好即可),且参数名和参数值以键值对互相连接组成一个请求参数串paramStr,每个参数以#结尾,例如:

PathVariable有2个(subId,typeQueryParams参数有2个(status,date
1.先将参数名按照ASCII码从小到大排序,排序后为date, status, subId, type
2.再将参数名和参数值以键值对互相连接,即date=2023-01-01#status=1#subId=1001#type=2#

  • 步骤2:拼接参数
    将③body参数json字符串、当前时间戳timestamp、随机串nonce、直接拼接到paramStr字符串前面,顺序为:nonce,timestamp,body;例如:

这里可以根据项目需求,可以再添加一个密钥key加入到签名中,前后端保持一致,增加安全性

body参数为:{"name":"zhangsan","age":1},最后拼接的字符串为:nonce=KnjIHO9F4w#timestamp=1672554225456#{"name":"zhangsan","age":1}#date=2023-01-01#status=1#subId=1001#type=2#

注意:所有键值对都需要以#结尾

  • 步骤3:计算签名
    步骤2最后得到的字符串进行MD5计算,得到sign签名如下:0e72ded211cc19f3c00aeca54378d06b

  • 步骤4:携带Headers
    步骤3获取到的签名sign以及进行签名的时间戳timestamp放到Headers一起请求接口地址

三、服务端实现

服务端依然是采用AOP思想来实现对参数进行统一解析并签名对比的思路实现,其实现思路为:通过自定义注解,对需要进行验证签名的接口进行拦截,再按照签名规则进行签名验证,如果验证失败直接进行返回,验证成功则放行。话不多说,直接开干

4.1、创建自定义注解

sign包中创建自定义注解CheckSign

package com.light.common.sign;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 自定义验证签名注解*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME )
public @interface CheckSign {}

4.2、创建切面处理类

package com.light.common.sign;import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.light.common.exception.ServiceException;
import com.light.common.utils.RequestUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import java.util.Map;
import java.util.TreeMap;/*** 签名验证的切面处理类*/
@Aspect
@Component
@Order(3)
public class SignAspect {private static final Logger log = LoggerFactory.getLogger(SignAspect.class);//接口签名验证超时时间,默认3分钟,此处private final int EXPIRE_TIME = 180;/*** 处理请求前执行*/@Before(value = "@annotation(checkSign)")public void boBefore(JoinPoint joinPoint, CheckSign checkSign) throws ServiceException {log.info("开始验证签名");signHandle(joinPoint);}/*** 切面逻辑实现** @param joinPoint 连接点*/public void signHandle(JoinPoint joinPoint) throws ServiceException {// 获取HttpServletRequest对象ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 从请求头中获取timestamp和sign参数String timestamp = request.getHeader("timestamp");String sign = request.getHeader("sign");if (ObjectUtil.isEmpty(timestamp) || ObjectUtil.isEmpty(sign)) {throw new ServiceException("未检测到签名参数");}long requestTime = Long.parseLong(timestamp) / 1000; // 请求时间(秒)long now = System.currentTimeMillis() / 1000; // 当前时间(秒)// 如果当前时间小于请求时间,说明请求时间无效if (now < requestTime) {log.warn("请求时间无效,当前时间: {}, 请求时间: {}", now, requestTime);throw new ServiceException("请求时间无效");}// 判断请求时间与当前时间的差值是否超过过期时间if ((now - requestTime) > EXPIRE_TIME) {// 请求过期,处理过期逻辑log.warn("请求已过期,当前时间: {}, 请求时间: {}", now, requestTime);throw new ServiceException("请求已过期");}// 1. 获取并排序参数String paramStr = RequestUtil.getSortedParamString(joinPoint);// 2. 拼接Body参数和时间戳String bodyParam = RequestUtil.getBodyParameter(joinPoint);String finalStr = String.join("#", timestamp, bodyParam, paramStr);// 3. 计算签名String generatedSign = SecureUtil.md5(finalStr);log.warn("参数签名: {}, 计算签名: {}", sign, generatedSign);if (!generatedSign.equals(sign)) {throw new ServiceException("签名错误");}}}

4.3、对应工具类RequestUtil

package com.light.common.utils;import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;public class RequestUtil {/*** 获取PathVariable和QueryParams参数,并按字典序排序拼接成字符串* @param joinPoint 切点* @return 参数拼装后的字符串*/public static String getSortedParamString(JoinPoint joinPoint) {// 使用TreeMap自动排序Map<String, String> params = new TreeMap<>();// 获取QueryParams和PathVariable参数JSONObject requestParamJson = RequestUtil.getRequestParamParameterJson(joinPoint);if (requestParamJson != null) {// 如果参数不为空,将其加入到params中requestParamJson.forEach((key, value) -> params.put(key, value.toString()));}JSONObject pathVariableJson = RequestUtil.getPathVariableParameterJson(joinPoint);if (pathVariableJson != null) {// 如果参数不为空,将其加入到params中pathVariableJson.forEach((key, value) -> params.put(key, value.toString()));}// 拼接成字符串,如:date=2023-01-01#status=1#subId=1001#type=2#StringBuilder paramStr = new StringBuilder();for (Map.Entry<String, String> entry : params.entrySet()) {paramStr.append(entry.getKey()).append("=").append(entry.getValue()).append("#");}return paramStr.toString();}/*** 获取Body参数并将其转为JSON字符串* @param joinPoint 切点* @return body参数json字符串*/public static String getBodyParameter(JoinPoint joinPoint) {// 获取Body参数JSONObject bodyParamJson = RequestUtil.getRequestBodyParameterJson(joinPoint);return bodyParamJson != null ? JSONUtil.toJsonStr(bodyParamJson) : "";}/*** 获取@RequestBody参数的JSON格式对象** @param joinPoint 连接点,包含了当前方法执行的信息,主要用于获取方法参数* @return 返回RequestBody参数的JSON格式对象,如果没有找到@RequestBody参数,返回null*/public static JSONObject getRequestBodyParameterJson(JoinPoint joinPoint) {Object[] args = joinPoint.getArgs();for (int i = 0; i < args.length; i++) {Parameter parameter = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameters()[i];if (parameter.isAnnotationPresent(RequestBody.class)) {// 将RequestBody参数转换为JSON对象return JSONUtil.parseObj(args[i]);}}return null;}/*** 获取@RequestParam参数的JSON格式对象** @param joinPoint 连接点,包含了当前方法执行的信息,主要用于获取方法参数* @return 返回RequestParam参数的JSON格式对象,如果没有找到@RequestParam参数,返回null*/public static JSONObject getRequestParamParameterJson(JoinPoint joinPoint) {Object[] args = joinPoint.getArgs();Parameter[] parameters = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameters();for (int i = 0; i < parameters.length; i++) {RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);if (requestParam != null) {Map<String, Object> map = new HashMap<>();String key = parameters[i].getName();if (!StrUtil.isEmpty(requestParam.value())) {key = requestParam.value();}if (args[i] != null) {map.put(key, args[i]);}// 将@RequestParam参数转换为JSON对象return JSONUtil.parseObj(map);}}return null;}/*** 获取@PathVariable参数的JSON格式对象** @param joinPoint 连接点,包含了当前方法执行的信息,主要用于获取方法参数* @return 返回PathVariable参数的JSON格式对象,如果没有找到@PathVariable参数,返回null*/public static JSONObject getPathVariableParameterJson(JoinPoint joinPoint) {Object[] args = joinPoint.getArgs();Parameter[] parameters = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameters();for (int i = 0; i < parameters.length; i++) {PathVariable pathVariable = parameters[i].getAnnotation(PathVariable.class);if (pathVariable != null) {Map<String, Object> map = new HashMap<>();String key = parameters[i].getName();if (!StrUtil.isEmpty(pathVariable.value())) {key = pathVariable.value();}if (args[i] != null) {map.put(key, args[i]);}// 将@PathVariable参数转换为JSON对象return JSONUtil.parseObj(map);}}return null;}/*** 获取所有参数的JSON格式字符串,包含@RequestBody、@RequestParam和@PathVariable注解参数** @param joinPoint 连接点,包含了当前方法执行的信息,主要用于获取方法参数* @return 返回包含@RequestBody、@RequestParam和@PathVariable参数的JSON格式对象*/public static JSONObject getAllParameter(JoinPoint joinPoint) {// 创建一个Map来保存所有参数的JSON对象Map<String, Object> parametersJson = new HashMap<>();// 获取RequestBody参数的JSONJSONObject requestBodyJson = getRequestBodyParameterJson(joinPoint);if (requestBodyJson != null) {parametersJson.put("bodyParam", requestBodyJson);} else {parametersJson.put("bodyParam", null);}// 获取RequestParam参数的JSONJSONObject requestParamJson = getRequestParamParameterJson(joinPoint);if (requestParamJson != null) {parametersJson.put("requestParam", requestParamJson);} else {parametersJson.put("requestParam", null);}// 获取PathVariable参数的JSONJSONObject pathVariableJson = getPathVariableParameterJson(joinPoint);if (pathVariableJson != null) {parametersJson.put("pathParam", pathVariableJson);} else {parametersJson.put("pathParam", null);}// 将所有参数的JSON格式对象拼装成一个JSONObject并返回return new JSONObject(parametersJson);}
}

四、测试

我们只需要需要进行验证签名的控制器方法中加上@CheckSign即可验证自动验证签名

    @CheckSign@GetMapping("/getMsg/{subId}")public BaseResult<?> getTest(@PathVariable String subId, @RequestParam int name,@RequestBody JSONObject postData) {JSONObject data = new JSONObject();data.put("subId", subId);data.put("name", name);data.put("postData", postData);return BaseResult.success(data);}

4.1 签名失败测试:

在这里插入图片描述

  • 控制台输出
    在这里插入图片描述

4.2 签名成功测试:

在这里插入图片描述

四、总结

对请求参数进行签名验证的方式还有多种,比如WebFilter,拦截器 InterceptorAOP,RequestBodyAdviceHandlerMethodArgumentResolver等,我们这里采用的AOP主要是希望可以灵活控制哪些接口是否需要验签,且灵活度较高,实际项目可根据需求自行选择

关键字:网络公司门头照片_广告学是热门还是冷门_怎样制作一个网站_a站

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: