JWT請求的流程:
1.使用者輸入賬号密碼登入,前端傳輸給後端認證
2.認證成功後,後端用jwt建立一個token令牌
3.後端傳給前端token令牌,前端把這個令牌儲存下來
4.前端每次通路後端資源的時候都要帶上之前儲存的token令牌給後端認證
5.驗證成功後傳回資源給前端
JWT組成
JWT有3部分組成:
Header(頭部):兩部分組成,令牌的中繼資料,簽名和/或加密算法的類型
Payload(載體):JWT中的一些資料比如簽發者、過期時間,簽發時間之類
Signature(簽名):由base64對header部分編碼後加上.和base64對Payload編碼後組成的字元串,再用一個密鑰sercet對組合成的字元串用由頭部聲明的加密方式加密組成
一個JWT如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIiwiZXhwIjoxNTc4MzA0NzI2LCJpYXQiOjE1NzgzMDExMjZ9.Vvh49ThZlFNsoTVj09QA4Wb51s7-Jk1-LyQHPuVpZCA
Springboot中整合JWT
先在 pom.xml 中引入jar包
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
然後建立兩個注解 PassToken(有此注解的接口不需要驗證Token)和UserLoginToken(有此注解的接口需要驗證Token)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
boolean required() default true;
}
建立一個token工具類,我在後面的token建立中Payload中隻放了使用者的ID,TOKEN建立時間,有效時間。是以在這個工具類中寫了個擷取使用者ID的方法。
TokenUtil.java
public class TokenUtil {
//從token中拿到使用者的ID
public static String getTokenUserId() {
String token = getRequest().getHeader("token");// 從 http 請求頭中取出 token
String userId = JWT.decode(token).getAudience().get(0);
return userId;
}
/**
* 擷取request
*
* @return
*/
public static HttpServletRequest getRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
return requestAttributes == null ? null : requestAttributes.getRequest();
}
}
建立一個TokenService 來建立Token,也就是頒發Token。
TokenService.java
@Service
public class TokenService {
//頒發Token
public String getToken(User user){
Date start = new Date();
long current=System.currentTimeMillis()+60*60*1000; //1小時有效期
Date end=new Date(current);
String token="";
token= JWT.create().withAudience(user.getUserid()).withIssuedAt(start).withExpiresAt(end)
//這裡用了HMAC加密,密鑰是使用者的密碼
.sign(Algorithm.HMAC256(user.getPassword()));
return token;
}
}
然後建立一個攔截器,這個攔截器攔截所有請求,然後判斷請求的接口有沒有上面自定義的注解@UserLoginToken 注解,有該注解表示需要Token,若是遇到@PassToken 注解 則表示不需要Token,直接放行。
AuthenticationInterceptor.java
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
String token = httpServletRequest.getHeader("token");
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;
}
//判斷是否有注解UserLoginToken
if (method.isAnnotationPresent(UserLoginToken.class)) {
UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
if (userLoginToken.required()) {
if (token == null)
throw new RuntimeException("無token,請重新登入");
String userid;
try {
userid = JWT.decode(token).getAudience().get(0);
boolean time= JWT.decode(token).getExpiresAt().before(new Date());
} catch (JWTDecodeException j) {
throw new RuntimeException("401");
}
//此處通過使用者ID獲得使用者
User user = userService.selectByPrimaryKey(userid);
if (user == null)
throw new RuntimeException("使用者不存在,請重新登入");
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
jwtVerifier.verify(token);
} catch (Exception j) {
throw new RuntimeException("401");
}
return true;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
最後需要把我們剛才建立的攔截器進行注冊。
InterceptorConfig.java
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**"); // 攔截所有請求,通過判斷是否有 @UserLoginToken注解 決定是否需要登入
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();// 自己寫的攔截器
}
}
至此,Springboot整合jwt實作token的過程完成。
接下來要進行測試,建立一個測試用的User Bean
User.java
public class User {
private String userid;
private String account;
private String password;
//省略get和set
}
controller層建立測試api
TestController.java
@Controller
public class TestController {
@Autowired
TokenService tokenService;
@RequestMapping(value = "/login/{account}/{password}" ,method = RequestMethod.GET)
@ResponseBody
public String login(HttpServletResponse response, @PathVariable String account, @PathVariable String password){
User userForBase = new User();
userForBase.setUserid("1");
userForBase.setPassword("123");
userForBase.setAccount("qwe");
if (!userForBase.getPassword().equals(password)) {
return "登入失敗,密碼錯誤";
} else {
String token = tokenService.getToken(userForBase);
//建立一個名為token的cookie,供前端調用
Cookie cookie = new Cookie("token", token);
cookie.setPath("/");
response.addCookie(cookie);
return token;
}
}
@UserLoginToken
@RequestMapping("/verify")
@ResponseBody
public String verify() {
// 取出token中帶的使用者id 進行操作
System.out.println(TokenUtil.getTokenUserId());
return "你已認證驗證";
}
}
使用postmen來測試
下面傳回的内容就是Token
這是傳回的cookie
然後請求 localhost:8080/verify 接口 帶上前面傳回的token參數
如上圖 表示帶上的token驗證通過,且在控制台輸出了使用者的ID