1、什么是JWT
JSON Web Tokens,是一種開發的行業標準規范RFC 7519,廣泛的用在系統的認證和資料交換方面,
2、JWT結構
JWT 由三個部分依次組成
- Header(頭部)
- Payload(載荷)
- Signature(簽名)
2.1、Header
Header 部分是一個 JSON 物件,描述 JWT 的元資料,包含演算法和token型別,
需要對json進行base64url加密
{
"alg": "HS256",
"typ": "JWT"
}
2.2、Payload
用來存放實際需要傳遞的資料,
需要json進行base64url加密
里面的前五個欄位都是由JWT的標準所定義的,并且也支持自定義欄位
iss: 該JWT的簽發者
sub: 該JWT所面向的用戶
aud: 接收該JWT的一方
exp(expires): 什么時候過期,這里是一個Unix時間戳
iat(issued at): 在什么時候簽發的
#自定義欄位
name:hello
2.3、Signature
把前兩段的base密文通過·拼接起來,使用HS256加密
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
例如
eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2MTQ2Puk_fv6cyfk0B1j7vbIqw_Q
3、JWT認證流程
- 客戶端發送(用戶名、密碼)身份資訊至服務器端;
- 服務器端對客戶端發送身份資訊進行校驗,校驗通過后,生成token字串;
- 服務器端將生成的token字串發送給客戶端(服務器端不保存token);
- 客戶端接收到token后,將token保存到cookie或者localstorage;
- 客戶端每次向服務器端發送請求,攜帶token(通過header、cookie等方式);
- 服務器端收到請求,首先驗證token是否合法過期(可通過攔截器方式),回傳對應資訊;
4、JWT認證和傳統session認證區別
4.1、基于session的認證
http協議是一種無狀態的協議,本身是無法對訪問的客戶端進行識別,
采用session機制,客戶端在服務端登陸成功之后,服務端會生成一個sessionID,回傳給客戶端,客戶端將sessionID保存到cookie中,再次發起請求的時候,攜帶cookie中的sessionID到服務端,服務端會快取該session(會話),當客戶端請求到來的時候,服務端就知道是哪個用戶的請求,并將處理的結果回傳給客戶端,
存在問題:
- session保存在服務端,當客戶訪問量增加時,服務端就需要存盤大量的session會話,對記憶體壓力增大
- 分布式集群環境存在session共享問題
- CSRF攻擊: 基于cookie來進行用戶識別的, cookie如果被截獲,用戶就會很容易受到跨站請求偽造的攻擊,
4.2、基于token的鑒權機制
基于token的鑒權機制類似于http協議也是無狀態的,服務端不需要保存認證資訊或者會話資訊
5、JWT特點
跨域訪問:基于Token的訪問策略可以克服cookies的跨域問題
無狀態token:token無狀態,session有狀態的
使用前后端分離、移動端:Cookie手機端不支持
跨平臺:語言無關性,標準規范
避免了CSRF 攻擊
JWT撤銷問題:無法在服務器端撤銷,只能輔助邏輯判斷處理
6、JWT續約
jwt受自身機制原因,payload具有過期時間,參與簽名程序,過期時間改動,簽名發生改變,因此jwt本身是不支持續簽的,但是結合一些方案來輔助實作,
- 每次請求重繪token,每次請求都回傳一個新的 jwt 給客戶端,但產生性能上問題;
- 要過期token預重繪,服務器端判斷token是否將要過期(如10min內過期),則更新token,回傳最新token客戶端,但觸發重繪時間無法判斷,可能后10min內沒有請求發送,則token無法更新;
- 第三方組件redis 等保存過期時間,結合redis保存token,改變了token的無狀態性;
- refreshToken機制,1個 acessToken 設定過期時間 ,如半個小時,另一個是 refreshToken 過期時間如為1天,客戶端登錄后,將 accessToken和refreshToken 保存在本地,每次訪問將 accessToken 傳給服務端,服務端校驗 accessToken 的有效性,如果過期,就將 refreshToken 傳給服務端,如果有效,服務端就生成新的 accessToken 給客戶端,否則,客戶端就重新登錄即可,但存在問題是:1、客戶端操作更復雜;2、用戶注銷的時候需要同時保證兩個 token 都無效;3、重新請求獲取 token 的程序中會有短暫 token 不可用的情況(可以通過在客戶端設定定時器,當accessToken 快過期的時候,提前去通過 refreshToken 獲取新的accessToken)
7、專案實戰
package com.spring.util;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 測驗
*
* @author yilei
* @className DemoController
* @date 2021/3/1 20:33
**/
@Controller
public class DemoController {
/**
* 獲取access_token
*
* @param clientId 客戶端ID
* @param clientSecret 客戶端secret
* @return java.lang.String
* @author yilei
* @date 2021-03-01 21:32
*/
@RequestMapping(value = "tokens")
@ResponseBody
public String tokens(String clientId, String clientSecret) {
Map<String, Object> map = new HashMap<>(16);
// 驗證客戶端ID,密鑰是否匹配
if (StringUtils.equals("ddddd", clientId) && StringUtils.equals("sssss", clientSecret)) {
// 生成access_token
String token = JwtTokenUtil.generateToken(clientId);
map.put("code", 1);
map.put("access_token", token);
Date expiration = JwtTokenUtil.getClaimsFromToken(token).getExpiration();
map.put("expires_in", DateUtil.between(expiration, new Date(), DateUnit.MS));
} else {
map.put("code", -1);
map.put("msg", "授權資訊不正確!");
}
return JSON.toJSONString(map);
}
/**
* 校驗token
*
* @param token
* @param clientId 客戶端ID
* @return java.lang.String
* @author yilei
* @date 2021-03-01 21:33
*/
@RequestMapping(value = "tokens/check")
@ResponseBody
public String tokensCheck(@RequestHeader String token, @RequestParam String clientId) {
Map<String, Object> map = new HashMap<>(16);
boolean flag = JwtTokenUtil.validateToken(token, clientId);
if (flag) {
map.put("code", 1);
map.put("client_id", clientId);
Date expiration = JwtTokenUtil.getClaimsFromToken(token).getExpiration();
map.put("expires_in", DateUtil.between(expiration, new Date(), DateUnit.MS));
} else {
map.put("code", -1);
map.put("msg", "非法token或token已過期!");
}
return JSON.toJSONString(map);
}
}
package com.spring.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JwtToken生成的工具類
*
* @author yilei
* @className JwtTokenUtil
* @date 2021/3/1 20:33
**/
@Component
@PropertySource("classpath:conf/jwt.properties")
public class JwtTokenUtil implements InitializingBean {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
private static final String CLAIM_KEY_CLIENT_ID = "client";
private static final String CLAIM_KEY_CREATED = "created";
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expire}")
private Long expire;
private static String JWT_SECRET;
private static Long JWT_EXPIRE;
@Override
public void afterPropertiesSet() {
JWT_SECRET = secret;
JWT_EXPIRE = expire;
}
/**
* 生成token
*
* @param clientId
* @return java.lang.String
* @author yilei
* @date 2021-03-01 20:04
*/
public static String generateToken(String clientId) {
Map<String, Object> claims = new HashMap<>(3);
claims.put(CLAIM_KEY_CLIENT_ID, clientId);
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/**
* 根據claims生成token
*
* @param claims
* @return java.lang.String
* @author yilei
* @date 2021-03-01 20:04
*/
public static String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRE * 1000))
.signWith(SignatureAlgorithm.HS512, JWT_SECRET)
.compact();
}
/**
* 根據token獲取claims
*
* @param token
* @return io.jsonwebtoken.Claims
* @author yilei
* @date 2021-03-01 20:04
*/
public static Claims getClaimsFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(JWT_SECRET)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.info("JWT格式驗證失敗:{}", token);
}
return claims;
}
/**
* 驗證token是否合法
* 1、client_id是否匹配
* 2、expire是否過期
*
* @param token
* @param clientId
* @return boolean
* @author yilei
* @date 2021-03-01 20:04
*/
public static boolean validateToken(String token, String clientId) {
Claims claims = getClaimsFromToken(token);
if (null == claims) {
return false;
}
Object cId = claims.get(CLAIM_KEY_CLIENT_ID);
if (null == cId) {
return false;
}
return StringUtils.equals(clientId, cId.toString()) && !isTokenExpired(token);
}
/**
* 判斷token是否已經失效
*
* @param token
* @return boolean
* @author yilei
* @date 2021-03-01 20:04
*/
public static boolean isTokenExpired(String token) {
Claims claims = getClaimsFromToken(token);
Date expiredDate = claims.getExpiration();
return expiredDate.before(new Date());
}
public static void main(String[] args) {
String token = JwtTokenUtil.generateToken("123456");
System.out.println(token);
System.out.println("============");
String aa = "eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2MTQ2MDg3NjgsImNsaWVudCI6IjEyMzQ1NiIsImNyZWF0ZWQiOjE2MTQ2MDg3MDg2MDV9.DyVpgKZ_2_8fP1gQdNYzdH5pI5JM7diw1ivXTHGtl1ayH6KQn3K3pRxGn1xrQRVzJyz6flLooY2_611XE8RNZA";
Claims claimsFromToken = JwtTokenUtil.getClaimsFromToken(aa);
System.out.println(claimsFromToken);
}
}
獲取access_token
var settings = {
"url": "http://localhost:8080/spring_jwt/tokens.do?clientId=ddddd&clientSecret=sssss",
"method": "GET",
"timeout": 0,
};
$.ajax(settings).done(function (response) {
console.log(response);
});
{“access_token”:“eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2MTQ2NDk3OTUsImNsaWVudCI6ImRkZGRkIiwiY3JlYXRlZCI6MTYxNDY0OTczNTI5OH0.EsKmthI44vBXdrl3ZkOb2tQGdds2T90LJ1CKXsjj4dge2JCW4Rw1c5MbMwwzwaGoDbaGE09Fwav6KTnFGqI-qg”,“code”:1,“expires_in”:59193}
校驗token
var settings = {
"url": "http://localhost:8080/spring_jwt/tokens/check.do?clientId=ddddd",
"method": "GET",
"timeout": 0,
"headers": {
"token": "eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2MTQ2NDk3OTUsImNsaWVudCI6ImRkZGRkIiwiY3JlYXRlZCI6MTYxNDY0OTczNTI5OH0.EsKmthI44vBXdrl3ZkOb2tQGdds2T90LJ1CKXsjj4dge2JCW4Rw1c5MbMwwzwaGoDbaGE09Fwav6KTnFGqI-qg"
},
};
$.ajax(settings).done(function (response) {
console.log(response);
});
{“code”:1,“expires_in”:5206,“client_id”:“ddddd”}
原始碼spring-jwt
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/265856.html
標籤:其他
