我们想在项目的一些接口上增加权限校验的功能,最方便的方式就是通过AOP切面的方式。这里我们利用自定义自定义注解的方式来实现这一功能
首先自定义一个注解
@Retention(value = RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SignatureValidation {
}
在需要验证的controller上使用该注解
@RestController
@RequestMapping
@SignatureValidation
public class ChainOrgController {
}
利用切进行对权限进行校验,这里我们介绍两种方式
a. 使用AspectJ的方式
@Aspect
@Component
@Slf4j
public class SignatureValidationAop {
private static final Map<String, String> map = new HashMap<>();
/**
* 时间戳请求最大限制
*/
private static final long MAX_REQUEST = 30 * 1000L;
@PostConstruct
public void init(){
System.out.println("系统启动中。。。加载map");
}
/*
* 验签切点
*/
@Pointcut("@within(com.test.SignatureValidation)")//有SignatureValidation注解的类,也可以兼容方法级别需要增加切点范围https://blog.csdn.net/sunlihuo/article/details/52701548
private void verifyUserKey() {
}
/**
* 开始验签
*/
@Before("verifyUserKey()")
public void doBasicProfiling() {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String token = request.getHeader("token");
String timestamp = request.getHeader("timestamp");
String orgId = request.getHeader("orgId");
try {
Boolean check = checkToken(token, timestamp, orgId);
if (!check) {
throw new RuntimeException("验签失败!");
}
} catch (Throwable throwable) {
throw new RuntimeException("验签失败!");
}
}
/**
* 校验token
*
* @param token 签名
* @param timestamp 时间戳
* @return 校验结果
*/
private Boolean checkToken(String token, String timestamp, String orgId) {
log.info("checktoken:{},{},{}", token, timestamp, orgId);
if (StringUtils.isAnyBlank(token, timestamp)) {
return false;
}
long now = System.currentTimeMillis();
long time = Long.parseLong(timestamp);
if (now - time > MAX_REQUEST) {
return false;
}
String secret = map.get(orgId);
String crypt = MD5Utils.getMD5(secret + orgId + timestamp);
return StringUtils.equals(crypt, token);
}
}
b. 实现HandlerInterceptor
@Slf4j
public class SignValidateInterceptor implements HandlerInterceptor {
private static final Map<String, String> map = new HashMap<>();
/**
* 时间戳请求最大限制
*/
private static final long MAX_REQUEST = 30 * 1000L;
@PostConstruct
public void init(){
System.out.println("系统启动中。。。加载codeMap");
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
boolean flag = true;
if (handler instanceof HandlerMethod) {
// 访问的是接口方法,转化为待访问的目标方法对象
HandlerMethod targetMethod = (HandlerMethod) handler;
// 获取目标接口方法所在类的注解@AccessLimit
SignatureValidation targetClassAnnotation = targetMethod.getMethod().getDeclaringClass().getAnnotation(SignatureValidation.class);
if (!Objects.isNull(targetClassAnnotation)) {
String token = request.getHeader("token");
String timestamp = request.getHeader("timestamp");
String orgId = request.getHeader("orgId");
flag = checkToken(token, timestamp, orgId);
if(!flag){
throw new CommonException("权限校验出错,请重试!");
}
}
}
return true;
}
private Boolean checkToken(String token, String timestamp, String orgId) {
log.info("checktoken:{},{},{}", token, timestamp, orgId);
if (StringUtils.isAnyBlank(token, timestamp)) {
return false;
}
long now = System.currentTimeMillis();
long time = Long.parseLong(timestamp);
if (now - time > MAX_REQUEST) {
return false;
}
String secret = map.get(orgId);
String crypt = MD5Utils.getMD5(secret + orgId + timestamp);
return StringUtils.equals(crypt, token);
}
}
使用这种方式需要把拦截器在WebConfiguration注册,定义拦截规则
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Bean
public SignValidateInterceptor createSignValidateInterceptor(){
return new SignValidateInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(createSignValidateInterceptor())
.addPathPatterns("/**");
}
}