注:業餘時間自學,以此作為筆記.
JWT資料結構
例如 xxxx.yyy.zzz 由三部分組成,每部分用英文句号連接配接,JWT的三個部分: header 頭部 payload 負載 signature 簽名.也就是 Header.Payload.Signature,如圖
JWT結構圖
1、Header 頭部
是一個JSON 對象, 描述JWT的中繼資料,例如: {"alg": "HS256", "typ": "JWT"}
alg屬性表示簽名的算法(algorithm),預設是 HMAC SHA256
typ屬性表示這個令牌的類型(type),JWT 令牌統一寫為JWT,不寫則預設上面格式
2、payload 負載
是一個JSON 對象, 用來存放實際需要傳遞的資料,形如: {"sub": "1234567890", "name": "John Doe","admin": true}
一般是在這個部分定義私有字段: 例如{"userId":"1","userName":"jack"}
注意,JWT 預設是不加密的,任何人都可以讀到,是以不要把機密資訊放在這個部分,如使用者密碼等。
3、signature 簽名
signature 是對前兩部分的簽名,防止資料篡改
1、需要指定一個密鑰(secret) 2、這個密鑰隻有伺服器才知道,不能洩露給用戶端 3、使用 Header 裡面指定的簽名算法,按照下面的公式産生簽名
jwt認證流程圖
springBoot整合JWT
1、首先寫一個jwt工具類
public class JwtHelper { // 加密字元串,随便定義 private static final String SECRET = "@[email protected]"; // 簽發人 private static String ISSUER = "sys_user"; // 期限 15d public static final long EXPIRE_SECONDS = 15 * 24 * 60 * 60; /** * 生成JWT * * @return */ public static String getToken(UserInfo userInfo) { try { //使用HMAC256進行加密 Algorithm algorithm = Algorithm.HMAC256(SECRET); LocalDateTime localDateTimeNow = LocalDateTime.now(); LocalDateTime localDateTimeExpire = localDateTimeNow.plusSeconds(EXPIRE_SECONDS); //token過期時間 Date expire = Date.from(localDateTimeExpire.atZone(ZoneId.systemDefault()).toInstant()); //建立jwt String token = JWT.create() .withClaim("userId", userInfo.getUserId()) .withClaim("userName", userInfo.getUsername()) .withClaim("role", userInfo.getRole()) .withIssuer(ISSUER) // 發行人 .withExpiresAt(expire) .sign(algorithm);// 過期時間點 return token; } catch (IllegalArgumentException e) { throw new RuntimeException(e); } } /** * 解密jwt */ public static UserInfo parseToken(String token) { UserInfo user = null; try { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).withIssuer(ISSUER).build(); DecodedJWT jwt = verifier.verify(token); Map claimMap = jwt.getClaims(); Map userMap = new HashMap<>(); claimMap.forEach((k, v) -> userMap.put(k, v.asString())); user = BeanUtil.mapToBean(userMap, UserInfo.class, true); } catch (Exception e) { e.printStackTrace(); } return user; }}
2、登入時擷取token
public Rest loginApp(@RequestParam(value = "data") String data) throws IOException { HashMap paramMap = JSONUtil.toBean(data, HashMap.class); UserInfo userInfo = systemAppService.getLoginInfo(paramMap); if (userInfo != null) { String token = JwtHelper.getToken(userInfo); userInfo.setToken(token); Map config = new HashMap(); config.put("socketAddress", propertyConfig.getSocketAddress()); config.put("allocurl", propertyConfig.getAllocurl()); userInfo.setConfig(config); return RestUtil.ok(userInfo); } return RestUtil.fail("使用者名或密碼不正确"); }
3、攔截器,攔截頭部沒有攜帶token的接口
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // System.out.println("app login intercept ..."); log.info("app login intercept used ..."); // 從 http 請求頭中取出 token final String token = request.getHeader("token"); // 如果不是映射到方法直接通過 if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); // 檢查是否有passtoken注釋,有則跳過認證 if (method.isAnnotationPresent(PassToken.class)) { PassToken passToken = method.getAnnotation(PassToken.class); if (passToken.required()) { return true; } } // 執行認證 if (StringUtils.isEmpty(token)) { // 未登入,傳回401 response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } // 解析token UserInfo userInfo = JwtHelper.parseToken(token); if (userInfo == null) { // 抛出異常,證明未登入,傳回401 response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } // 放入線程域 UserLocal.set(userInfo); return true; }
4、配置攔截器
@Configurationpublic class CustomMVCConfiguration implements WebMvcConfigurer { //攔截器 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AppLoginInterceptor()) .addPathPatterns("/**") //攔截所有路徑 .excludePathPatterns("/system/loginApp") //放行登入接口 .excludePathPatterns("/doc.html", "/swagger-resources/**"); }}