JWT的全稱為json web token。不要把它想得多么高深,其實就是一種生成token的方式。一般我們訪問一個系統(tǒng)的流程就是:請求登錄接口,該接口會返回一個token,請求其他接口都要帶上token,token驗證通過才能訪問成功,而JWT可以理解為就是生成token的一種機制。
1、添加jwt的依賴:
<!-- JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
2、新建一個TokenService,用來生成token:
@Service
public class TokenService {
// token過期時間5分鐘
private static final long EXPIRE_TIME = (60 * 1000 * 5);
public String getToken(User user) {
return JWT.create()
// 需要放入token中的信息
.withAudience(user.getId())
// 設置token過期時間
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRE_TIME))
// 用戶密碼當作密鑰
.sign(Algorithm.HMAC256(user.getPassword()));
}
}
User類就是一個普通的pojo,這里就不把代碼貼出來了。這個getToken方法表示將用戶的密碼作為密鑰,把用戶的id放進token中,設置token過期時間為5分鐘。
3、新建兩個注解,一個注解表示需要驗證,另一個表示跳過驗證:
需要驗證:
//可以作用在方法,類和接口上
@Target({ElementType.METHOD, ElementType.TYPE})
//編譯器會將SkipToken的信息保存在虛擬機中
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedToken {
// required 屬性默認值為true
boolean required() default true;
}
跳過驗證:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SkipToken {
boolean required() default true;
}
4、新建一個AuthInterceptor類,用于攔截請求:
public class AuthInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,Object object) {
// 從 http 請求頭中取出 token
String token = httpServletRequest.getHeader("token");
// 只攔截方法,不是方法直接返回true
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
// 有SkipToken注解的直接跳過認證
if (method.isAnnotationPresent(SkipToken.class)) {
SkipToken skipToken = method.getAnnotation(SkipToken.class);
if (skipToken.required()) {
return true;
}
}
// 有NeedToken注解的就進行認證
if (method.isAnnotationPresent(NeedToken.class)) {
NeedToken needToken = method.getAnnotation(NeedToken.class);
if (needToken.required()) {
if (token == null) {
throw new RuntimeException("無token,請重新登錄");
}
// 獲取 token 中的 userId
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new RuntimeException("401");
}
// 根據(jù)userId查詢數(shù)據(jù)庫
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException("用戶不存在,請重新登錄");
}
// 用查出來的user的密碼去校驗token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
// 沒發(fā)生異常就表示校驗通過
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException("401");
}
return true;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse, Object o,ModelAndView modelAndView) {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Object o, Exception e) {
}
}
這個攔截器的處理邏輯就是:
只攔截方法,如果不是方法,就放行;
如果攔截的方法有@SkipToken注解,放行;
如果攔截的方法有@NeedToken注解,則需要驗證token;
取出請求頭中的token,拿出token中的userId,根據(jù)此userId去數(shù)據(jù)庫查詢對應的記錄;
再將查出來的user的password去驗證token,驗證成功則放行;
如果沒有@NeedToken也沒有@SkipToken注解的,也放行。
5、新建一個InterceptorConfig類,將我們上面寫的攔截器配置到spring容器中去:
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor())
.addPathPatterns("/**");
}
@Bean
public AuthInterceptor authInterceptor() {
return new AuthInterceptor();
}
}
6、在controller中的用法:
@RestController
@RequestMapping("api")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private TokenService tokenService;
@SkipToken
@PostMapping("/user")
public JsonResult login(User user){
User dbUser = userService.findForLogin(user);
if (dbUser == null){
return new JsonResult(200, "用戶名或密碼錯誤", null);
} else {
String token = tokenService.getToken(dbUser);
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("token", token);
resultMap.put("user", dbUser);
return new JsonResult(200, "登錄成功", resultMap);
}
}
@NeedToken
@GetMapping("/getMessage")
public String getMessage(){
return "你已通過驗證";
}
@SkipToken
@GetMapping("/noToken")
public String noToken() {
return "無需token訪問";
}
}
以上就是JWT的用法,其實在實際生產(chǎn)中一般會配合shiro使用。