jwt(JSON Web Tokens)
JSON Web Token (JWT) 是一个开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息作为 JSON 对象。 此信息可以验证和信任,因为它是数字签名的。 JWT 可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。
虽然 JWT 可以加密以在各方之间提供保密性,但我们将专注于签名令牌。 签名的令牌可以验证其中包含的声明的完整性,而加密的令牌会向其他方隐藏这些声明。 当使用公钥/私钥对对令牌进行签名时,签名还证明只有持有私钥的一方才是签署它的一方。
https://jwt.io/
从这张图我们可以看出来jjwt是在Java中支持最好的,所以我们直接学这个
http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
在前后端分离的项目中我们使用jwt生成的token令牌进行校验工作
校验通过响应请求,校验不通过直接拒绝
分为三个部分
header.payload.signature
标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HNAC SHA256或RSA。它会使用Base64编码组成JWT结构的第一部分。
注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
{
"alg":"HS256",
"typ":"JWT"
}
有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用Base64编码组成JWT 结构的第二部分
Signature 需要使用编码后的header和 payload以及我们提供的一个密钥,然后使用header 中指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过
最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。
https://github.com/jwtk/jjwt
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
package com.example.ss1.util;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
public class JWTUtil {
public static void main(String[] args) {
SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS384);
String userName = Jwts.builder().setSubject("userName").signWith(secretKey).compact();
System.out.println(userName);
}
}
SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS384);
Jwts.builder()
.setSubject("userName")
.signWith(secretKey)
.compact()
jjwt不支持ES256,ES512,ES384所以如果你使用Keys.secretKeyFor(SignatureAlgorithm.ES256)
就会看到如下报错信息
Exception in thread "main" java.lang.IllegalArgumentException: The ES256 algorithm does not support shared secret keys.
at io.jsonwebtoken.security.Keys.secretKeyFor(Keys.java:140)
at com.example.ss1.util.JWTUtil.main(JWTUtil.java:11)
很简单将ES系类换成HS系列即可
JWT HMAC-SHA 签名算法 HS256、HS384 和 HS512 要求密钥的位数至少与 RFC 7512 第 3.2 节中算法的签名(摘要)长度一样多。 这表示:
HS256 是 HMAC-SHA-256,它会产生 256 位(32 字节)长的摘要,因此 HS256 要求您使用至少 32 字节长的密钥。
HS384 是 HMAC-SHA-384,它会生成 384 位(48 字节)长的摘要,因此 HS384 要求您使用至少 48 字节长的密钥。
HS512 是 HMAC-SHA-512,它会生成 512 位(64 字节)长的摘要,因此 HS512 要求您使用至少 64 字节长的密钥。
JWT RSA 签名算法 RS256、RS384、RS512、PS256、PS384 和 PS512 都需要 2048 位的最小密钥长度(也称为 RSA 模数位长度)。 任何小于此值(例如 1024 位)的内容都将被拒绝并返回 InvalidKeyException。
也就是说,为了与最佳实践保持一致并增加密钥长度以延长安全寿命,JJWT 建议您使用:
RS256 和 PS256 至少 2048 位密钥
至少 3072 位密钥,带 RS384 和 PS384
RS512 和 PS512 至少 4096 位密钥
这些只是 JJWT 建议而不是要求。 JJWT 仅强制执行 JWT 规范要求,对于任何 RSA 密钥,要求是 RSA 密钥(模数)长度(以位为单位)必须 >= 2048 位。
JWT解析时需要捕获JwtException
异常
String subject = Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(userName).getBody().getSubject();
Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(userName).getBody().getSubject();
package com.example.ss1.util;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
public class JWTUtil {
public static void main(String[] args) {
SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS384);
String userName = Jwts.builder().setSubject("userName").signWith(secretKey).compact();
System.out.println(userName);
//解析JWT
String subject = Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(userName).getBody().getSubject();
System.out.println(subject);
}
}
setSubject("userName")
放置多个时使用的是Map来存
String jws = Jwts.builder()
.setHeaderParam("kid", "myKeyId")
在JwtBuilder提供了在JWT规范中定义的标准注册权利要求名方便setter方法。他们是:
setIssuer:设置iss(发行方)索赔
setSubject:设置sub(主题)声明
setAudience:设置aud(受众群体)声明
setExpiration:设置exp(到期时间)声明
setNotBefore:设置nbf(不早于)声明
setIssuedAt:设置iat(签发)声明
setId:设置jti(JWT ID)声明
Calendar instance = Calendar.getInstance();
instance.add(Calendar.HOUR,10);
Date expiration = instance.getTime();
Jwts.builder()
.setIssuer("发行方")
.setSubject("主题声明")
.setAudience("受众群体")
.setExpiration(expiration) //a java.util.Date
.setNotBefore(expiration) //a java.util.Date
.setIssuedAt(expiration) // for example, now
.setId(String.valueOf(UUID.randomUUID())); //just an example id
果您需要设置一个或多个与上面显示的标准setter方法声明不匹配的自定义声明,则可以JwtBuilder claim根据需要简单地调用一次或多次:
Claims claims = Jwts.claims();
claims.put("userId",54564);
claims.put("userName","zhangsan");
Jwts.builder().setClaims(claims);
注意:如果您期望使用JWS,请始终调用JwtParser的parseClaimsJws方法(而不是其他可用的类似方法之一),因为这可以保证解析签名的JWT的正确安全模型。
Jws<Claims> jws;
try {
jws = Jwts.parserBuilder() // (1)
.setSigningKey(key) // (2)
.build() // (3)
.parseClaimsJws(jwsString); // (4)
// we can safely trust the JWT
catch (JwtException ex) { // (5)
// we *cannot* use the JWT as intended by its creator
}
package com.example.ss1.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Calendar;
import java.util.Date;
import java.util.UUID;
public class JWTUtil {
public static void main(String[] args) {
SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS384);
Claims claims = Jwts.claims();
claims.put("userId",1234);
claims.put("userName","zhangsan");
String token = Jwts.builder()
.setClaims(claims)
.signWith(secretKey)
.compact();
System.out.println(token);
//解析JWT
Claims body = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build().parseClaimsJws(token).getBody();
Object userId = body.get("userId");
System.out.println(userId);
}
}
package com.example.ss1.util;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import java.util.Date;
public class JJWTUtil {
//默认密钥
public final static String DEFAULT_SECRET_KEY = "self_define_secret_key_about_the_project";
public SecretKey getSecretKey() {
return Keys.hmacShaKeyFor(DEFAULT_SECRET_KEY.getBytes(StandardCharsets.UTF_8));
}
//设置Claim
public Claims setClaims(Integer userId, String username) {
Claims claims = Jwts.claims();
claims.put("userId", userId);
claims.put("username", username);
return claims;
}
//构造token
public String createToken(Claims claims) {
//默认设置七天
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, 60 * 60 * 24 * 7);
String token = Jwts.builder()
.setClaims(claims)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(calendar.getTime())
.signWith(getSecretKey(), SignatureAlgorithm.HS256)
.compact();
return token;
}
//解析token判断是否过期,过期为false,未过期为true
public Boolean parseToken(String token) {
boolean flag = false;
try {
Jwts.parserBuilder()
.setSigningKey(getSecretKey())
.build()
.parseClaimsJws(token);
flag = true;
} catch (JwtException e) {
e.getMessage();
}
return flag;
}
//获取header
public JwsHeader getHeader(String token) {
Boolean exceptionJudge = parseToken(token);
JwsHeader header = null;
if (exceptionJudge) {
header = Jwts.parserBuilder()
.setSigningKey(getSecretKey())
.build()
.parseClaimsJws(token)
.getHeader();
}else {
System.out.println("token已过期");
}
return header;
}
//获取payload
public Claims getPayload(String token) {
Boolean exceptionJudge = parseToken(token);
Claims body = null;
if (exceptionJudge) {
body = Jwts.parserBuilder()
.setSigningKey(getSecretKey())
.build()
.parseClaimsJws(token)
.getBody();
}else {
System.out.println("token已过期");
}
return body;
}
//获取时间数据
public Object getExceptionTime(String token) {
Boolean exceptionJudge = parseToken(token);
Claims body = null;
if (exceptionJudge) {
body = Jwts.parserBuilder()
.setSigningKey(getSecretKey())
.build()
.parseClaimsJws(token)
.getBody();
}else {
System.out.println("token已过期");
}
Object exp = body.get("exp");
return exp;
}
}