为何要用自定义注解
有些方法我们想要它只能被特定的用户访问到,比如用户登录之后才能访问。spring 的拦截器可以配置拦截的路由,但在 restful 风格的路由中,往往有重复的,根据 http method 来指定功能,这样子的话直接配置拦截器路由规则也不太方便。所以我们可以自定义一个注解,将它用在需要登录的方法中,然后在拦截器中判断要访问的方法是否有我们自定义的注解,如果有就判断当前用户是否登录了(判断是否携带了登录之后获取到的 token ),从而决定是否拦截。
编写一个自定义注解
这篇文章新增的文件如下
新增 LoginRequired.java
/**
* 在需要登录验证的Controller的方法上使用此注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}
ElementType.MeTHOD
表示该自定义注解可以用在方法上
RetentionPolicy.RUNTIME
表示该注解在代码运行时起作用
编写登录拦截器
新增 AuthenticationInterceptor.java
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
// 如果不是映射到方法直接通过
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 判断接口是否需要登录
LoginRequired methodAnnotation = method.getAnnotation(LoginRequired.class);
// 有 @LoginRequired 注解,需要认证
if (methodAnnotation != null) {
// 执行认证
String token = request.getHeader("token"); // 从 http 请求头中取出 token
if (token == null) {
throw new RuntimeException("无token,请重新登录");
}
int userId;
try {
userId = Integer.parseInt(JWT.decode(token).getAudience().get(0)); // 获取 token 中的 user id
} catch (JWTDecodeException e) {
throw new RuntimeException("token无效,请重新登录");
}
User user = userService.findById(userId);
if (user == null) {
throw new RuntimeException("用户不存在,请重新登录");
}
// 验证 token
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
verifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException("token无效,请重新登录");
}
} catch (UnsupportedEncodingException ignore) {}
request.setAttribute("currentUser", user);
return true;
}
return true;
}
token 的验证过程和 token 的生成过程有关,在用户登录接口中,我使用的是用户的密码左右 token 的密钥进行加密(因为服务器并没有对 token 进行存储,所以加密的密钥最好是一个用户更改密码之后会变的东西,我就直接用密码了),还将 user id 存到了 JWT token 的 audience 中,因此我们能够从 token 中知道用户是谁。具体的JWT token 的生成和验证过程可以看看我们项目中使用的 jar 包的文档
配置拦截器
spring boot 有很多默认配置,如果要添加拦截器之类的,就继承 WebMvcConfigurerAdapter 类,Override 相应的方法,来看看怎么添加我们刚刚编写好的拦截器
新增 WebMvcConfigurer.java
@Configuration
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**"); // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
super.addInterceptors(registry);
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}
测试
在 userApi.java 里面添加一个临时用的测试方法
@GetMapping("/test")
public Object testLogin() {
return "success";
}
重启项目
访问 /api/user/test
正常返回 “success” 字符串。现在给 testLogin 方法加上自定义的 @LoginRequired 注解
@LoginRequired
@GetMapping("/test")
public Object testLogin() {
return "success";
}
重启项目,再次访问 /api/use/test
请求被登录拦截器拦截了,拦截器抛出异常,由全局异常处理返回了错误信息。
怎样添加 token 呢?访问登录接口,复制返回的token,将它添加到 header 中
返回 success,请求成功。测试完毕,将临时添加的测试方法删掉吧。
查看项目完整代码
项目地址: https://github.com/hyrijk/spring-boot-blog
克隆项目到本地
git clone https://github.com/hyrijk/spring-boot-blog.git
checkout 到当前版本
git checkout b7498954eba034b82b3619a3f07b62f48d390eb0