铿然架构 | 作者 / 铿然一叶 这是 铿然架构 的第 116 篇原创文章
1. JWT介绍
1.1 定义
JWT 是JSON Web Token的缩写,是一种用于在网络上传递信息的轻量级、自包含的安全标准。JWT是基于JSON格式的标准,定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息。通常,JWT被用于身份验证和信息传递,特别是在Web应用程序和服务之间。
1.2 组成
JWT由三部分组成:
● Header(头部)
头部通常包含两部分信息:使用的签名算法("alg")和令牌类型("type")。
令牌类型和例子:
JWT(默认类型): 如果未提供"typ"声明,默认为JWT类型。在JWT头部中通常不显式指定这个类型,因为它是默认值。
java
{
"alg": "HS256",
"typ": "JWT"
}
JWS(JSON Web Signature): 表示JWT是带有数字签名的JWT。数字签名用于验证JWT的完整性和来源。
java
{
"alg": "HS256",
"typ": "JWS"
}
JWE(JSON Web Encryption): 表示JWT是加密的JWT。加密用于保护JWT的负载信息,使其只能被授权的实体解密和使用。
java
{
"alg": "RSA-OAEP",
"enc": "A256GCM",
"typ": "JWE"
}
注意:实际使用中,并没有严格要求只能按照上述定义,属性和取值都可以自定义,只要客户端和服务端双方约定好则可。
● Payload(负载)
负载包含了要传递的信息。它可以包含一些预定义的声明(claims),比如过期时间(exp)、发布时间(iat)、发行人(iss)等,也可以包含一些自定义的声明。负载被编码成JSON格式。
Payload的信息也可以自定义,符合约定则可。
● Signature(签名)
签名用于验证消息在传输过程中是否被篡改。它由头部、负载和密钥进行加密而成。签名算法由头部的 "alg" 字段指定,常见的有 HMAC SHA256("HS256")和 RSA SHA256("RS256")等。
1.3 应用场景
JWT适用于在不同的系统、服务或组件之间进行安全且可靠的身份验证和信息传递。在Web开发中,常见的场景包括用户身份验证、单点登录(SSO)等。
1.4 优点
● 轻量级
JWT是一种轻量级的标准,使用JSON格式,使得它相对于其他格式更加紧凑,适合在网络中传递。
● 自包含
JWT负载(Payload)中包含了所需的信息,因此,验证方无需查询数据库或远程服务器来获取相关信息,这可以提高性能。
● 无状态
JWT是无状态的,服务端不需要在会话中保存状态信息,这对于分布式系统或无状态服务的扩展性有一定的优势。
● 跨平台
由于JWT基于JSON格式,因此易于在不同平台之间进行解析和生成,适用于多语言环境。
1.5 JWT使用的常见加密算法
● HS256(HMAC SHA-256)
使用对称加密,通过共享的密钥进行签名和验证。HS256表示使用HMAC SHA-256算法。
其他HS算法还有HS384(HMAC SHA-384)、HS512(HMAC SHA-512)。
● RS256(RSA SHA-256)
使用非对称加密,通过使用RSA密钥对进行签名和验证。RS256表示使用RSA SHA-256算法。
其他RS算法还有RS384(RSA SHA-384)、RS512(RSA SHA-512)
● ES256(ECDSA P-256 SHA-256)
使用椭圆曲线数字签名算法(ECDSA)进行签名和验证,使用P-256曲线和SHA-256散列算法。
其他ES算法还有ES384(ECDSA P-384 SHA-384、ES512(ECDSA P-521 SHA-512)
1.6 JWT性能考虑因素
● 密钥管理
使用对称密钥(HMAC)或非对称密钥(RSA、ECDSA)的选择,以及密钥的安全性都会影响 JWT 的性能。对称密钥通常更快,但需要更好的密钥管理。
● 过期时间
如果JWT的生命周期较短,验证方可能需要更频繁地生成新的JWT,这会对性能产生一定影响。
● 算法选择
不同的签名算法和加密算法对性能有影响。一些算法可能更复杂,因此更消耗计算资源。
● 网络传输
虽然JWT本身很紧凑,但在大规模的分布式系统中,网络传输可能成为性能的瓶颈。考虑使用压缩算法来减小传输的数据量。
2. JWT的生成和验证过程
2.1 生成JWT
2.1.1 步骤
1.创建头部,指定算法和令牌类型。
2.创建负载,包含要传递的信息。
3.使用头部和负载以及密钥,通过指定的签名算法生成签名。
4.将头部、负载和签名组合成一个字符串,中间用点号连接起来。
2.1.2 代码示例
引入maven依赖:
java
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.2.2</version>
</dependency>
代码:
java
/**
*
* @param apiKey api使用的key,一般用于绑定客户端身份,服务端下发
* @param apiSecret 密钥,服务端下发
* @return JWT token
*/
public static String getToken(String apiKey, String apiSecret) {
Algorithm algorithm = Algorithm.HMAC256(apiSecret.getBytes(StandardCharsets.UTF_8));
Map<String, Object> headers = new HashMap<>();
headers.put("alg", "HS256");
headers.put("typ", "JWS");
Map<String, Object> playload = new HashMap<>();
playload.put("api_key", apiKey);
playload.put("exp", System.currentTimeMillis() + expireMillis); // 过期时间
playload.put("timestamp", Calendar.getInstance().getTimeInMillis()); // 时间戳
return JWT.create().withHeader(headers).withPayload(playload).sign(algorithm);
}
2.2 验证JWT
2.2.1 步骤
1.拆分JWT字符串,得到头部、负载和签名。
2.使用相同的密钥和签名算法,对头部和负载进行签名,得到新的签名。
3.比较新生成的签名与原始签名是否一致,以验证JWT的完整性。
4.验证通过后,解析负载中的信息。
2.2.2 代码示例
java
public static void verifySignature(String token) throws UnsupportedEncodingException {
DecodedJWT decodedJWT = JWT.decode(token);
String base64Header = decodedJWT.getHeader();
String base64Payload = decodedJWT.getPayload();
String base64Signature = decodedJWT.getSignature();
System.out.println("************** Base64 格式 **************");
System.out.println("header: " + base64Header);
System.out.println("payload: " + base64Payload);
System.out.println("signature: " + base64Signature);
System.out.println("");
// Base64解码得到json格式字符串
String jsonHeader = new String(Base64.getUrlDecoder().decode(base64Header));
String jsonPayload = new String(Base64.getUrlDecoder().decode(base64Payload));
System.out.println("************** Json 格式 **************");
System.out.println("header: " + jsonHeader);
System.out.println("payload: " + jsonPayload);
System.out.println("");
// 解析header
JSONObject jsonObject = JSONObject.parseObject(jsonHeader);
String alg = (String) jsonObject.get("alg");
String typ = (String) jsonObject.get("typ");
// 解析playload
JSONObject payloadJsonObject = JSONObject.parseObject(jsonPayload);
String apiKey = payloadJsonObject.get("api_key").toString();
String exp = payloadJsonObject.get("exp").toString();
String timestamp = payloadJsonObject.get("timestamp").toString();
// 进行必要的校验,例如校验exp是否还在有效期内
// do something
// 这里用约定的密钥生成算法类, 通常是服务端根据客户端传过来的API_KEY查询得到,这样每个客户端的密钥不一样,更安全
String clientAPISecret = getExceptedAPISecret(apiKey);
Algorithm algorithm = Algorithm.HMAC256(clientAPISecret.getBytes(StandardCharsets.UTF_8));
// 重新生成签名,要点是用服务端获取到的密钥,其他信息可用客户端的
byte[] exceptedSignatureBytes = algorithm.sign(base64Header.getBytes(StandardCharsets.UTF_8), base64Payload.getBytes(StandardCharsets.UTF_8));
String exceptedSignature = Base64.getUrlEncoder().withoutPadding().encodeToString(exceptedSignatureBytes);
// 比较签名是否一致
System.out.println("签名验证结果: " + base64Signature.equals(exceptedSignature));
}
打印日志:
java
************** Base64 格式 **************
header: eyJ0eXAiOiJKV1MiLCJhbGciOiJIUzI1NiJ9
payload: eyJhcGlfa2V5IjoiZGM2MTJiZTM4ZmY3YjhiZGJiMTllNGM4ZGU3MmQ1YjUiLCJleHAiOjE3MDEzMzIxODM5NTIsInRpbWVzdGFtcCI6MTcwMTMzMDM4Mzk1NX0
signature: O4IqFY6zqSUnsKb-0HgH-t596pcL3ClBSE4rdPlvs2E
************** Json 格式 **************
header: {"typ":"JWS","alg":"HS256"}
payload: {"api_key":"dc612be38ff7b8bdbb19e4c8de72d5b5","exp":1701332183952,"timestamp":1701330383955}
签名是否一致: true
3. 其他类似标准和协议
除了JWT之外,还有一些类似的标准和协议,用于实现安全的身份验证和授权,以及在网络中传递信息。以下是一些常见的标准和协议:
● OAuth 2.0
OAuth 2.0 是一种用于授权的开放标准,允许用户授权第三方应用访问他们存储在其他服务提供者上的资源,而不需要将用户名和密码提供给第三方应用。OAuth 2.0 定义了不同的授权流程,例如授权码授权流程、密码授权流程等。
● OpenID Connect (OIDC)
OpenID Connect 是建立在 OAuth 2.0 基础上的身份验证协议。它定义了一种用于验证用户身份的标准化方法,允许第三方应用通过身份提供者验证用户,同时获得访问令牌。OIDC 使用 JWT 来传递身份信息。
● SAML (Security Assertion Markup Language)
SAML 是一种基于 XML 的标准,用于在身份提供者和服务提供者之间交换身份和认证信息。SAML 主要用于企业单点登录(SSO)场景。
● Kerberos
Kerberos 是一种网络身份验证协议,通过密钥分发和票据传递实现身份验证。它常用于企业环境中的单点登录和身份验证。
● SCIM (System for Cross-domain Identity Management)
SCIM 是一种用于用户身份管理的开放标准,旨在简化用户身份的创建、修改、查询和删除等操作。它可以与 OAuth 和其他标准结合使用。
● JOSE (JSON Object Signing and Encryption)
JOSE 定义了一系列用于在 JSON 对象中加密和签名的规范。它包括 JWS(JSON Web Signature)、JWE(JSON Web Encryption)、JWK(JSON Web Key)等规范。
这些标准和协议通常用于构建安全的身份验证和授权系统,使得在分布式系统中进行身份验证和数据传递更加安全和可靠。选择使用哪个标准或协议取决于应用的需求、安全性要求以及所涉及的技术栈。
4. 总结
JWT是一个跨平台、无状态、自包含、轻量级的鉴权协议,通常用于机机接口。
另外,JWT的header和playload可以根据业务需要灵活定义,增加了扩展性。
这些特点使得JWT易于使用,快速构建系统间安全认证时是一个不错的选择。
其他阅读:
萌新快速成长之路
如何编写软件设计文档
Spring Cache架构、机制及使用
布隆过滤器适配Spring Cache及问题与解决策略
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(五)函数式接口-复用,解耦之利刃