如何在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;
  }
}

关于如何在SpringBoot中使用Spring-AOP实现接口鉴权的文章就介绍至此,更多相关 SpringBoot Spring-AOP 接口鉴权内容请搜索编程宝库以前的文章,希望以后支持编程宝库

本文讲解"Java中cas的实现原理是什么",希望能够解决相关问题。 java提供了三个CAS操作不安全:compareAndSwapLong compareAndSwapObject c ...