Java后端JWT验证的实现

在Java后端中使用JWT令牌验证需要引入三个包

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>

jjwt-api用于在编译时提供API接口,包含接口、方法等,jjwt-impl在运行时提供默认实现,里面的方法可以被重写,jjwt-jackson用于提供JSON处理器,也可以使用其他JSON库


基本API

Claims接口

Claims是JWT的核心载荷,继承于Map<String, Object>泛型集合,其内部有一些默认字段

String ISSUER = "iss";  //签发者,代表签发令牌的机构
String SUBJECT = "sub"; //主题,用于标识JWT的用法什么的
String AUDIENCE = "aud"; //受众,标识该JWT的预期接收对象
String EXPIRATION = "exp"; //过期时间,表明该JWT的有效截止时间
String NOT_BEFORE = "nbf"; //生效时间,JWT在该时间前无效
String ISSUED_AT = "iat"; //签发时间,表示该JWT创建的时间
String ID = "jti"; //JWT ID,表示一个JWT的唯一标识

以上字段在接口内都规定了getter、setter方法

CompressionCodec接口

该接口用于规定JWT的压缩与解压方法,以减少 JWT 的总体大小

byte[] compress(byte[] var1) throws CompressionException;

byte[] decompress(byte[] var1) throws CompressionException;

Header接口

header是JWT的头部部分,继承于Map<String, Object>泛型集合,可声明JWT的一些元数据、描述

String JWT_TYPE = "JWT";  //声明JWT类型
String TYPE = "typ"; //令牌类型字段名
String CONTENT_TYPE = "cty"; //内容类型字段名
String COMPRESSION_ALGORITHM = "zip"; //压缩算法字段名

以上字段在接口内都规定了getter、setter方法

JwtBuilder接口

该接口规定了构建JWT令牌的核心方法

public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
//设置JWT头
JwtBuilder setHeader(Header var1);
//由于JWT头本质是一个泛型Map,也支持直接传入Map对象
JwtBuilder setHeader(Map<String, Object> var1);
//像Hearder中添加单个参数
JwtBuilder setHeaderParams(Map<String, Object> var1);

JwtBuilder setHeaderParam(String var1, Object var2);
//直接传入JSON数据的方法,现已很少使用
JwtBuilder setPayload(String var1);
//设置整体载荷
JwtBuilder setClaims(Claims var1);

JwtBuilder setClaims(Map<String, ?> var1);
//添加自定义载荷字段名
JwtBuilder addClaims(Map<String, Object> var1);
//设置签发者
JwtBuilder setIssuer(String var1);
//设置主题
JwtBuilder setSubject(String var1);
//设置目标受众
JwtBuilder setAudience(String var1);
//设置过期时间
JwtBuilder setExpiration(Date var1);
//设置生效时间
JwtBuilder setNotBefore(Date var1);
//设置签发时间
JwtBuilder setIssuedAt(Date var1);
//设置JWT ID
JwtBuilder setId(String var1);

JwtBuilder claim(String var1, Object var2);
//使用传入的key为JWT令牌签名
JwtBuilder signWith(Key var1) throws InvalidKeyException;
//使用指定的签名算法对密钥进行签名
JwtBuilder signWith(Key var1, SignatureAlgorithm var2) throws InvalidKeyException;
//使用压缩
JwtBuilder compressWith(CompressionCodec var1);
//自定义Base64编码逻辑
JwtBuilder base64UrlEncodeWith(Encoder<byte[], String> var1);

JwtBuilder serializeToJsonWith(Serializer<Map<String, ?>> var1);
//执行JWT构建,输出JWT字符串
String compact();

}

claim()方法在默认实现类中的逻辑。若claims字段不存在则新添字段,否则检测value是否为空,为空则删除传入的字段,否则放入字段

public JwtBuilder claim(String name, Object value) {
Assert.hasText(name, "Claim property name cannot be null or empty.");
if (this.claims == null) {
if (value != null) {
this.ensureClaims().put(name, value);
}
} else if (value == null) {
this.claims.remove(name);
} else {
this.claims.put(name, value);
}

return this;
}

JwtParserBuilder接口

该接口规定了解析JWT令牌的核心方法,先前的JwtParser众多方法已被弃用,JwtParserBuilder是其替代方案,该方案强制验证 JWT Claims 中的特定值,不匹配则解析失败

public interface JwtParserBuilder {
//验证JWT ID
JwtParserBuilder requireId(String var1);
//验证主题
JwtParserBuilder requireSubject(String var1);
//验证受众
JwtParserBuilder requireAudience(String var1);
//验证签发者
JwtParserBuilder requireIssuer(String var1);

JwtParserBuilder requireIssuedAt(Date var1);

JwtParserBuilder requireExpiration(Date var1);

JwtParserBuilder requireNotBefore(Date var1);

JwtParserBuilder require(String var1, Object var2);
//设置参考时钟
JwtParserBuilder setClock(Clock var1);
//设置允许的时间偏差
JwtParserBuilder setAllowedClockSkewSeconds(long var1) throws IllegalArgumentException;
//设置密钥
JwtParserBuilder setSigningKey(byte[] var1);

JwtParserBuilder setSigningKey(String var1);

JwtParserBuilder setSigningKey(Key var1);
//设置动态密钥解析器
JwtParserBuilder setSigningKeyResolver(SigningKeyResolver var1);
//处理压缩的JWT载荷
JwtParserBuilder setCompressionCodecResolver(CompressionCodecResolver var1);

JwtParserBuilder base64UrlDecodeWith(Decoder<String, byte[]> var1);

JwtParserBuilder deserializeJsonWith(Deserializer<Map<String, ?>> var1);
//构建解析器
JwtParser build();
}

JwtParser接口

现在JwtParser一般只用于接收JwtParserBuilder规定的解析规则进行解析操作,不直接参与验证过程

public interface JwtParser {
char SEPARATOR_CHAR = '.';
//检查是否具有签名
boolean isSigned(String var1);
//不验证签名返回Jwt对象
Jwt parse(String var1) throws ExpiredJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
//使用指定处理器解析
<T> T parse(String var1, JwtHandler<T> var2) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
//解析未签名/不验证签名的纯文本JWT
Jwt<Header, String> parsePlaintextJwt(String var1) throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
//解析但不验证签名的声明JWT
Jwt<Header, Claims> parseClaimsJwt(String var1) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
//验证签名的纯文本JWS
Jws<String> parsePlaintextJws(String var1) throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
//验证签名的声明JWS
Jws<Claims> parseClaimsJws(String var1) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
}

具体实现

构造JWT,先使用jjwt-impl自带的Jwts类获取claims

Claims claims = Jwts.claims();

放入字段,使用密钥进行签名

public String makeUserToken() {
Claims claims = Jwts.claims().setSubject("CommonUser");
claims.put("iat", new Date());
claims.put("exp", new Date(System.currentTimeMillis() + EXPIRATION_TIME));
return Jwts.builder().setClaims(claims).signWith(Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8))).compact();
}

解析JWT,需要先定义一个解析器

Jws<Claims> claimsJws = Jwts.parserBuilder()
.setSigningKey(SECRET.getBytes(StandardCharsets.UTF_8))
.build()
.parseClaimsJws(Token);

获取字段名

String role = claimsJws.getBody().getSubject();
String other = claimsJws.getBody().get(String claimName, Class<T> requiredType);

最后是我写的一个JWT验证后台,用于一个题目的JWT验证,虽十分简陋但也够用

@Component
public class JWTHandler {
private static final String SECRET = "{SECRET_KEY_WHICH_YOU_NEED_TO_FIND_IT_OUT}";
private static final long EXPIRATION_TIME = 86_400_000L;

public Boolean parseToken(String Token) {
try {

Jws<Claims> claimsJws = Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8)))
.build()
.parseClaimsJws(Token);
String role = claimsJws.getBody().getSubject();
return role.equals("admin");

} catch (Exception e) {
return false;
}
}

public String makeUserToken() {
Date now = new Date();
Date expiry = new Date(now.getTime() + EXPIRATION_TIME);
Claims claims = Jwts.claims().setSubject("CommonUser");
claims.put("iat", now);
claims.put("exp", expiry);
return Jwts.builder().setClaims(claims).signWith(Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8))).compact();
}

}