JWT(⼆):使⽤Java实现JWT
介绍
原理在已经说过了,实现起来并不难,你可以⾃⼰写⼀个 jwt ⼯具类(如果你有兴趣的话)
当然了,重复造轮⼦不是程序员的风格,我们主张拿来主义!
JWT 官⽹提供了多种语⾔的 JWT 库,详情可以参考页⾯下半部分
建议使⽤ jjwt库,它的github地址
jjwt 版本 0.10.7,它和 0.9.x 有很⼤的区别,⼀定要注意!!!
本⽂分5部分
:以简单例⼦演⽰⽣成、验证、解析 jwt 过程
:介绍 jjwt 的常⽤⽅法
:封装⼀个常⽤的 jwt ⼯具类
如果只是拿来主义,看到这⾥就可以了
:介绍 jjwt 的各种签名算法
:对 jwt 进⾏安全加密
简单例⼦
引⼊ MAVN 依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.7</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.7</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.7</version>
<scope>runtime</scope>
</dependency>
⼀个例⼦
// ⽣成密钥
String key = "0123456789_0123456789_0123456789";
SecretKey secretKey = new Bytes(), JcaName());
// 1. ⽣成 token
String token = Jwts.builder() // 创建 JWT 对象
.setSubject("JSON Web Token") // 设置主题(声明信息)
.signWith(secretKey) // 设置安全密钥(⽣成签名所需的密钥和算法)
pact(); // ⽣成token(1.编码 Header 和 Payload 2.⽣成签名 3.拼接字符串)
System.out.println(token);
//token = token + "s";
// 2. 验证token,如果验证token失败则会抛出异常
try {
Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token);
// OK, we can trust this token
System.out.println("验证成功");
} catch (JwtException e) {
//don't trust the token!
System.out.println("验证失败");
}
// 3. 解析token
Claims body = Jwts.parser() // 创建解析对象
.setSigningKey(secretKey) // 设置安全密钥(⽣成签名所需的密钥和算法)
java源代码加密.parseClaimsJws(token) // 解析token
.getBody(); // 获取 payload 部分内容
System.out.println(body);
输出结果:
验证成功
{sub=JSON Web Token}
常⽤⽅法
以下内容建议参考源码获知更多详情
Jwts.builder() 创建了 DefaultJwtBuilder 对象,该对象的常⽤⽅法如下:
在compact()⽅法中会⾃动根据签名算法设置头部信息,当然也可以⼿动设置
setHeader(Header header): JwtBuilder
setHeader(Map<String, Object> header): JwtBuilder
setHeaderParams(Map<String, Object> params): JwtBuilder
setHeaderParam(String name, Object value): JwtBuilder
参数 Header 对象可通过 Jwts.header(); 创建,它简单得就像⼀个 map (把它当做 map 使⽤即可)
Payload
⾄少设置⼀个 claims,否则在⽣成签名时会抛出异常
setClaims(Claims claims): JwtBuilder
setClaims(Map<String, Object> claims): JwtBuilder
addClaims(Map<String, Object> claims): JwtBuilder
setIssuer(String iss): JwtBuilder
setSubject(String sub): JwtBuilder
setAudience(String aud): JwtBuilder
setExpiration(Date exp): JwtBuilder
setNotBefore(Date nbf): JwtBuilder
setIssuedAt(Date iat): JwtBuilder
setId(String jti): JwtBuilder
claim(String name, Object value: JwtBuilder
参数对象 Claims 同 Header 类似,通过 Jwts.claims() 创建,同样简单得就像⼀个 map
值得注意的⼀点是:不要在 setXxx 之后调⽤ setClaims(Claims claims) 或 setClaims(Map<String, Object> claims),因为这两个⽅法会覆盖所有已设置的 claim Signature
signWith(Key key)
signWith(Key key, SignatureAlgorithm alg)
signWith(SignatureAlgorithm alg, byte[] secretKeyBytes)
signWith(SignatureAlgorithm alg, String base64EncodedSecretKey)
signWith(SignatureAlgorithm alg, Key key)
以上⽅法最终就是设置两个对象:key 和 algorithm,分别代表密钥和算法
⽅法内部⽣成密钥使⽤的⽅法的和演⽰中的⼀样
SecretKey key = new SecretKeySpec(secretKeyBytes, JcaName());
注意:key 的长度必须符合签名算法的要求(避免⽣成弱密钥)
HS256:bit 长度要>=256,即字节长度>=32
HS384:bit 长度要>=384,即字节长度>=48
HS512:bit 长度要>=512,即字节长度>=64
在 secret key algorithms 名称中的数字代表了最⼩bit长度
更多签名算法的详情,请参考
封装 JWT ⼯具类
package com.liuchuanv.jwt;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.SignatureException;
pto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
/**
* JSON Web Token ⼯具类
*
* @author LiuChuanWei
* @date 2019-12-11
*/
public class JwtUtils {
/**
* key(按照签名算法的字节长度设置key)
*/
private final static String SECRET_KEY = "0123456789_0123456789_0123456789";
/**
* 过期时间(毫秒单位)
*/
private final static long TOKEN_EXPIRE_MILLIS = 1000 * 60 * 60;
/**
* 创建token
* @param claimMap
* @return
*/
public static String createToken(Map<String, Object> claimMap) {
long currentTimeMillis = System.currentTimeMillis();
return Jwts.builder()
.setId(UUID.randomUUID().toString())
.setIssuedAt(new Date(currentTimeMillis)) // 设置签发时间
.setExpiration(new Date(currentTimeMillis + TOKEN_EXPIRE_MILLIS)) // 设置过期时间
.addClaims(claimMap)
.
signWith(generateKey())
pact();
}
/**
* 验证token
* @param token
* @return 0 验证成功,1、2、3、4、5 验证失败
*/
public static int verifyToken(String token) {
try {
Jwts.parser().setSigningKey(generateKey()).parseClaimsJws(token);
} catch (ExpiredJwtException e) {
e.printStackTrace();
return 1;
} catch (UnsupportedJwtException e) {
e.printStackTrace();
return 2;
} catch (MalformedJwtException e) {
e.printStackTrace();
return 3;
} catch (SignatureException e) {
e.printStackTrace();
return 4;
} catch (IllegalArgumentException e) {
e.printStackTrace();
return 5;
}
}
/**
* 解析token
* @param token
* @return
*/
public static Map<String, Object> parseToken(String token) {
return Jwts.parser() // 得到DefaultJwtParser
.setSigningKey(generateKey()) // 设置签名密钥
.parseClaimsJws(token)
.getBody();
}
/**
* ⽣成安全密钥
* @return
*/
public static Key generateKey() {
return new SecretKeySpec(Bytes(), JcaName());
}
}
测试代码如下:
//Map<String, Object> map = new HashMap<String, Object>();
//map.put("userId", 1002);
//map.put("userName", "张晓明");
//map.put("age", 12);
//map.put("address", "⼭东省青岛市李沧区");
//String token = ateToken(map);
//System.out.println(token);
String token = "JqdGkiOiI0ZWM2NWNhNC0wZjVmLTRlOTktOTI5NS1mYWUyN2UwODIzYzQiLCJpYXQiOjE1NzY0OTI4NjYsImV4cCI6MTU3NjQ5NjQ2NiwiYWRkcmVzcyI6IuWxseS4nOecgemdku int result = JwtUtils.verifyToken(token);
System.out.println(result);
Map<String, Object> map = JwtUtils.parseToken(token);
System.out.println(map);
输出结果:
{jti=4ec65ca4-0f5f-4e99-9295-fae27e0823c4, iat=1576492866, exp=1576496466, address=⼭东省青岛市李沧区, userName=张晓明, userId=1002, age=12}
签名算法
12 种签名算法
JWT 规范定义了12种标准签名算法:3种 secret key 算法和9种⾮对称密钥算法
HS256: HMAC using SHA-256
HS384: HMAC using SHA-384
HS512: HMAC using SHA-512
ES256: ECDSA using P-256 and SHA-256
ES384: ECDSA using P-384 and SHA-384
ES512: ECDSA using P-521 and SHA-512
RS256: RSASSA-PKCS-v1_5 using SHA-256
RS384: RSASSA-PKCS-v1_5 using SHA-384
RS512: RSASSA-PKCS-v1_5 using SHA-512
PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256
PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384
PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512
根据算法名称可分为四类:HSxxx(secret key 算法)、ESxxx、RSxxx、PSxxx
HSxxx、ESxxx 中的 xxx 表⽰算法 key 最⼩ Bit 长度
RSxxx、PSxxx 中的 xxx 表⽰算法 key 最⼩ Byte 长度
规定key的最⼩长度是为了避免因 key 过短⽣成弱密钥
⽣成密钥
jjwt ⽣成 secret key 两种⽅法
String key = "1234567890_1234567890_1234567890";
// 1. 根据key⽣成密钥(会根据字节参数长度⾃动选择相应的 HMAC 算法)
SecretKey secretKey1 = Keys.Bytes());
// 2. 根据随机数⽣成密钥
SecretKey secretKey2 = Keys.secretKeyFor(SignatureAlgorithm.HS256);
⽅法 Keys.secretKeyFor(SignatureAlgorithm) 内部使⽤ ateKey() ⽣成密钥
jjwt 也提供了⾮对称密钥对的⽣成⽅法
// 1. 使⽤jjwt提供的⽅法⽣成
KeyPair keyPair = Keys.keyPairFor(SignatureAlgorithm.RS256); //or RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512
// 2. ⼿动⽣成
// RSA算法要求有⼀个可信任的随机数源
SecureRandom secureRandom = new SecureRandom();
// 为RSA算法创建⼀个KeyPairGenerator对象
KeyPairGenerator keyPairGenerator = null;
try {
keyPairGenerator = Instance("RSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
// 利⽤上⾯的随机数据源初始化这个KeyPairGenerator对象
keyPairGenerator.initialize(keySize, secureRandom);
// ⽣成密钥对
KeyPair keyPair2 = ateKeyPair();
不同密钥⽣成token
以上都是使⽤同⼀密钥签名⽣成所有的token,下⾯我们使⽤不同的密钥
这⼀个特性可以应⽤于不同⽤户/⾓⾊使⽤不同的密钥⽣成的 token,帮助你更好的构建权限系统
1. ⾸先在 Header(或 claims)中设置⼀个 keyId
2. 定义⼀个类,继承 SigningKeyResolverAdapter,并重写 resolveSigningKey() 或 resolveSigningKeyBytes() ⽅法
public class MySigningKeyResolver extends SigningKeyResolverAdapter {
@Override
public Key resolveSigningKey(JwsHeader header, Claims claims) {
// 除了从 header 中获取 keyId 外,也可以从 claims 中获取(前提是在 claims 中设置了 keyId 声明)
String keyId = KeyId();
// 根据 keyId 查相应的 key
Key key = lookupVerificationKey(keyId);
return key;
}
public Key lookupVerificationKey(String keyId) {
// TODO 根据 keyId 获取 key,⽐如从数据库中获取
// 下⾯语句仅做演⽰⽤,绝对不可⽤于实际开发中!!!
String key = "qwertyuiopasdfghjklzxcvbnm2019_" + keyId;
return Keys.Bytes());
}
}
3. 解析时,不再调⽤ setSigningKey(SecretKey) ,⽽是调⽤ setSigningKeyResolver(SigningKeyResolver)
// ⽣成密钥
// TODO 此处 keyId 仅做演⽰⽤,实际开发中可以使⽤ UserId、RoleId 等作为 keyId
String keyId = new Long(System.currentTimeMillis()).toString();
System.out.println("keyId=" + keyId);
String key = "qwertyuiopasdfghjklzxcvbnm2019_" + keyId;
SecretKey secretKey = new Bytes(), JcaName());
// 1. ⽣成 token
String token = Jwts.builder()
.setHeaderParam(JwsHeader.KEY_ID, keyId) // 设置 keyId(当然也可以在 claims 中设置)
.setSubject("JSON Web Token")
.signWith(secretKey)
pact();
System.out.println("token=" + token);
// 2. 验证token
// token 使⽤了不同的密钥⽣成签名,在解析时就不⽤调⽤ setSigningKey(SecretKey) 了
// ⽽是调⽤ setSigningKeyResolver(SigningKeyResolver)
try {
Jwts.parser()
.setSigningKeyResolver(new MySigningKeyResolver())
.parseClaimsJws(token);
// OK, we can trust this token
System.out.println("token验证成功");
} catch (JwtException e) {
/
/don't trust the token!
System.out.println("token验证失败");
}
安全加密
敬请期待 .....
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论