系统安全认证协议JWT


铿然架构 | 作者 / 铿然一叶 这是 铿然架构 的第 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基础(五)函数式接口-复用,解耦之利刃

相关推荐
技术无疆15 分钟前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
GEEKVIP18 分钟前
Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
android·笔记·安全·macos·智能手机·电脑·笔记本电脑
草莓屁屁我不吃1 小时前
Siri因ChatGPT-4o升级:我们的个人信息还安全吗?
人工智能·安全·chatgpt·chatgpt-4o
罗政3 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
架构文摘JGWZ3 小时前
Java 23 的12 个新特性!!
java·开发语言·学习
拾光师4 小时前
spring获取当前request
java·后端·spring
aPurpleBerry4 小时前
neo4j安装启动教程+对应的jdk配置
java·neo4j
我是苏苏4 小时前
Web开发:ABP框架2——入门级别的增删改查Demo
java·开发语言
xujinwei_gingko4 小时前
Spring IOC容器Bean对象管理-Java Config方式
java·spring
2301_789985944 小时前
Java语言程序设计基础篇_编程练习题*18.29(某个目录下的文件数目)
java·开发语言·学习