JWT介紹 JWT是JSON Web Token的縮寫,即JSON Web令牌,是一種自包含令牌。 是為了在網路應用環境間傳遞聲明而執行的一種基於JSON的開放標準。 JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源伺服器獲取資源。比如用在用戶登錄上。 JWT最 ...
JWT介紹
-
JWT是JSON Web Token的縮寫,即JSON Web令牌,是一種自包含令牌。 是為了在網路應用環境間傳遞聲明而執行的一種基於JSON的開放標準。
-
JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源伺服器獲取資源。比如用在用戶登錄上。
-
JWT最重要的作用就是對 token信息的防偽作用。
-
一個JWT由三個部分組成:JWT頭、有效載荷、簽名哈希
-
最後由這三者組合進行base64url編碼得到JWT
-
典型的,一個JWT看起來如下圖:該對象為一個很長的字元串,字元之間通過"."分隔符分為三個子串。
https://jwt.io/
JWT頭
JWT頭部分是一個描述JWT元數據的JSON對象,通常如下所示。
{
"alg": "HS256",
"typ": "JWT"
}
在上面的代碼中,alg屬性表示簽名使用的演算法,預設為HMAC SHA256(寫為HS256);
typ屬性表示令牌的類型,JWT令牌統一寫為JWT。
最後,使用Base64 URL演算法將上述JSON對象轉換為字元串保存。
有效載荷
有效載荷部分,是JWT的主體內容部分,也是一個JSON對象,包含需要傳遞的數據。 JWT指定七個預設欄位供選擇。
iss: jwt簽發者
sub: 主題
aud: 接收jwt的一方
exp: jwt的過期時間,這個過期時間必須要大於簽發時間
nbf: 定義在什麼時間之前,該jwt都是不可用的.
iat: jwt的簽發時間
jti: jwt的唯一身份標識,主要用來作為一次性token,從而迴避重放攻擊。
{
"name": "Helen",
"role": "editor",
"avatar": "helen.jpg"
}
請註意,預設情況下JWT是未加密的,任何人都可以解讀其內容,因此不要構建隱私信息欄位,存放保密信息,以防止信息泄露。
JSON對象也使用Base64 URL演算法轉換為字元串保存。
簽名哈希
簽名哈希部分是對上面兩部分數據簽名,通過指定的演算法生成哈希,以確保數據不會被篡改。
首先,需要指定一個密碼(secret)。該密碼僅僅為保存在伺服器中,並且不能向用戶公開。然後,使用標頭中指定的簽名演算法(預設情況下為HMAC SHA256)根據以下公式生成簽名。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(claims), secret) ==> 簽名hash
在計算出簽名哈希後,JWT頭,有效載荷和簽名哈希的三個部分組合成一個字元串,每個部分用"."分隔,就構成整個JWT對象。
Base64URL演算法
如前所述,JWT頭和有效載荷序列化的演算法都用到了Base64URL。該演算法和常見Base64演算法類似,稍有差別。
作為令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三個字元是"+","/"和"=",由於在URL中有特殊含義,因此Base64URL中對他們做了替換:"="去掉,"+"用"-"替換,"/"用"_"替換,這就是Base64URL演算法。
Spring Boot中集成JWT
- 導入jwt的pom依賴
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
- 添加JWT幫助類
import io.jsonwebtoken.*;
import org.springframework.util.StringUtils;
import java.util.Date;
public class JwtHelper {
private static long tokenExpiration = 365 * 24 * 60 * 60 * 1000;
private static String tokenSignKey = "123456";
public static String createToken(Long userId, String username) {
String token = Jwts.builder()
.setSubject("AUTH-USER")
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.claim("userId", userId)
.claim("username", username)
.signWith(SignatureAlgorithm.HS512, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact();
return token;
}
public static Long getUserId(String token) {
try {
if (StringUtils.isEmpty(token)) return null;
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
Integer userId = (Integer) claims.get("userId");
return userId.longValue();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String getUsername(String token) {
try {
if (StringUtils.isEmpty(token)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return (String) claims.get("username");
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
String token = JwtHelper.createToken(1L, "admin");
System.out.println(token);
System.out.println(JwtHelper.getUserId(token));
System.out.println(JwtHelper.getUsername(token));
}
}
- Controller的使用
@Autowired
private SysUserService sysUserService;
@ApiOperation(value = "登錄")
@PostMapping("login")
public Result login(@RequestBody LoginVo loginVo) {
SysUser sysUser = sysUserService.getByUsername(loginVo.getUsername());
if(null == sysUser) {
throw new GuiguException(201,"用戶不存在");
}
if(!MD5.encrypt(loginVo.getPassword()).equals(loginVo.getPassword())) {
throw new GuiguException(201,"密碼錯誤");
}
if(sysUser.getStatus().intValue() == 0) {
throw new GuiguException(201,"用戶被禁用");
}
Map<String, Object> map = new HashMap<>();
map.put("token", JwtHelper.createToken(sysUser.getId(), sysUser.getUsername()));
return Result.ok(map);
}