JWT , 全写JSON Web Token, 是开放的行业标准RFC7591,用来实现端到端安全验证.
从开发者理解的角度来说:
- JWT 是一个定义传递信息的标准
- JWT 的Token是一个Base64编码的字符串, 类似
eyJhbGciOiJIUzI1NyJ9.eyJzdWIiOiJvc2NhciJ9.p1no61S/XIQ0QNEhzB8e0eI9Q39jIGgkDxUjYipKnzw=
这个字符串里面包含2个点号(.),可以分成3段, 分别表示头部,负载和签名。
关于JWT的更多介绍,可以参考:
JWT介绍以及java-jwt的使用
JWT Token是如何产生的
- 最简单的JWT由两部分组成: header(头部) 和 payload(负载)
关于JWT的基本介绍, 可以参考:
JWT介绍以及java-jwt的使用
比如头部是:
{
"alg": "none"
}
负载是:
Hello, JWT
- 移除不必要的空格
String header = "{\"alg\":\"none\"}";
String payload= "Hello, JWT";
- 获取UTF-8的字节后,使用Base64进行编码
String encodedHeader = Base64.getEncoder().encodeToString(header.getBytes());
String encodedPayload = Base64.getEncoder().encodeToString(payload.getBytes());
4.将编码后的字符使用点号(.) 连接起来
String compact= encodedHeader + "." + encodedPayload + ".";
连接后的Token如下:
eyJhbGciOiJub25lIn0=.SGVsbG/vvIwgSldU.
从这里可以看出, JWT其实并不神秘, 只是对字符串进行了Base64编码, 所以通过解码或是一些在线的Base64解码的工具就可以直接得到结果了,比如 https://base64.us/
因为没有进行数字签名,上面的Token是不安全和未受保护的, 不能保证没有第三方对这个内容进行修改,所以为了提升安全,就需要添加数字签名。
JWS ,signedJWT -加密签名的JWT
上面的示例payload是一个字符串, 但实际使用时,payload更多是JSON 格式的数据, 对JWT进行数字签名后的Token 就称为JWS了。
还是从简单的示例来看:
- 头部
{
"alg": "HS256"
}
使用HS256算法进行数字签名
2. 负载
{
"sub":"oscar"
}
- 去除空格的内容
String header = "{\"alg\":\"HS257\"}";
String payload= "{\"sub\":\"oscar\"}";
- 获取字节数组和使用Base64编码,并且使用点号连接
String encodedHeader = Base64.getEncoder().encodeToString(header.getBytes());
String encodedPayload = Base64.getEncoder().encodeToString(payload.getBytes());
String concatenated = encodedHeader + '.' + encodedPayload;
- 使用足够安全的摘要加密算法(比如使用HMAC-SHA-256), 获取加密后的摘要,也就是数字签名。最后将头部、负载和签名组合起来就是一个完整的Token 了。
String header = "{\"alg\":\"HS257\"}";
String payload= "{\"sub\":\"oscar\"}";
Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String encodedHeader = Base64.getEncoder().encodeToString(header.getBytes());
String encodedPayload = Base64.getEncoder().encodeToString(payload.getBytes());
String concatenated = encodedHeader + '.' + encodedPayload;
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
sha256_HMAC.init(secretKey);
byte[] signature = sha256_HMAC.doFinal(concatenated.getBytes("utf-8"));//使用加密算法产生签名
String compact = concatenated + '.' + Base64.getEncoder().encodeToString( signature ); //将签名和头,载荷连接起来
System.out.println(compact);
由此产生的Token如下:
eyJhbGciOiJIUzI1NyJ9.eyJzdWIiOiJvc2NhciJ9.HjomJsGXx3vO/x16euJo7kFsBFJaOEdyoj5Czbt3XE4=
JWE, encrypted JWT
JWE就是对数字签名的Token进行加密,加密之后使用Base64解码之后是无法直接获取Token的内容的, 加密的方式可以是对称加密, 可以是非对称加密。JJWT也提供了一些加密的快速方法, 但是在不同的版本中支持有差异, 这里使用Java 本身的DES对称加密进行演示。
示例代码如下:
@Test
public void jwe() throws Exception {
String header = "{\"alg\":\"HS257\"}";
String payload = "{\"sub\":\"oscar\"}";
Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String encodedHeader = Base64.getEncoder().encodeToString(header.getBytes());
String encodedPayload = Base64.getEncoder().encodeToString(payload.getBytes());
String concatenated = encodedHeader + '.' + encodedPayload;
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
sha256_HMAC.init(secretKey);
byte[] signature = sha256_HMAC.doFinal(concatenated.getBytes("utf-8"));
String compact = concatenated + '.' + Base64.getEncoder().encodeToString(signature);
//使用DES算法对称加密
String strKey = "this is my password";
DESKeySpec desKeySpec = new DESKeySpec(strKey.getBytes(StandardCharsets.UTF_8));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey key = keyFactory.generateSecret(desKeySpec);
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = cipher.doFinal(compact.getBytes(StandardCharsets.UTF_8));
String encryptedText = Base64.getEncoder().encodeToString(encryptedBytes);
System.out.println(encryptedText);
}
加密之后的Token使用Base64解码后得到的内容如下:
要得到正确的内容, 需要使用密钥进行解密之后, 再进行Base64解码。
关于Java对称加密与解密, 可以参考:
Java实现对称加密(DES,AES)快速入门示例
名词解释
- JWT, JSON Web Token。 JSON格式的Web令牌
- JWS: signed JWT,签名的JWT
- JWE: encrypted JWT,签名且加密的JWT(对负载也加密)
三者的关系如下:
完整示例代码
- https://github.com/osxm/java-ency/blob/master/src/main/java/com/osxm/je/topic/security/JjwtDemo.java