• 在SpringBoot中使用Spring-AOP实现接口鉴权


    面向切面编程

    面向切面编程,可以将与业务无关但是需要被各个业务模块共同调用的逻辑抽取出来,以切面的方式切入到代码中,从而降低系统中代码的耦合度,减少重复的代码。

    Spring AOP是通过预编译方式和运行期间动态代理实现程序面向切面编程

    AOP的底层原理实现

    AOP底层使用动态代理完成需求,为需要增加增强功能的类来生成代理类,有两种生成代理类的方式,对于被代理类(即需要增强的类),如果:

    • 实现了接口,使用JDK动态代理,生成的代理类会使用其接口
    • 没有实现接口,使用CGlib动态代理,生成的代理类会集成被代理类

    AOP的相关术语

    • 连接点:被代理(被增强)的类中的方法
    • 切入点:实际上需要被增强的方法
    • 通知:要增强的逻辑代码
      • 前置通知:在主体功能执行之前执行
      • 后置通知:在主题功能执行之后执行
      • 环绕通知:在主体功能执行前后执行
      • 异常通知:在主题功能执行出现异常时执行
      • 最终通知:主体功能无论执行是否成功都会执行
    • 切面:切入点和切面的结合,即被增强的方法和增强的功能组成切面

    相关注解以及切入点表达式

    注解
    • @Aspect: 声明某个类是切面,编写通知、切入点
    • @Before: 对应前置通知
    • @AfterReturning: 对应后置通知
    • @Around: 对应环绕通知
    • @AfterThrowing: 对应异常通知
    • @After: 对应最终通知
    • @Pointcut: 声明切入点,标注在一个方法上可以让表达式更简洁
    使用切入点表达式声明切入点
    • execution([权限修饰符][返回类型][类完全路径].[方法名称][参数列表类型])
      • execution(* com.xxx.ABC.add()),对ABC类的方法进行增强

    实现接口鉴权

    1. 配置yml文件

    配置接口鉴权账密

    account:
      infos:
        - account: xinchao
          secret: admin
    
    2. 读取账密配置
    @Data
    public class SecretInfo {
        private String account;
        private String secret;
    }
    
    3.编写接口鉴权方法
    @Configuration
    @ConfigurationProperties("account")
    public class SecretConfig {
        private List<SecretInfo> infos;
    
        private Map<String, SecretInfo> map;
    
        private Map<String, TokenInfo> tokenMap = new HashMap<>();
    
        public void setInfos(List<SecretInfo> infos) {
            this.infos = infos;
            map = infos.stream().collect(Collectors.toMap(SecretInfo::getAccount, Function.identity()));
        }
    
        public synchronized String getToken(String account, String secret) {
            SecretInfo info = map.get(account);
            if (info == null) {
                throw new BusinessException("无效账号");
            }
            if (!StringUtils.equals(info.getSecret(), secret)) {
                throw new BusinessException("无效密码");
            }
            TokenInfo tokenInfo = tokenMap.get(account);
            if (tokenInfo != null && tokenInfo.getToken() != null) {
                return tokenInfo.getToken();
            }
            tokenInfo = new TokenInfo();
            String uuid = UUID.randomUUID().toString();
            tokenInfo.setToken(uuid);
            tokenInfo.setCreateDate(LocalDateTime.now());
            tokenInfo.setExpireDate(LocalDateTime.now().plusHours(2));
            tokenMap.put(account,tokenInfo);
            return tokenInfo.getToken();
        }
    
        public boolean checkCaptcha(String captcha) {
            return tokenMap.values().stream().anyMatch(e->StringUtils.equals(e.getToken(),captcha));
        }
    }
    
    @Data
    public class TokenInfo {
        private LocalDateTime createDate;
        private LocalDateTime expireDate;
        private String token;
    
        public String getToken() {
            if (LocalDateTime.now().isBefore(expireDate)) {
                return token;
            }
            return null;
        }
    
        public boolean verification(String token) {
            return Objects.equals(this.token, token);
        }
    }
    
    4. 编写AOP

    首先,编写一个注解来标识不需要鉴权

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface CaptchaIgnoreAop {
    }
    
    @Slf4j
    @Aspect
    @Component
    @Order(2)
    public class CaptchaAop {
    
        @Value("${spring.profiles.active:dev}")
        private String env;
    
        @Autowired
        private SecretConfig config;
    
        @Pointcut("execution(public * com.herenit.phsswitch.controller.impl..*.*(..))" +
                "&&@annotation(org.springframework.web.bind.annotation.PostMapping)" +
                "&&!@annotation(com.herenit.phsswitch.aop.CaptchaIgnoreAop)")
        public void tokenAop() {
        }
    
        @Around("tokenAop()")
        public Object doBefore(ProceedingJoinPoint joinPoint) throws Throwable {
            Object[] args = joinPoint.getArgs();
            if (args.length == 0 || !(args[0] instanceof RequestWrapper)
                    || "test,dev".contains(env)) {
                log.info("当前环境无需校验token");
                return joinPoint.proceed();
            }
            String captcha = ((RequestWrapper) joinPoint.getArgs()[0]).getCaptcha();
            if (!config.checkCaptcha(captcha)) {
                throw new BusinessException("captcha无效");
            }
            return joinPoint.proceed();
        }
    
    }
    
    5.编写接口测试
    @PostMapping("/login")
    @CaptchaIgnoreAop
    public ResponseWrapper login(@RequestBody JSONObject userInfo) {
        String token = config.getToken(userInfo.getString("loginName")
                , userInfo.getString("password"));
        JSONObject result = new JSONObject();
        result.put("platformAccessToken", token);
        return ResponseWrapper.success(result);
    }
    

    通过这个接口,我们可以在内存中生成一个token,同时也会返回给前端。之后我们在调其他接口时传入这个token进行鉴权即可。传入的位置是captcha字段

    public class RequestWrapper<T> implements Serializable {
    
        private static final long serialVersionUID = 8988706670118918321L;
    
        public RequestWrapper() {
            super();
        }
    
        private T args;
    
        private String captcha;
    
        private String funcode;
    
        public T getArgs() {
            return args;
        }
    
        public void setArgs(T args) {
            this.args = args;
        }
    
        public String getCaptcha() {
            return captcha;
        }
    
        public void setCaptcha(String captcha) {
            this.captcha = captcha;
        }
    
        public String getFuncode() {
            return funcode;
        }
    
        public void setFuncode(String funcode) {
            this.funcode = funcode;
        }
    }
    
  • 相关阅读:
    Unity 阴影——阴影平坠(Shadow pancaking)
    Linux python2 python3 切换
    电源硬件设计----升降压变换器(负压输出)基础
    MySQL中的锁和Redis的分布式锁的详细介绍,看这一篇就够啦!
    [附源码]Python计算机毕业设计Django港口集团仓库管理系统
    Zabbix干啥用?
    leetCode 567. 字符串的排列
    JavaSE - 多态
    《最新出炉》系列入门篇-Python+Playwright自动化测试-51- 字符串操作 - 上篇
    LRTimelapse 6 for Mac(延时摄影视频制作软件)
  • 原文地址:https://blog.csdn.net/qq_42582773/article/details/127069099