使用aop结合redis进行方法参数的签名等验证
迪丽瓦拉
2024-02-05 19:56:04
0

在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

上一篇:关于ebpf 的co-re

下一篇:计算机基础知识

相关内容