JWT , 全写JSON Web Token, 是开放的行业标准RFC7591,用来实现端到端安全验证.
从开发者理解的角度来说:
eyJhbGciOiJIUzI1NyJ9.eyJzdWIiOiJvc2NhciJ9.p1no61S/XIQ0QNEhzB8e0eI9Q39jIGgkDxUjYipKnzw=
这个字符串里面包含2个点号(.),可以分成3段, 分别表示头部,负载和签名。
关于JWT的更多介绍,可以参考:
JWT介绍以及java-jwt的使用
比如头部是:
{
"alg": "none"
}
负载是:
Hello, JWT
String header = "{\"alg\":\"none\"}";
String payload= "Hello, JWT";
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是不安全和未受保护的, 不能保证没有第三方对这个内容进行修改,所以为了提升安全,就需要添加数字签名。
上面的示例payload是一个字符串, 但实际使用时,payload更多是JSON 格式的数据, 对JWT进行数字签名后的Token 就称为JWS了。
还是从简单的示例来看:
{
"alg": "HS256"
}
使用HS256算法进行数字签名
2. 负载
{
"sub":"oscar"
}
String header = "{\"alg\":\"HS257\"}";
String payload= "{\"sub\":\"oscar\"}";
String encodedHeader = Base64.getEncoder().encodeToString(header.getBytes());
String encodedPayload = Base64.getEncoder().encodeToString(payload.getBytes());
String concatenated = encodedHeader + '.' + encodedPayload;
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就是对数字签名的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);
}
要得到正确的内容, 需要使用密钥进行解密之后, 再进行Base64解码。
关于Java对称加密与解密, 可以参考:
Java实现对称加密(DES,AES)快速入门示例