定義:JSON Web Token(縮寫 JWT)是目前最流行的跨域認證解決方案算法
JWT官網數據庫
因爲HTTP協議是無狀態的,這意味着若是咱們想斷定一個接口是否被認證後訪問,就須要藉助cookie或者session會話機制進行斷定,可是因爲如今的系統架構大部分都不止一臺服務器,此時又要藉助數據庫或者全局緩存 作存儲,這種方案顯然受限太多。api
那麼咱們可不可讓認證令牌的發佈者本身去識別這個令牌是否是我曾經發布的令牌呢(JWT核心思想),這是JWT最大的優勢也是最大的缺點,優勢是簡單快捷、不須要依賴任何第三方操做就能實現身份認證,缺點就是對於任何擁有用戶發佈令牌的請求都會認證經過。跨域
正常的JWT數據結構應該以下緩存
它是一個很長的字符串,中間用點(.
)分隔成三個部分服務器
JWT的三個部分依次: Header - 頭部 、Payload - 負載 、Signature(簽名)cookie
即:Header.Payload.Signaturesession
Header 部分是一個 JSON 對象,描述 JWT 的元數據,一般是下面的樣子。數據結構
{
"alg": "HS256",
"typ": "JWT"
}
alg
屬性表示簽名的算法(algorithm),默認是 HMAC SHA256(寫成 HS256);typ
屬性表示這個令牌(token)的類型(type),JWT 令牌統一寫爲JWT
Payload 部分也是一個 JSON 對象,用來存放實際須要傳遞的數據。JWT 規定了7個官方字段,供選用。
iss (issuer):簽發人
exp (expiration time):過時時間
sub (subject):主題
aud (audience):受衆
nbf (Not Before):生效時間
iat (Issued At):簽發時間
jti (JWT ID):編號
除了官方字段,你還能夠在這個部分定義私有字段
{
"sub": "1234567890",
"name": "John Doe",
"age": "19"
}
注意:JWT默認是明文展現,任何人均可以讀取到,因此此處不要放私密信息
這個 JSON 對象也要使用 Base64URL 算法轉成字符串。
Signature 部分是對前兩部分的簽名,防止數據篡改。
首先,須要指定一個密鑰(secret)。這個密鑰只有服務器才知道,不能泄露給用戶。而後,使用 Header 裏面指定的簽名算法(默認是 HMAC SHA256),按照下面的公式產生簽名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出簽名之後,把 Header、Payload、Signature 三個部分拼成一個字符串,每一個部分之間用"點"(.
)分隔,就能夠返回給用戶。
前面提到,Header 和 Payload 串型化的算法是 Base64URL。這個算法跟 Base64 算法基本相似,但有一些小的不一樣。
JWT 做爲一個令牌(token),有些場合可能會放到 URL(好比 api.example.com/?token=xxx)。Base64 有三個字符+
、/
和=
,在 URL 裏面有特殊含義,因此要被替換掉:=
被省略、+
替換成-
,/
替換成_
。這就是 Base64URL 算法
Maven依賴
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.5.0</version> </dependency>
JWT簽名發佈和驗證代碼
public class TokenUtil { //Token的過時時間 private static final long EXPIRE_TIME = 30 * 60 * 1000; //Token的私鑰 private static final String TOKEN_SECRET = "jytoken_secret"; /** * 生成簽名,30分鐘過時 * @param **userInfo** 用戶信息 用戶姓名 * @param **other** 用戶其餘信息 用戶id * @return */ public static String sign(String userInfo, String other) { try { // 設置過時時間 Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); //私鑰和加密算法 Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); // 設置頭部信息 Map<String, Object> header = new HashMap<>(2); header.put("Type", "Jwt"); header.put("alg", "HS256"); // 返回token字符串 return JWT.create() .withHeader(header) .withClaim("userInfo", userInfo) .withClaim("other", other) .withExpiresAt(date) .sign(algorithm); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 生成簽名,30分鐘過時 * @param **userInfo** 用戶信息 用戶姓名 * @param **other** 用戶其餘信息 用戶id * @return */ public static String sign(String userInfo, String other,long expire) { try { // 設置過時時間 Date date = new Date(System.currentTimeMillis() + expire); //私鑰和加密算法 Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); // 設置頭部信息 Map<String, Object> header = new HashMap<>(2); header.put("Type", "Jwt"); header.put("alg", "HS256"); // 返回token字符串 return JWT.create() .withHeader(header) .withClaim("userInfo", userInfo) .withClaim("other", other) .withExpiresAt(date) .sign(algorithm); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 檢驗token是否正確 * @param **token** * @return */ public static boolean verify(String token){ try { Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); JWTVerifier verifier = JWT.require(algorithm).build(); verifier.verify(token);//未驗證經過會拋出異常 return true; } catch (Exception e){ return false; } } /** * 從token中獲取info信息 * @param **token** * @return */ public static String getUserName(String token,String info){ try { DecodedJWT jwt = JWT.decode(token); return jwt.getClaim(info).asString(); } catch (JWTDecodeException e){ e.printStackTrace(); } return null; } }
攔截器配置無需認證的請求
@Configuration public class InterceptorConfig extends WebMvcConfigurationSupport { @Autowired private TokenHandler tokenHandler; @Override public void addInterceptors(InterceptorRegistry registry) { List<String> excludePath = new ArrayList<>(); String checkLogin = "/pushlogin/checkIsCanLogin"; String login = "/pushlogin/login"; String getVerifyCode = "/common/send"; String verfifyMethod = "/common/validationCode"; excludePath.add(checkLogin); excludePath.add(login); excludePath.add(getVerifyCode); excludePath.add(verfifyMethod); registry.addInterceptor(tokenHandler).excludePathPatterns(excludePath); } }
Token統一攔截器代碼
@Component @Slf4j public class TokenHandler implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authentication"); if (token != null){ boolean result = TokenUtil.verify(token); if(result){ log.info("經過攔截器"); return true; } } log.info("認證失敗"); return false; } }
用戶登陸時驗證用戶信息後,返回Token信息
@Override public UserDTO selectIsExistUserInfo(String phone) { //TODO 僞代碼 驗證用戶信息 UserDTO info = 查詢用戶信息 if (info != null) { String token = TokenUtil.sign(info.getUsername(), info.getUserId(), 6 * 60 * 60 * 1000); info.setToken(token); } return info; }