為何要用自定義注解
有些方法我們想要它只能被特定的用戶訪問到,比如用戶登錄之后才能訪問。spring 的攔截器可以配置攔截的路由,但在 restful 風(fēng)格的路由中,往往有重復(fù)的,根據(jù) http method 來(lái)指定功能,這樣子的話直接配置攔截器路由規(guī)則也不太方便。所以我們可以自定義一個(gè)注解,將它用在需要登錄的方法中,然后在攔截器中判斷要訪問的方法是否有我們自定義的注解,如果有就判斷當(dāng)前用戶是否登錄了(判斷是否攜帶了登錄之后獲取到的 token ),從而決定是否攔截。
編寫一個(gè)自定義注解
這篇文章新增的文件如下

新增 LoginRequired.java
/**
* 在需要登錄驗(yàn)證的Controller的方法上使用此注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}
ElementType.MeTHOD 表示該自定義注解可以用在方法上
RetentionPolicy.RUNTIME 表示該注解在代碼運(yùn)行時(shí)起作用
編寫登錄攔截器
新增 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 注解,需要認(rèn)證
if (methodAnnotation != null) {
// 執(zhí)行認(rèn)證
String token = request.getHeader("token"); // 從 http 請(qǐng)求頭中取出 token
if (token == null) {
throw new RuntimeException("無(wú)token,請(qǐng)重新登錄");
}
int userId;
try {
userId = Integer.parseInt(JWT.decode(token).getAudience().get(0)); // 獲取 token 中的 user id
} catch (JWTDecodeException e) {
throw new RuntimeException("token無(wú)效,請(qǐng)重新登錄");
}
User user = userService.findById(userId);
if (user == null) {
throw new RuntimeException("用戶不存在,請(qǐng)重新登錄");
}
// 驗(yàn)證 token
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
verifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException("token無(wú)效,請(qǐng)重新登錄");
}
} catch (UnsupportedEncodingException ignore) {}
request.setAttribute("currentUser", user);
return true;
}
return true;
}
token 的驗(yàn)證過程和 token 的生成過程有關(guān),在用戶登錄接口中,我使用的是用戶的密碼左右 token 的密鑰進(jìn)行加密(因?yàn)榉?wù)器并沒有對(duì) token 進(jìn)行存儲(chǔ),所以加密的密鑰最好是一個(gè)用戶更改密碼之后會(huì)變的東西,我就直接用密碼了),還將 user id 存到了 JWT token 的 audience 中,因此我們能夠從 token 中知道用戶是誰(shuí)。具體的JWT token 的生成和驗(yàn)證過程可以看看我們項(xiàng)目中使用的 jar 包的文檔
配置攔截器
spring boot 有很多默認(rèn)配置,如果要添加攔截器之類的,就繼承 WebMvcConfigurerAdapter 類,Override 相應(yīng)的方法,來(lái)看看怎么添加我們剛剛編寫好的攔截器
新增 WebMvcConfigurer.java
@Configuration
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**"); // 攔截所有請(qǐng)求,通過判斷是否有 @LoginRequired 注解 決定是否需要登錄
super.addInterceptors(registry);
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}
測(cè)試
在 userApi.java 里面添加一個(gè)臨時(shí)用的測(cè)試方法
@GetMapping("/test")
public Object testLogin() {
return "success";
}
重啟項(xiàng)目
訪問 /api/user/test

正常返回 “success” 字符串。現(xiàn)在給 testLogin 方法加上自定義的 @LoginRequired 注解
@LoginRequired
@GetMapping("/test")
public Object testLogin() {
return "success";
}
重啟項(xiàng)目,再次訪問 /api/use/test

請(qǐng)求被登錄攔截器攔截了,攔截器拋出異常,由全局異常處理返回了錯(cuò)誤信息。
怎樣添加 token 呢?訪問登錄接口,復(fù)制返回的token,將它添加到 header 中

返回 success,請(qǐng)求成功。測(cè)試完畢,將臨時(shí)添加的測(cè)試方法刪掉吧。
查看項(xiàng)目完整代碼
項(xiàng)目地址: https://github.com/hyrijk/spring-boot-blog
克隆項(xiàng)目到本地
git clone https://github.com/hyrijk/spring-boot-blog.git
checkout 到當(dāng)前版本
git checkout b7498954eba034b82b3619a3f07b62f48d390eb0