使用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

<think>好的,我现在需要帮助用户解决如何使用AOP拦截所有实现相同接口的类的方法的问题。首先,我得理解用户的需求。用户提到他们已经有了使用拦截器和Redis进行接口访问限制的例子,还提到了Spring AOP实现重复触发拦截。现在他们想扩展这个功能,通过AOP来拦截所有实现同一接口的类的方法,并希望看到示例代码。 首先,我需要回忆一下Spring AOP的相关知识。AOP(面向切面编程)通常通过切点表达式来定义哪些方法需要被拦截。用户需要拦截的是所有实现某个特定接口的类的方法。因此,切点表达式应该匹配那些实现了目标接口的类中的方法。 接下来,我需要确定如何编写这样的切点表达式。在Spring AOP中,可以使用`execution`表达式来指定方法执行的点。要匹配实现某个接口的所有类,可能需要使用`within`表达式或者`target`表达式。比如,`target(com.example.MyInterface)`会匹配所有实现了MyInterface接口的目标对象的方法。不过需要注意,Spring AOP默认是基于代理的,对于接口的代理使用的是JDK动态代理,而类则使用CGLIB。因此,如果目标类实现了接口,Spring会优先使用JDK代理,否则用CGLIB。这可能影响切点的匹配方式。 然后,我需要考虑如何定义切面。用户需要一个@Around建议,这样可以在方法执行前后添加逻辑。同时,可能需要自定义一个注解,比如@DistinctRequest,来标记需要拦截的方法,或者直接在切点中指定接口。 示例代码的结构大致如下:首先定义一个注解(如果需要),然后编写切面类,使用@Aspect和@Component注解,定义切点表达式,并编写通知方法。例如,切点表达式可能是`execution(* com.example.MyInterface.*(..))`,这样会匹配MyInterface接口中所有方法的所有实现。 不过,用户可能希望更灵活地拦截所有实现某个接口的类,而不仅仅是显式调用接口方法的情况。这时候可能需要使用`target`表达式,比如`target(com.example.MyInterface)`,这样所有实现该接口的类的所有方法都会被拦截,无论它们是否在接口中声明。但需要注意,这可能会拦截到更多的方法,包括那些在子类中新增的方法,而不仅仅是接口中的方法。 然后,在通知方法中,用户可能需要获取方法签名参数等信息,以便进行后续处理,比如限流、日志记录等。例如,可以使用ProceedingJoinPoint来获取方法的信息,并决定是否继续执行方法。 另外,还需要考虑Spring AOP的局限性,比如只能拦截Spring管理的bean的方法,对于非Spring管理的对象无效。此外,自调用(即同一个类中的一个方法调用另一个方法)不会被AOP拦截,因为这是通过代理实现的。 在代码示例中,可能需要包括定义切面类、切点表达式、通知方法,以及可能的自定义注解。例如,用户可能希望某些方法不拦截,或者根据注解参数进行不同的处理,如设置过期时间等。 最后,需要验证示例代码的正确性,确保切点表达式正确匹配目标方法,并且通知逻辑能够按预期执行。可能还需要测试不同的场景,比如不同的实现类、不同的方法签名等,确保拦截正确无误。 总结一下,步骤大致是:确定切点表达式,定义切面类,编写通知方法,处理拦截逻辑,并考虑Spring AOP的注意事项。然后,将这些内容整理成用户容易理解的示例代码,并解释关键部分,确保用户能够根据示例实现自己的需求。</think>### 使用AOP拦截实现同一接口的所有类方法 #### 1. 定义接口和实现类 假设需要拦截所有实现`DataService`接口的类方法: ```java public interface DataService { String processData(String input); } // 实现类A @Service public class DataServiceImplA implements DataService { @Override public String processData(String input) { return "Processed by A: " + input; } } // 实现类B @Service public class DataServiceImplB implements DataService { @Override public String processData(String input) { return "Processed by B: " + input; } } ``` #### 2. 定义切面类 通过`@Pointcut`表达式匹配所有实现`DataService`接口的类方法: ```java @Aspect @Component public class InterfaceAspect { // 切点表达式:拦截所有实现DataService接口的类方法 @Pointcut("target(com.example.service.DataService)") public void interfaceMethods() {} // 环绕通知 @Around("interfaceMethods()") public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String methodName = signature.getMethod().getName(); System.out.println("拦截方法: " + methodName); Object result = joinPoint.proceed(); System.out.println("方法执行完成"); return result; } } ``` #### 3. 技术要点说明 1. **切点表达式**: - `target(com.example.service.DataService)`表示拦截所有实现该接口的类实例的方法 - 若需精确控制方法范围,可组合使用`execution`表达式: $$@Pointcut("execution(* com.example.service.DataService.*(..))")$$[^2] 2. **代理方式选择**: - 若目标类未实现接口,需强制使用CGLIB代理: $$@EnableAspectJAutoProxy(proxyTargetClass = true)$$[^2] 3. **获取方法信息**: ```java Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); Object[] args = joinPoint.getArgs(); ``` #### 4. 扩展应用场景 可结合自定义注解实现更细粒度的控制: ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ApiMonitor { int timeout() default 1000; } ``` 在切面中组合使用注解过滤: ```java @Pointcut("@annotation(com.example.annotation.ApiMonitor)") public void annotatedMethods() {} @Around("interfaceMethods() && annotatedMethods()") public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable { // 性能监控逻辑 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值