使用aop结合redis进行方法参数的签名等验证

本文介绍了一种基于Spring Boot的签名验证机制实现方案,包括自定义注解、AOP切面编程进行参数校验、异常处理等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在controller中直接对方法进行验证,最初想着简单省事;因为springboot貌似对数据请求二次解析,需要请求转发处理,后续补上;

1.自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SignValidate {
    String name() default "";
}

2.解析注解,使用Before(),抛出异常 全局异常处理的方式;
若使用aroud(),依然会走方法体;
/***
 * 验证sign
 * appid  appsecret Header
 * timestamp 13位毫秒 Header
 * sign MD5(appid+appkey+timestamp) 32位小写 Header 
 *   1.appkey最好存在服务端
 *   2.RSA更安全
 */
@Slf4j
@Aspect
@Component
public class SignValidateAspect {
    private final static Long REDIS_SIGN_EXPIRE_TIME = 5 * 60 * 1000L;
    private final static String REDIS_KEY_PREFIX = "APPID:";
    private final static String REDIS_APPSIGN_PREFIX = "APPSIGN:";

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    SysApplicationService sysApplicationService;

    @Pointcut("@annotation(com.xx.xx.annotation.SignValidate)")
    public void pointCut() {
    }

    // 验证时间是否有效;验证sign是否有效;验证sign是否被使用过,防止被人截取一直请求
    @Before(value = "pointCut()")
    public void before(JoinPoint point) {
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = sra.getRequest();
        String timestamp = request.getHeader("timestamp");
        String appid = request.getHeader("appid");
        String sign = request.getHeader("sign");

        if (StringUtils.isBlank(appid) || StringUtils.isBlank(sign) || StringUtils.isBlank(timestamp)) {
            throw new SignValidateException(ReturnCodeEnum.INVALID_HEADER);
        }

        // 验证时间是否有效
        long now = System.currentTimeMillis();
        if (Math.abs(now - Long.valueOf(timestamp)) >= REDIS_SIGN_EXPIRE_TIME) {
            // 过期
            throw new SignValidateException(ReturnCodeEnum.INVALID_TIMESTAMP);
        }

        // 验证sign MD5(appid+appkey+timestamp) 也可以将appkey放在配置文件中
//        String appSecret = redisTemplate.opsForValue().get(REDIS_KEY_PREFIX + appid);
        String appSecret = sysApplicationService.getAppSecret(appid);
        if (StringUtils.isBlank(appSecret)) {
            appSecret = DigestUtils.md5DigestAsHex(appid.getBytes(StandardCharsets.UTF_8)).substring(10, 14);
        }
        String md5Str = appid + appSecret + timestamp;
        log.info(md5Str);
        String encode = DigestUtils.md5DigestAsHex(md5Str.getBytes(StandardCharsets.UTF_8));
        log.info(encode);

        if (!StringUtils.equals(sign, encode) || redisTemplate.hasKey(REDIS_APPSIGN_PREFIX + sign)) {
            throw new SignValidateException(ReturnCodeEnum.INVALID_SIGN);
        }

        // 保存sign
        redisTemplate.opsForValue()
                .set(REDIS_APPSIGN_PREFIX + sign, "1", REDIS_SIGN_EXPIRE_TIME, TimeUnit.MILLISECONDS);
    }

}


3.自定义异常
@Data
public class SignValidateException extends RuntimeException {
    private ReturnCodeEnum returnCodeEnum;

    private SignValidateException() {}

    public SignValidateException(ReturnCodeEnum returnCodeEnum) {
        super(returnCodeEnum.getMsg());
        this.returnCodeEnum = returnCodeEnum;
    }
}

4.全局异常处理@RestcontrollerAdvice
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * @param e
     * @return
     * @Validated 验证签名
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(SignValidateException.class)
    public ResponseWrapper handler(SignValidateException e) throws IOException {
        return ResponseWrapper.error(e.getReturnCode());
    }

    /**
     * 系统异常
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(value = Exception.class)
    public ResponseWrapper exceptionHandler(Exception e) {
        log.info(e.getMessage(),e);
        return ResponseWrapper.error(e.getMessage());
    }
}

5.在需要验证的地方加@SignValidate

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值