面向切面编程,可以将与业务无关但是需要被各个业务模块共同调用的逻辑抽取出来,以切面的方式切入到代码中,从而降低系统中代码的耦合度,减少重复的代码。
Spring AOP是通过预编译方式和运行期间动态代理实现程序面向切面编程
AOP底层使用动态代理完成需求,为需要增加增强功能的类来生成代理类,有两种生成代理类的方式,对于被代理类(即需要增强的类),如果:
注解:
使用切入点表达式声明切入点
execution(* com.xxx.ABC.add()),对ABC类的方法进行增强
配置接口鉴权账密
account: infos: - account: xinchao secret: admin
@Data public class SecretInfo { private String account; private String secret; }
@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); } }
首先,编写一个注解来标识不需要鉴权
@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(); } }
@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; } }