一、背景
在给第三方提供接口时,我们需要对接口进行验签。具体来说,当外部系统调用我们的接口时,请求中需要携带一个签名,我们接收到请求后,会解析数据并校验签名是否正确,以确保请求的合法性和安全性。
为了在不同项目中方便地使用这一功能,我们将签名校验规则封装成一个工具包。使用方只需通过简单的注解即可轻松集成验签功能,无需重复编写验签逻辑,从而提高开发效率并确保一致性。
二、实现原理
- 使用AOP来拦截方法
- 获取参数值进行组装、校验签名是否一致
三、设计思路
通过俩个注解进行标记所需要进行验签的方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SignatureChecker {
// 服务Code
String serviceCode() default SignatureConst.EMPTY_STR;
// 签名生成密钥
String secretKey() default SignatureConst.EMPTY_STR;
// 签名过期时间,单位为分钟
int expireMinutes() default -1;
// 默认为true,表示需要验证签名
boolean required() default true;
// 返回值类型
String returnType() default SignatureConst.DEFAULT_RETURN_TYPE;
}
serviceCode:服务编码,进行区分不同的服务/业务
secretKey:双方约定好的密钥,进行生成签名,可以写在配置文件中。
expireMinutes:标识签名有效时长,默认5分钟,可以配置文件中进行全局修改。
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface SignatureParam {
// 0:标识serviceCode 1:标识请求参数
SignatureParamTypeEnum type() default SignatureParamTypeEnum.PARAMS;
String requestIdField() default SignatureConst.EMPTY_STR;
String timestampField() default SignatureConst.EMPTY_STR;
String signatureField() default SignatureConst.EMPTY_STR;
}
对于不同的请求实体,可能对应的字段名不相同,所以我们需要使用一个注解进行标注当前实体验签字段的名称。
当签名字段发生变化时,可以使用requestIdField、timestampField、signatureField 字段进行指定。
四、代码
4.1 代码结构
4.2 详细代码
4.2.1 SignatureChecker.class
import org.tao.consts.SignatureConst;
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 SignatureChecker {
// 服务Code
String serviceCode() default SignatureConst.EMPTY_STR;
// 签名生成密钥
String secretKey() default SignatureConst.EMPTY_STR;
// 签名过期时间,单位为分钟
int expireMinutes() default -1;
// 默认为true,表示需要验证签名
boolean required() default true;
// 返回值类型
String returnType() default SignatureConst.DEFAULT_RETURN_TYPE;
}
4.2.2 SignatureParam.class
import org.tao.consts.SignatureConst;
import org.tao.enums.SignatureParamTypeEnum;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface SignatureParam {
// 0:标识serviceCode 1:标识请求参数
SignatureParamTypeEnum type() default SignatureParamTypeEnum.PARAMS;
String requestIdField() default SignatureConst.EMPTY_STR;
String timestampField() default SignatureConst.EMPTY_STR;
String signatureField() default SignatureConst.EMPTY_STR;
}
4.2.3 SignatureAspect.class
import com.alibaba.fastjson2.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.tao.anno.SignatureChecker;
import org.tao.anno.SignatureParam;
import org.tao.config.SignatureProperties;
import org.tao.consts.SignatureConst;
import org.tao.enums.SignatureParamTypeEnum;
import org.tao.exception.SignatureValidationException;
import org.tao.utils.SignatureUtil;
import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Map;
@Aspect
@Component
public class SignatureAspect {
private static final Logger logger = LoggerFactory.getLogger(SignatureAspect.class);
@Resource
private SignatureProperties signatureProperties;
@Around("@annotation(org.tao.anno.SignatureChecker) " +
"&& (@annotation(org.springframework.web.bind.annotation.PostMapping) || @annotation(org.springframework.web.bind.annotation.RequestMapping))")
public Object validateSignature(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Map<String, Object> paramMap = null;
String serviceCode = null;
Object[] args = joinPoint.getArgs();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
SignatureChecker s