JWT官方文档:
通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个 Json字符串中,然后进行编码后得到一个JWT token,并且这个 JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为 Json对象传输。
在其紧凑的形式中,JWT由以点 ( .) 分隔的三部分组成,它们是:
在输出时,会将 JWT的 各部分进行 Base64编码后用点 ( .) 进行连接形成最终传输的字符串。
JWTString=
Base64(Header).
Base64(Payload).
HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
官方首页有查看 JWT Token字符串的解析内容。
JWT标头是一个描述 JWT元数据的 JSON对象。
通常由两部分组成:令牌的类型,即 JWT,以及正在使用的签名算法,例如 HMAC SHA256 或 RSA。
比如:
{
"alg": "HS256",
"typ": "JWT"
}
有效载荷部分,是 JWT的主体内容部分,也是一个 JSON对象,包含需要传递的数据。
JWT指定七个默认字段供选择:
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
这些预定义的字段并不要求强制使用。除以上默认字段外,我们还可以自定义私有字段,一般会把包含用户信息的数据放到 payload中。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
最后,对有效负载进行 Base64Url编码以形成 JSON Web 令牌的第二部分。
请注意:
对于已签名的令牌,此信息虽然受到保护以防篡改,但任何人都可以读取。除非已加密,否则请勿将机密信息放入 JWT 的有效负载或标头元素中。
签名部分是对上面两个部分数据进行哈希,需要使用 base64编码后的 header和 payload数据,通过指定的算法生成哈希签名,以确保数据不会被篡改。
例如,如果您想使用 HMAC SHA256 算法,签名将通过以下方式创建:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
签名用于验证消息在此过程中没有被篡改,并且在使用私钥签名的令牌的情况下,它还可以验证 JWT 的发送者就是它所说的那个人。
JWT Token就是将 JWT的 3部分分别进行 Base64编码后用点 ( .) 进行连接形成最终传输的 Base64-URL 字符串。可以在 HTML 和 HTTP 环境中轻松传递,同时与基于 XML 的标准(如 SAML)相比更紧凑。
官网推荐了 6个 Java使用 JWT的开源库,其中比较推荐使用的是 java-jwt和 jjwt-root。
通常根据使用的签名算法可以分为对称签名和非对称签名来生成 JWT Token,主要区别在于使用的算法。推荐使用非对称加密算法签名。
首先引入依赖:
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>3.18.2version>
dependency>
这里使用 HMAC256对称算法。
public class JavaJwtLearn1 {
private static final String SECRET_KEY = "secret_salt";
public static void main(String[] args) {
// 指定token过期时间为10秒
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, 10);
Date expiresDate = calendar.getTime();
String jwtToken = genetatetJwtToken(expiresDate);
System.out.println("生成 jwtToken=" + jwtToken);
}
/**
* 生成token
* @param expiresDate
* @return
*/
private static String genetatetJwtToken(Date expiresDate ) {
String jwtToken = JWT.create()
// Header
.withHeader(new HashMap<>())
// Payload
.withClaim("userId", 21)
.withClaim("userName", "admin")
// 过期时间
.withExpiresAt(expiresDate)
// 签名用的secret
.sign(Algorithm.HMAC256(SECRET_KEY));
return jwtToken;
}
}
public class JavaJwtLearn1 {
private static final String SECRET_KEY = "secret_salt";
public static void main(String[] args) {
// 指定token过期时间为10秒
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, 10);
Date expiresDate = calendar.getTime();
String jwtToken = genetatetJwtToken(expiresDate);
System.out.println("生成 jwtToken=" + jwtToken);
resolveJwtToken(jwtToken);
}
/**
* 解析token
* @param jwtToken
*/
private static void resolveJwtToken(String jwtToken) {
// 创建解析对象,使用的算法和secret要与创建token时保持一致
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET_KEY)).build();
// 解析指定的token
DecodedJWT decodedJWT = jwtVerifier.verify(jwtToken);
// 获取解析后的token中的信息
String header = decodedJWT.getHeader();
System.out.println("header:" + header);
Map<String, Claim> payloadMap = decodedJWT.getClaims();
System.out.println("Payload:" + payloadMap);
Date expires = decodedJWT.getExpiresAt();
System.out.println("过期时间:" + expires);
String signature = decodedJWT.getSignature();
System.out.println("signature:" + signature);
}
}
注意:
/**
* 判断token是否存在与有效
* @param jwtToken token字符串
* @return 如果token有效返回true,否则返回false
*/
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) {
return false;
}
try {
JWT.require(Algorithm.HMAC256(SECRET_KEY)).build().verify(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
这里使用 RSA非对称加密算法。使用同上。
注意:
私钥生成 JWT Token,公钥解析 JWT Token
。/**
* 使用 java-jwt开源库 非对称加密算法签名
*
*/
public class JavaJwtLearn2 {
private static final String RSA_PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAIR4W9EIR4mNBrXu6cyw7ninv4koTUTHSlB9bAcvUqVcbRfK2ZWyojgEMNjUcTSYXmWS4RwWSDcWr1/ZbaksY8UNcK2cNk81YuL3CHRl4tOoA5YfKBwDn8q6xbX/bjDPK65hr/eGBUCvdW8ZPbKVLJujpoHEeM28yzMiCv0Igik/AgMBAAECgYA6jZjIBIjaW+Ojdz8QowRFgKBA1/ePdyd5/HZLlrdJMFloMtmKObNKX0/YB88iGFdhPlMSPycccoKCM3EtXdmbEPNYPJDcOqOH+NyqdVP7mDw2KJ7ulSMGINuW/4MGqTecUdL01BAX3KlgwuJu6BzRiPMoWY/LEqYoUmHeedxBwQJBAOgKQ5J/A1ZRFHsyFGYwBY8LJcOGb/yEkawEEOn+yRVy16t+o5R3YFdbHwuWlFVFAys9rXxd4dBLQZI+dZQNZysCQQCSJhFh8DOpVzWqPtadFmlQipZZ48hFcsoq8zS3UTukhxaubBcmvzB5dixQFNOj1veCVic9f8SF57P6dsOWJ7w9AkEA0Aq70PIOBOsHKPmarpApu7mr7yVu7IHTtd2jaJjmo1NnKLyPX4K0nz30lMg6UEVi9PcEv7fQyZdfwAY+FzL5JwJADHj1OM+ACS6pJMtSE3vrJvV82VUILW0bdcjlsdNb7LGerOoKm8LrRyJfq8HrQetBmjzyAlyaD/dzM6fZD0J63QJBALR93M3+EItlV6QxeVGHi7sVyxHYlDlwsIIQPFZB12292blXzygWPCAC1b1BF4KbENN8yPmTL+0ixkRoxRojD8Q=";
private static final String RSA_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCEeFvRCEeJjQa17unMsO54p7+JKE1Ex0pQfWwHL1KlXG0XytmVsqI4BDDY1HE0mF5lkuEcFkg3Fq9f2W2pLGPFDXCtnDZPNWLi9wh0ZeLTqAOWHygcA5/KusW1/24wzyuuYa/3hgVAr3VvGT2ylSybo6aBxHjNvMszIgr9CIIpPwIDAQAB";
public static void main(String[] args) throws Exception {
// 指定token过期时间为10秒
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, 10);
Date expiresDate = calendar.getTime();
Map<String,String> payload = new HashMap<>();
payload.put("userId", "21");
payload.put("userName", "admin");
payload.put("userName2", "admin2");
payload.put("userName3", "admin3");
String jwtToken = genetatetJwtToken(expiresDate, payload);
System.out.println("生成 jwtToken=" + jwtToken);
// jwtToken =
// "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6ImFkbWluIiwiZXhwIjoxNjQ0NDc0MzY5LCJ1c2VySWQiOjIxfQ.2Tgvta3obNfyqUSlFuZPNuGGBs-stB7NeD-GU2R6Sn8";
resolveJwtToken(jwtToken);
}
/**
* 生成token
*
* @param expiresDate
* @return
*/
private static String genetatetJwtToken(Date expiresDate, Map<String,String> payload) throws Exception {
JWTCreator.Builder builder = JWT.create();
// Header
Map<String,Object> a = new HashMap<>(payload);
builder.withHeader(a);
// 构建payload
payload.forEach((k,v) -> builder.withClaim(k,v));
// 过期时间
builder.withExpiresAt(expiresDate);
// 获取RSA私钥
RSAPrivateKey privateKey = (RSAPrivateKey) RsaUtils.getPrivateKey(RSA_PRIVATE_KEY);
// 签名
String jwtToken = builder.sign(Algorithm.RSA256(null, privateKey));
return jwtToken;
}
/**
* 解析token
*
* @param jwtToken
*/
private static void resolveJwtToken(String jwtToken) throws Exception {
// 获取RSA公钥
RSAPublicKey publicKey = (RSAPublicKey) RsaUtils.getPublicKey(RSA_PUBLIC_KEY);
// 创建解析对象,使用的算法和secret要与创建token时保持一致
JWTVerifier jwtVerifier = JWT.require(Algorithm.RSA256(publicKey, null)).build();
// 解析指定的token
DecodedJWT decodedJWT = jwtVerifier.verify(jwtToken);
// 获取解析后的token中的信息
String header = decodedJWT.getHeader();
System.out.println("header:" + header);
Map<String, Claim> payloadMap = decodedJWT.getClaims();
System.out.println("Payload:" + payloadMap);
Date expires = decodedJWT.getExpiresAt();
System.out.println("过期时间:" + expires);
String signature = decodedJWT.getSignature();
System.out.println("signature:" + signature);
}
}
这里使用 0.11.1版本,引入依赖如下:
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwt-apiartifactId>
<version>0.11.2version>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwt-implartifactId>
<version>0.11.2version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwt-jacksonartifactId>
<version>0.11.2version>
<scope>runtimescope>
dependency>
注意:
jjwt-root在 0.10版本以后发生了较大变化,pom依赖和部分使用方法都有所变化,并且,0.10版本后强制要求 secretKey满足规范中的长度要求,否则生成 jws时会抛出异常。异常如下:
标准规范中对各种加密算法的 secretKey的长度有如下要求:
HS256:要求至少 256 bits (32 bytes)
HS384:要求至少384 bits (48 bytes)
HS512:要求至少512 bits (64 bytes)
RS256 and PS256:至少2048 bits
RS384 and PS384:至少3072 bits
RS512 and PS512:至少4096 bits
ES256:至少256 bits (32 bytes)
ES384:至少384 bits (48 bytes)
ES512:至少512 bits (64 bytes)
这里使用 HS256对称算法。
public class JjwtRootLearn1 {
private static final String SECRET_KEY = "secret_salt_aaaaaaaaaaaaaaa";
//private static final String SECRET_KEY = "secret_salt_MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCEeFvRCEeJjQa17unMsO54p7+JKE1Ex0pQfWwHL1KlXG0XytmVsqI4BDDY1HE0mF5lkuEcFkg3Fq9f2W2pLGPFDXCtnDZPNWLi9wh0ZeLTqAOWHygcA5/KusW1/24wzyuuYa/3hgVAr3VvGT2ylSybo6aBxHjNvMszIgr9CIIpPwIDAQAB";
public static void main(String[] args) {
// 指定token过期时间为10秒
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, 30);
Date expiresDate = calendar.getTime();
String jwtToken = genetatetJwtToken(expiresDate);
System.out.println("生成 jwtToken=" + jwtToken);
resolveJwtToken(jwtToken);
System.out.println(checkToken(jwtToken));
jwtToken = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiIyMSIsInVzZXJOYW1lIjoiYWRtaW4iLCJqdGkiOiJZbVk1TkdOa1pUUXRZVGt4TnkwMFlXTmlMVGsyWTJVdE1EVXdPRFUzTlRVMFlqRTQiLCJleHAiOjE2NDQ0Nzk5ODZ9.5lcaBjsDUxmeNCZOt-LnjCPhAajQPK3b2HF_bAzo2Hk";
System.out.println(checkToken(jwtToken));
}
/**
* 生成token
* @param expiresDate
* @return
*/
private static String genetatetJwtToken(Date expiresDate ) {
String jwtToken = Jwts.builder()
// Header
.setHeader(new HashMap<>())
// Payload
.claim("userId", "21")
.claim("userName", "admin")
//.setId(new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes())))
.setId("id----")
// 过期时间
.setExpiration(expiresDate)
// 签名指定key
.signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8)), SignatureAlgorithm.HS256)
.compact();
return jwtToken;
}
/**
* 解析token
* @param jwtToken
*/
private static void resolveJwtToken(String jwtToken) {
// 创建解析对象,使用的算法和secret要与创建token时保持一致
JwtParser jwtParser = Jwts.parserBuilder().setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8))).build();
// 解析指定的token
Jws<Claims> claimsJws = jwtParser.parseClaimsJws(jwtToken);
// 获取解析后的token中的信息
JwsHeader header = claimsJws.getHeader();
System.out.println("header:" + header);
Claims jwsBody = claimsJws.getBody();
System.out.println("Payload:" + jwsBody);
System.out.println("过期时间:" + jwsBody.getExpiration());
String signature = claimsJws.getSignature();
System.out.println("signature:" + signature);
}
/**
* 判断token是否存在与有效
* @param jwtToken token字符串
* @return 如果token有效返回true,否则返回false
*/
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) {
return false;
}
try {
Jwts.parserBuilder().setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8))).build().parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
}
这里使用 RSA非对称加密算法。使用同上。
public class JjwtRootLearn2 {
private static final String RSA_PRIVATE_KEY = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCZZGWcPP8zrMNA6oNhFTGCO2bEeI7t9syazhbIu7ndOM068iI7D+a9KqdPZBVfLQadF/wGV424YDZSO3jh+6SIQrTFhyOaeDWTfMoEqaahqiX4SbcU/9bfkCHX4NaiW4E/8egngnVq+x/Qg6SraC5AOBWLx6kAsUhgKhSJpqNigVh/n4EPcq6IRa2yZWc+FUahmxB4jTJo3s7exRGAyseXmBi8zP4nT+48eL2jSdMoTm0IbCeymCU5u6EHCuOWlnTSv7akXszwIPtpg+IuYIgxpT9Q0CJZrEFWPdjct50/yqVyZ1FHR+rR3I5WskfdJVVNSI7m139BKrrD9GtrazDjAgMBAAECggEAScuEGtM5j3m5Ab0Q8Z7Jj7bWLQU29gK60mr9iRrPQz91dLtSfoma3zzq+wXSRlSaDu+f/skWVDJtT8hu0oFG2YsF/tWR6lmUpNzvL6kSkkoSNE36d27RyAJGVd5ERB2zo7jUkFVx+cLQvnbmvNPFFH4m13V5t+ySPjlgYgy6I8QHa9embSuTlYeFCZe69CP6dla04kpwJzelfs5nziQ7Biy3B1O6V2Y0VeanTdVPHEXZbCVEOvUeJWVXoMAkkXbpiRglt3ugzK/nct4hkViTxu5RDHF/q29q0AvgmXGXontKVW0ee42JphsTsNSGpOxxuXHErE5i/JrbCIgi8ce3KQKBgQDoCkOSfwNWURR7MhRmMAWPCyXDhm/8hJGsBBDp/skVcterfqOUd2BXWx8LlpRVRQMrPa18XeHQS0GSPnWUDWVrP7JWi2sO+++8zKA7KwEpyuhWDfvjKdQcq9lZ7j7T9KQlCJ8e+lIapy8XWjTM3juqkSoah1KrjWF2FncS7S0h7QKBgQCpOyhrZzIocIXvnJ93dGRh6ozzbZ4KBl9VlNHmNNBs45QXsgE5VaR8byZKCvuOMfr/hUTIxTrEqwYo4P4KqUrM9EDjF67DtUKtEo+Dtrs3NE6RNPkG+/OqlcNul6w28mmzPl8XFjGm/MnR3E+Pjw7EkFvixgLMe+7yG3V5WGiEDwKBgQCf30KDUuOXuzFjWDPZ3EhYMBQKzTunPien3v1QW21sS73wuMY36rAEQBH5x/vXbD8sscgwIfcNrmw1OLeGFFzGMhLLsi9HGaop6MqVOaIJi3XcpLHh59XvEzAj2BSNsMbPhUss6sda+clmS46JgKyXboEV2hrJfBWkaQINlkA8WQKBgH3JrBSRIxYt9VASQfHfgNHLLrOuEd9vtyL8uDv9m8KkMiqetAwy3U1krLgyi6K5AdE19NeqyjDu0mhGPG4eQawwDZ7+tndf3syYVDZZ97Rj29ZQ4p1PX2G3agllEavR6cFCphmZ9JQjp7umnzic5CQ1DSd1eRUXNZed02a70Qv/AoGBAMX6hTPKhIY7FUCsvVOI0+CTgOsWJj+0G8r91Mwg/DPrSsPuQy0x9CLh6XvMzAw3J/d39YUVk47x/i48KVudITcNsn+JAckumlyk3cT01JvMJ0WH/p7lJiLGey54dG4nT/+dvkcpCLQMd0R5IC+/iPo03sNKMqhVUE8xL+ChATJf";
private static final String RSA_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmWRlnDz/M6zDQOqDYRUxgjtmxHiO7fbMms4WyLu53TjNOvIiOw/mvSqnT2QVXy0GnRf8BleNuGA2Ujt44fukiEK0xYcjmng1k3zKBKmmoaol+Em3FP/W35Ah1+DWoluBP/HoJ4J1avsf0IOkq2guQDgVi8epALFIYCoUiaajYoFYf5+BD3KuiEWtsmVnPhVGoZsQeI0yaN7O3sURgMrHl5gYvMz+J0/uPHi9o0nTKE5tCGwnspglObuhBwrjlpZ00r+2pF7M8CD7aYPiLmCIMaU/UNAiWaxBVj3Y3LedP8qlcmdRR0fq0dyOVrJH3SVVTUiO5td/QSq6w/Rra2sw4wIDAQAB";
public static void main(String[] args) throws Exception {
// 指定token过期时间为10秒
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, 10);
Date expiresDate = calendar.getTime();
String jwtToken = genetatetJwtToken(expiresDate);
System.out.println("生成 jwtToken=" + jwtToken);
//jwtToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6ImFkbWluIiwiZXhwIjoxNjQ0NDc0MzY5LCJ1c2VySWQiOjIxfQ.2Tgvta3obNfyqUSlFuZPNuGGBs-stB7NeD-GU2R6Sn8";
resolveJwtToken(jwtToken);
}
/**
* 生成token
* @param expiresDate
* @return
*/
private static String genetatetJwtToken(Date expiresDate ) throws Exception {
// 获取RSA私钥
RSAPrivateKey privateKey = (RSAPrivateKey) RsaUtils.getPrivateKey(RSA_PRIVATE_KEY);
// Header中自定义放点值
Map<String,Object> payload = new HashMap<>();
payload.put("userId", "21");
payload.put("userName", "admin");
String jwtToken = Jwts.builder()
// Header
.setHeader(payload)
// Payload
.claim("userId", "21")
.claim("userName", "admin")
.setId(new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes())))
// 过期时间
.setExpiration(expiresDate)
// 签名指定私钥
.signWith(privateKey, SignatureAlgorithm.RS256)
.compact();
return jwtToken;
}
/**
* 解析token
* @param jwtToken
*/
private static void resolveJwtToken(String jwtToken) throws Exception {
// 获取RSA公钥
RSAPublicKey publicKey = (RSAPublicKey) RsaUtils.getPublicKey(RSA_PUBLIC_KEY);
// 创建解析对象,使用的算法和secret要与创建token时保持一致
JwtParser jwtParser = Jwts.parserBuilder().setSigningKey(publicKey).build();
// 解析指定的token
Jws<Claims> claimsJws = jwtParser.parseClaimsJws(jwtToken);
// 获取解析后的token中的信息
JwsHeader header = claimsJws.getHeader();
System.out.println("header:" + header);
Claims jwsBody = claimsJws.getBody();
System.out.println("Payload:" + jwsBody);
System.out.println("过期时间:" + jwsBody.getExpiration());
String signature = claimsJws.getSignature();
System.out.println("signature:" + signature);
}
}
总结:
– 求知若饥,虚心若愚。