注解实现接口拦截功能
方式一
我们在Spring boot项目中时候,一般会采用token作为身份验证,这样也方便前端做免登录功能
- 一般方式:我们会自定义一个拦截器,继承WebMvcConfigurer类,重写addInterceptors方法,效果如下:
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 不需要拦截校验的url
String[] clientUserApiArrays = {
Constants.CLIENT_URI_V1 + "/users/token",
Constants.CLIENT_URI_V1 + "/users/we_chat_login"
};
registry.addInterceptor(clientUserApiInterceptor).addPathPatterns("/api/v*/client/**").excludePathPatterns(clientUserApiArrays);
}
}
将不用token验证的接口地址过滤掉,这样也是很不错的做法
- 第二种方式
采用注解的方式
- 先看下在代码中的应用,看看是不是你要的效果,不需要验证默认是不用加的
@ApiOperation(value = "获取推荐商户列表", notes = "获取推荐商户列表")
@GetMapping("/getMerchantList")
@UserToken
public BaseResult<List<MerchantVO>> getMerchantList() {
return BaseResult.success(iMerchantService.getMerchantList());
}
- 先定义两个注解用来区分需要token验证和不需要token验证
PassToken注解(对方法使用)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
UserToken注解(对方法使用)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserToken {
boolean required() default true;
}
- 我们对刚才继承WebMvcConfigurer类改造一下
@Configuration
public class UserApiInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns(Constants.APP_URI_V1 + "/**");
// 对所有的接口进行拦截,判断是否包含有上面的验证注解
}
// UserApiInterceptor拦截器
@Bean
public UserApiInterceptor authenticationInterceptor() {
return new UserApiInterceptor();
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
// 注册@CurrentUser注解的实现类
resolvers.add(new CurrentUserHandlerMethodArgResolver());
}
}
UserApiInterceptor类(用于拦截区分带UserToken的注解或者PassToken的注解)
@Slf4j
@Component
public class UserApiInterceptor implements HandlerInterceptor {
@Autowired
IUserService iUserService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
log.info("请求地址[{}], 请求参数[{}]", request.getRequestURI(), JSONObject.toJSON(request.getParameterMap()));
String token = request.getHeader(JwtTokenUtil.HEADER_STRING);
// 如果不是映射到方法直接通过
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
// 检查是否有passToken注释,有则跳过认证
if (method.isAnnotationPresent(PassToken.class)) {
PassToken passToken = method.getAnnotation(PassToken.class);
if (passToken.required()) {
return true;
}
}
// 检查有没有需要用户权限的注解
if (method.isAnnotationPresent(UserToken.class)) {
UserToken userToken = method.getAnnotation(UserToken.class);
if (userToken.required()) {
// 执行认证
if (StringUtils.isEmpty(token)) {
throw new TokenValidException(300, "token校验失败!未获取到token");
}
Boolean valid = JwtTokenUtil.validateJWTToken(token);
if (!valid) {
throw new TokenValidException(300, "token校验失败!请检查");
}
User user = iUserService.getById(JwtTokenUtil.getJwtUserId(request));
if (user == null) {
throw new BusinessException(300, "用户信息为空");
}
UserInfoVO userInfoVO = new UserInfoVO();
BeanUtils.copyProperties(user, userInfoVO);
//我们将解析的用户结果先放入session中
request.getSession().setAttribute("currentUser",userInfoVO);
}
}
return true;
}
}
CurrentUserHandlerMethodArgResolver类(用于获取CurrentUser注解对象的值,后面会介绍)
@Slf4j
@Component
public class CurrentUserHandlerMethodArgResolver implements HandlerMethodArgumentResolver {
/**
* 判断是否支持使用@CurrentUser注解的参数
*/
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
// 如果该参数注解有@CurrentUser且参数类型是User
return methodParameter.getParameterAnnotation(CurrentUser.class) != null && methodParameter.getParameterType() == UserInfoVO.class;
}
/**
* 注入参数值
*/
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
// 取得HttpServletRequest
HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest();
// 取出session中的User
UserInfoVO userInfoVO = (UserInfoVO) request.getSession().getAttribute("currentUser");
log.info("currentUser id:{}", userInfoVO.getId());
return userInfoVO;
}
}
-
这时候便可以使用这个上面PassToken和UserToken注解了(有些代码工具缺失,最后有源码包可供参考)
@ApiOperation(value = "获取推荐商户列表", notes = "获取推荐商户列表") @GetMapping("/getMerchantList") @UserToken public BaseResult<List<MerchantVO>> getMerchantList() { return BaseResult.success(iMerchantService.getMerchantList()); }
-
我们来看下CurrentUser的注解的用途
项目中我们一般通过token验证来验证某个用户是否是登录状态,我们只验证登录状态,不去获取这个用户的信息吗,一般情况下,我们也可以通过Request这种方式,将token(id)传入每个方法中,然后用该token获取获取id,在通过id获取一些信息,这样也是可以的,这边我们tokenCurrentUser注解,直接将用户信息拿到,供后面开发使用,即加即用,先看效果:
@ApiOperation(value = "获取轮播图列表", notes = "获取轮播图列表接口")
@GetMapping("/getBannerList")
@UserToken
public BaseResult<List<BannerVO>> getBannerList(@CurrentUser UserInfoVO userInfoVO) {
// 测试CurrentUser注解
log.info("UserInfoVo" + userInfoVO.toString());
return BaseResult.success(iBannerService.getBannerList());
}
打印结果:
2020-12-29 16:41:13.788 INFO 12632 --- [nio-8081-exec-3] com.llayjun.millet.api.BannerController : UserInfoVoUserInfoVO{id='1319550471008100353', name='1', sex=0, mobile='1', passWord='1'}
CurrentUser注解类(对对象使用)
@Target({ElementType.PARAMETER}) // Annotation所修饰的对象范围:方法参数
@Retention(RetentionPolicy.RUNTIME) // Annotation被保留时间:运行时保留(有效)
@Documented
public @interface CurrentUser {
}
- 源代码