jjwt源码解析
author:zxw
email:502513206@qq.com
@ Jishou University
1.前言
最近在做一个jwt授权服务,其实也就是给请求的用户生成一个token,不过关于token的验证并不在我服务负责,而是由网关去统一处理。jwt之前也用过几次,不过不是很熟悉只知道是通过base64Url算法进行加密,这次刚好用到了所以来看看jjwt的实现。
2.源码解析
在java中导入jjwt的包就可以使用包中提供的Builder生成jwt的token,以下是基础的用法。
JwtBuilder builder = Jwts.builder()
.setHeader(header) // 头部信息
.setId(uuid) //id:是JWT的唯一标识
.setSubject(subject) //Subject:可以存放用户信息
.setIssuer(appId) //Issuer:签发者
.setIssuedAt(now) //IssuedAt:jwt的签发时间
.signWith(signatureAlgorithm, secretKey);
通过名字可以看到,是使用的builder构造模式生成,默认给我们返回一个DefaultJwtBuilder对象
JwtBuilder
public static JwtBuilder builder() {
return new DefaultJwtBuilder();
}
DefaultJwtBuilder
我们知道jwt是由header,payload,Signature三部分组成。
public class DefaultJwtBuilder implements JwtBuilder {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private Header header; // Header
private Claims claims;
private String payload; // 载体
private SignatureAlgorithm algorithm; // 签名算法
private Key key;
private byte[] keyBytes;
private CompressionCodec compressionCodec; // 压缩算法
}
Header
头部由两部分信息组成,一个是类型,一个是加密的算法,具体类型如下。
{
'typ': 'JWT',
'alg': 'HS256'
}
Header
在jjwt中有个Header接口表示头部,可以看到Header的固定key
public interface Header<T extends Header<T>> extends Map<String,Object> {
/** JWT {@code Type} (typ) value: <code>"JWT"</code> */
public static final String JWT_TYPE = "JWT";
/** JWT {@code Type} header parameter name: <code>"typ"</code> */
public static final String TYPE = "typ";
/** JWT {@code Content Type} header parameter name: <code>"cty"</code> */
public static final String CONTENT_TYPE = "cty";
/** JWT {@code Compression Algorithm} header parameter name: <code>"zip"</code> */
public static final String COMPRESSION_ALGORITHM = "zip";
/** JJWT legacy/deprecated compression algorithm header parameter name: <code>"calg"</code>
* @deprecated use {@link #COMPRESSION_ALGORITHM} instead. */
@Deprecated
public static final String DEPRECATED_COMPRESSION_ALGORITHM = "calg";
}
DefaultHeader
jjwt中默认会使用该类,当然我们也能自己实现Header接口或者继承该类实现自定义的Header,可以看到Header其实就是一个Map,我们也能往头部添加我们自定义的数据
public class DefaultHeader<T extends Header<T>> extends JwtMap implements Header<T> {
}
接下来就是第二部分载体了,在jjwt中我们可以传入json的payload载体也可以用claim封装我们的载体,二者只能选一个。
Claims
可以看到cliams同样是个map,我们在使用setSubject等方法时,实际上就是往下面的key设置对应的Value
public interface Claims extends Map<String, Object>, ClaimsMutator<Claims> {
/** JWT {@code Issuer} claims parameter name: <code>"iss"</code> */
public static final String ISSUER = "iss";
/** JWT {@code Subject} claims parameter name: <code>"sub"</code> */
public static final String SUBJECT = "sub";
/** JWT {@code Audience} claims parameter name: <code>"aud"</code> */
public static final String AUDIENCE = "aud";
/** JWT {@code Expiration} claims parameter name: <code>"exp"</code> */
public static final String EXPIRATION = "exp";
/** JWT {@code Not Before} claims parameter name: <code>"nbf"</code> */
public static final String NOT_BEFORE = "nbf";
/** JWT {@code Issued At} claims parameter name: <code>"iat"</code> */
public static final String ISSUED_AT = "iat";
/** JWT {@code JWT ID} claims parameter name: <code>"jti"</code> */
public static final String ID = "jti";
}
最后一步就是Jwts.builder().signWith(signatureAlgorithm, secretKey);
在该方法中指定我们的加密的算法就是头部{"alg":"HS256"}
所对应的值
public JwtBuilder signWith(SignatureAlgorithm alg, Key key) {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
Assert.notNull(key, "Key argument cannot be null.");
// 加密算法,默认使用HS256
this.algorithm = alg;
// secret,我们与客户端规定的密钥
this.key = key;
return this;
}
@Override
public String compact() {
// 判断载体是否为空
if (payload == null && Collections.isEmpty(claims)) {
throw new IllegalStateException("Either 'payload' or 'claims' must be specified.");
}
// 判断payload和claim是否都不为空
if (payload != null && !Collections.isEmpty(claims)) {
throw new IllegalStateException("Both 'payload' and 'claims' cannot both be specified. Choose either one.");
}
// key和keybytes是否都不为空
if (key != null && keyBytes != null) {
throw new IllegalStateException("A key object and key bytes cannot both be specified. Choose either one.");
}
// 如果为空,生成默认的Header
Header header = ensureHeader();
Key key = this.key;
// 如果未指定密钥算法,则生成默认的算法
if (key == null && !Objects.isEmpty(keyBytes)) {
key = new SecretKeySpec(keyBytes, algorithm.getJcaName());
}
JwsHeader jwsHeader;
if (header instanceof JwsHeader) {
jwsHeader = (JwsHeader)header;
} else {
jwsHeader = new DefaultJwsHeader(header);
}
// 设置头部的alg对应的算法类型
if (key != null) {
jwsHeader.setAlgorithm(algorithm.getValue());
} else {
//no signature - plaintext JWT:
jwsHeader.setAlgorithm(SignatureAlgorithm.NONE.getValue());
}
// 设置压缩算法
if (compressionCodec != null) {
jwsHeader.setCompressionAlgorithm(compressionCodec.getAlgorithmName());
}
// 头部进行base64Url加密
String base64UrlEncodedHeader = base64UrlEncode(jwsHeader, "Unable to serialize header to json.");
String base64UrlEncodedBody;
// 对载体进行base64Url加密
if (compressionCodec != null) {
byte[] bytes;
try {
bytes = this.payload != null ? payload.getBytes(Strings.UTF_8) : toJson(claims);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Unable to serialize claims object to json.");
}
base64UrlEncodedBody = TextCodec.BASE64URL.encode(compressionCodec.compress(bytes));
} else {
// 加密
base64UrlEncodedBody = this.payload != null ?
TextCodec.BASE64URL.encode(this.payload) :
base64UrlEncode(claims, "Unable to serialize claims object to json.");
}
String jwt = base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR + base64UrlEncodedBody;
// 对签名进行base64Url加密
if (key != null) { //jwt must be signed:
// 生成Signer,下发给客户端的密钥
JwtSigner signer = createSigner(algorithm, key);
// 对前两部分进行base64Url加密
String base64UrlSignature = signer.sign(jwt);
// 组合
jwt += JwtParser.SEPARATOR_CHAR + base64UrlSignature;
} else {
// no signature (plaintext), but must terminate w/ a period, see
// https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-6.1
jwt += JwtParser.SEPARATOR_CHAR;
}
// 返回token
return jwt;
}
可以看到jwt的组成,对header进行base64Url加密 + payload的base64Url加密 + (header + payload + secret)三部分加密组成。