為何要用自定義注解
有些方法我們想要它只能被特定的用戶訪問到令境,比如用戶登錄之后才能訪問。spring 的攔截器可以配置攔截的路由顾瞪,但在 restful 風(fēng)格的路由中舔庶,往往有重復(fù)的,根據(jù) http method 來指定功能陈醒,這樣子的話直接配置攔截器路由規(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 請求頭中取出 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("用戶不存在栓始,請重新登錄");
}
// 驗(yàn)證 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 的驗(yàn)證過程和 token 的生成過程有關(guān)血当,在用戶登錄接口中幻赚,我使用的是用戶的密碼左右 token 的密鑰進(jìn)行加密(因?yàn)榉?wù)器并沒有對 token 進(jìn)行存儲(chǔ),所以加密的密鑰最好是一個(gè)用戶更改密碼之后會(huì)變的東西臊旭,我就直接用密碼了)落恼,還將 user id 存到了 JWT token 的 audience 中,因此我們能夠從 token 中知道用戶是誰巍扛。具體的JWT token 的生成和驗(yàn)證過程可以看看我們項(xiàng)目中使用的 jar 包的文檔
配置攔截器
spring boot 有很多默認(rèn)配置领跛,如果要添加攔截器之類的,就繼承 WebMvcConfigurerAdapter 類撤奸,Override 相應(yīng)的方法,來看看怎么添加我們剛剛編寫好的攔截器
新增 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 里面添加一個(gè)臨時(shí)用的測試方法
@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
請求被登錄攔截器攔截了郑什,攔截器拋出異常府喳,由全局異常處理返回了錯(cuò)誤信息。
怎樣添加 token 呢蘑拯?訪問登錄接口钝满,復(fù)制返回的token兜粘,將它添加到 header 中
返回 success,請求成功弯蚜。測試完畢孔轴,將臨時(shí)添加的測試方法刪掉吧。
查看項(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