aop切面编程真是个好东西,他类似于过滤器又不同于过滤器,因为这次项目中需要对用户的token进行验证。同事写了一个过滤器,但是他要加黑名单(不是所有请求都需要验证token),这样一来这个黑名单就很难维护。所以我想到了aop,可以使用自定义注解的形式来验证用户的token信息。
另外springboot中使用aop也很方便,几个注解就搞定了
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
自定义一个注解
import org.springframework.web.bind.annotation.Mapping;
import java.lang.annotation.*;
@Documented
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
public @interface CheckToken {}
其中@Target中的类别我写了两个,一个表示作用于方法,一个表示作用于类上:
配置aop切面
@Configuration
@Aspect
@Slf4j
public class AopConfig {
@Value("${tokenConfig.ssoUrl}")
private String ssoUrl;
@Value("${tokenConfig.open}")
private boolean open;
@Pointcut("@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController)")
public void pointcut() {
}
public Object simpleAop(final ProceedingJoinPoint joinPoint) throws Throwable {
try {
// 调用原有的方法
Long befor = System.currentTimeMillis();
Object o = joinPoint.proceed();
Long after = System.currentTimeMillis();
log.info("调用方法结束===================共耗时:" + (after - befor) + "毫秒");
log.info("方法返回:return:====================" + o);
return o;
} catch (Throwable throwable) {
throw throwable;
}
}
public void doBefore(JoinPoint joinPoint) throws Exception {
log.info("进入方法>>>>>>>" + getFunctionName(joinPoint));
Object[] args = joinPoint.getArgs();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
if (open){
int userId = SupperTokenUtils.getUserByToken(request);
if(userId==-1){
log.info("用户信息无效,请注册或登录!");
throw new BusinessException("-1", "用户信息无效,请注册或登录!");
}
checked(request, response);
}
String url = request.getRequestURL().toString();
log.info("请求URL:【{}】,\n请求参数:【{}】", url, args);
Long befor = System.currentTimeMillis();
log.info("方法开始时间:【{}】", befor);
}
public void doAfter() {
// todo 可以做一些事情
Long after = System.currentTimeMillis();
log.info("方法结束时间:" + after);
}
private String getFunctionName(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return signature.getName();
// CheckToken annotation = signature.getMethod().getAnnotation(CheckToken.class);
}
/**
* 获取目标主机的ip
*/
private String getRemoteHost(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}
private boolean checked(HttpServletRequest request, HttpServletResponse response) throws BusinessException {
boolean isDoFilter = true;
String token = request.getHeader("token");
if (token == null) {
//2表示没有传token
throw new BusinessException("-1", "请求参数token为空!");
}
try {
HttpResponse httpResponse = HttpReqUtil.postObjectReq(ssoUrl, token);
Map<String, Object> responsemap = HttpReqUtil.parseHttpResponse(httpResponse);
String isPassedStr = responsemap.get("isPassed").toString();
int i = Integer.valueOf(isPassedStr).intValue();
/**
* 返回0表示验证通过,-1表示不通过
*/
if (i == -1)
isDoFilter = false;
} catch (Exception e) {
e.printStackTrace();
log.info("请求验证token失败");
throw new BusinessException("3", "sso平台故障!");
}
if (!isDoFilter) {
throw new BusinessException("-1", "token验证失败!!");
}
return true;
}
}
@Pointcut:切入点我写的是controller和restController都算在内
@Around:满足切入点的同时还要满足自定义的注解,也就是必须写了自定义注解才会触发
ssoUrl:表示单点登录的主机地址,用来验证用户token
open:设置了控制token校验的总开关
效果
当我在类上或者方法上加了这个注解后,就会执行aop中定义的逻辑去校验token。
另外需要捕获异常情况的话可以参考:https://blog.csdn.net/qq_29807783/article/details/120154329;
不过项目一般都有统一的异常捕获机制,很少有在aop这里进行异常处理的。