JSON Web Token(缩写 JWT) 目前最流行、最常见的跨域认证解决方案

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: JSON Web Token(缩写 JWT) 目前最流行、最常见的跨域认证解决方案

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

JSON Web Token(缩写 JWT)是目前最流行,也是最常见的跨域认证解决方案。无论是咱们后端小伙伴,还是前端小伙伴对都是需要了解。 本文介绍它的原理、使用场景、用法。

一、跨域认证的问题

1.1、常见的前后端认证方式

  • Session-Cookie
  • Token 验证(包括JWT,SSO)
  • OAuth2.0(开放授权)

1.2、Session-Cookie实现方式

流程大致如下:

1、用户向服务器发送用户名和密码。

2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。

3、服务器向用户返回一个 session_id,写入用户的 Cookie

4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。

5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这种模式在单机时不存在什么问题,但是一旦服务器变为集群模式时,或者是跨域的服务器时,这个时候Session就必须实现数据共享。

这个时候就要考虑每台服务器如何实现对 Session 的数据共享呢??

  1. 第一种解决方式就是实现 Session 数据的持久化。各种服务收到请求时,都向数据持久层请求数据,来验证是否是正确的用户。但其实无论我们将 Session 存放在服务器哪里,都会增加服务器的负担。这种方案优点就是简单,缺点就是扩展性不好,安全性较差,容易增加服务器的负担。
  2. 第二种解决方式其实就是 JWT 的方式实现的,所有的数据不在保存到服务器端,而是保存到客户端,每次请求时都携带上 Token 令牌。

二、什么是 JWT ?

根据官网介绍:

JSON Web Token (JWT) 是一个开放标准,它定义了一种紧凑且自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。该信息可以被验证和信任,因为它是经过数字签名的。JWT 可以使用秘密(使用HMAC算法)或使用RSAECDSA的公钥/私钥对进行签名

简单来理解就是 JWT 就是一个JSON对象经过加密和签名的,可以在网络中安全的传输信息,并且可以被验证和信任。

2.1、什么时候应该使用 JWT ?

我目前用的最多的地方就是在授权方面,这也是 JWT 最常见的场景,其次还可以用来交换信息。

授权例子:

用户登录后,服务器端返回一个JWT,用户保存在本地,之后的每次请求都将包含JWT,服务器验证用户携带的JWT,来判断是否允许访问服务和资源。

另外,单点登录(SSO) 也是当今广泛使用JWT的一项功能,就是在A网站登录后,在B网站也能够实现自动登录,而不需要重复登录,如你在淘宝登录了,在身份没有过期前,你去看天猫网站,也会发现你已经登录了。

简而言之:用户只需要登录一次就可以访问所有相互信任的应用系统。并且能够轻松跨不同域使用,服务器也不需要存储session相关信息,减轻了负担

2.2、JWT 原理

其实 JWT 的原理就是,服务器认证以后,将一个 JSON 对象加密成一个紧凑的字符串(Token),发回给用户,就像下面这样。

// JSON 对象
{
  "姓名": "王五",
  "角色": "管理员",
  "到期时间": "2021年9月21日0点0分"
}
//加密后
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VybmFtZSIsIm5iZiI6MTYzMjI3NzU1NCwiaXNzIjoiY3J1c2giLCJleHAiOjE2MzIyNzc2NTQsImRlbW8iOiLlj6_lrZjlgqjkv6Hmga8iLCJpYXQiOjE2MzIyNzc1NTQsImRlbW8yIjoi5Y-v5a2Y5YKo5L-h5oGvMiJ9.OuqG5Ha_Ofmh5R9Et1vqLYSAlIO85oW9D9Jq9cKKYODO643ZLiDTyQs8dl3PLsZ-_5t0xv6kfKhCzCkCYznBNA

在认证之后,用户和服务器通信时,每次都会携带上这个Token。服务器端不再存储session信息,完全依靠用户携带的Token来判断用户身份。为了安全,服务器在生成Token的时候,都会加上一个数字签名。

这样做的优势:服务器不需要再保存 session数据,减轻了服务器负担,并且基于 JWT 认证机制的应用不需要去考虑用户在哪一台服务器登录,为应用的扩展提供了便利。

2.3、JWT 数据结构

JSON Web Tokens 由用点 ( .)分隔的三个部分组成,它们是:

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

因此,JWT 通常如下所示。注意:实际上是未分行的,这里是为了便于展示。

xxxxx.yyyyy.zzzzz
如:
eyJhbGciOiJIUzUxMiJ9. 
eyJzdWIiOiJ1c2VybmFtZSIsIm5iZiI6MTYzMjI3NzU1NCwiaXNzIjoiY3J1c2giLCJleHAiOjE2MzIyNzc2NTQsImRlbW8iOiLlj6_lrZjlgqjkv6Hmga8iLCJpYXQiOjE2MzIyNzc1NTQsImRlbW8yIjoi5Y-v5a2Y5YKo5L-h5oGvMiJ9.
OuqG5Ha_Ofmh5R9Et1vqLYSAlIO85oW9D9Jq9cKKYODO643ZLiDTyQs8dl3PLsZ-_5t0xv6kfKhCzCkCYznBNA

2.3.1、Header (标题)

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

{
"alg": "HS256",
"typ": "JWT"
}

上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT

2.3.2、Payload(有效载荷)

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题 jwt所面向的用户
  • aud (audience):受众 接收jwt的一方
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号,jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。

2.3.3、Signature(签名)

Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

HMACSHA256
(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

注意:签名用于验证消息在此过程中没有更改,并且在使用私钥签名的令牌的情况下,它还可以验证 JWT 的发送者是它所说的人。secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证的关键,所以,它就是我们服务端的私钥,在任何场景都不应该泄露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了,那么安全将不复存在。

2.3.4、 Base64URL

前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。

JWT 作为一个令牌(token),有些场合可能会 放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+/=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-/替换成_ 。这就是 Base64URL 算法。

2.4、JWT工具类

相关依赖:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

如果是Jdk11使用的话,可能会报这样的一个错误:

Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter
  at io.jsonwebtoken.impl.Base64Codec.decode(Base64Codec.java:26)
  at io.jsonwebtoken.impl.DefaultJwtBuilder.signWith(DefaultJwtBuilder.java:99)
  at com.crush.jwt.utils.JwtUtils.createJwt(JwtUtils.java:47)
  at com.crush.jwt.utils.JwtUtils.main(JwtUtils.java:127)
Caused by: java.lang.ClassNotFoundException: javax.xml.bind.DatatypeConverter
  at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
  at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
  at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
  ... 4 more

好像是因为Jdk11中没有这个类了,得加上下面这样的一个依赖:

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.0</version>
</dependency>

工具类

import io.jsonwebtoken.*;
import java.util.Date;
import java.util.HashMap;
/**
 * @Author: crush
 * @Date: 2021-09-21 22:18
 * version 1.0
 */
public class JwtUtils {
    /**
     * 服务器端密钥
     */
    private static final String SECRET = "jwtsecretdemo";
    /**
     * 颁发者
     */
    private static final String ISS = "crush";
    /**
     * 这里创建用到的时间、用户名、应该是传入进来的,
     * 登录时选择是否记住我,过期时间应当是不一致的。
     * @return
     */
    public static String createJwt() {
        HashMap<String, Object> map = new HashMap<>();
        map.put("demo", "可存储信息");
        map.put("demo2","可存储信息2");
        String jwt = Jwts.builder()
                .setClaims(map)
                // jwt所面向的用户
                .setSubject("username")
                //设置颁发者
                .setIssuer(ISS)
                // 定义在什么时间之前,该jwt都是不可用的.
                .setNotBefore(new Date())
                //签发时间
                .setIssuedAt(new Date())
                //设置 JWT 声明exp (到期)值
                .setExpiration(new Date(System.currentTimeMillis() + 100000))
                .signWith(SignatureAlgorithm.HS512, SECRET)
                //实际构建 JWT 并根据JWT 紧凑序列化 规则将其序列化为紧凑的、URL 安全的字符串。
                .compact();
        return jwt;
    }
    /**
     * 获取 Claims 实例
     * Claims :一个 JWT声明集 。
     *   这最终是一个 JSON 映射,可以向其中添加任何值,但为了方便起见,JWT 标准名称作为类型安全的 getter 和 setter 提供。
     *   因为这个接口扩展了Map&lt;String, Object&gt; , 如果您想添加自己的属性,只需使用 map 方法,
     *   例如:
     *      claims.put("someKey", "someValue");
     *
     * @param jwt
     * @return
     */
    public static Claims getBody(String jwt) {
        return Jwts.parser()
                .setSigningKey(SECRET)
                .parseClaimsJws(jwt)
                .getBody();
    }
    /**
     * 判断 JWT 是否已过期
     *
     * @param jwt
     * @return
     */
    public static boolean isExpiration(String jwt) {
        return getBody(jwt)
                //返回 JWT exp (到期)时间戳,如果不存在则返回null 。
                .getExpiration()
                //测试此日期是否在指定日期之前。
                .before(new Date());
    }
    /**
     * Subject:获取 jwt 所面向的用户
     *
     * @param jwt
     * @return
     */
    public static String getSubject(String jwt) {
        return getBody(jwt).getSubject();
    }
    /**
     * Issuer:获取颁发者
     *
     * @param jwt
     * @return
     */
    public static String getIssuer(String jwt) {
        return getBody(jwt).getIssuer();
    }
    /**
     * getClaimsValue
     *
     * @param jwt
     * @return
     */
    public static String getClaimsValue(String jwt) {
        return (String) getBody(jwt).get("demo");
    }
    /**
     * getClaimsValue
     *
     * @param jwt
     * @return
     */
    public static String getClaimsValue2(String jwt) {
        return (String) getBody(jwt).get("demo2");
    }
    public static void main(String[] args) {
        String jwt = createJwt();
        System.out.println(jwt);
        System.out.println("jwt 是否已经过期:"+isExpiration(jwt));
        System.out.println("Claims 中所存储信息:"+getBody(jwt).toString());
        System.out.println("jwt 所面向的用户:"+getSubject(jwt));
        System.out.println("jwt 颁发者:"+getIssuer(jwt));
        System.out.println("通过键值,取出我们自己放进 Jwt 中的信息:"+getClaimsValue(jwt));
        System.out.println("通过键值,取出我们自己放进 Jwt 中的信息2:"+getClaimsValue2(jwt));
    }
}

三、如何应用

此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

Authorization: Bearer <token>

一般是在请求头里加入Authorization,并加上Bearer标注:

fetch('api/user/1', {
  headers: {
    'Authorization': 'Bearer ' + token
  }
})

服务端会验证token,如果验证通过就会返回相应的资源。整个流程就是这样的:

1704461379110.jpg

3.1、实际使用

实际使用过程中,我们通常是结合着Security安全框架一起使用的,大家感兴趣的话,可以来一起看看我写的这篇文章。

SpringBoot整合Security安全框架、控制权限

也可以直接看源码:Security-Gitee

四、总结

4.1、优点:

  • 因为json的通用性,JWT支持多语言,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
  • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
  • 可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
  • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
  • 它不需要在服务端保存会话信息, 所以它易于应用的扩展

4.2、安全相关:

  • 保护好secret私钥,该私钥非常重要。如果密钥泄露,用户自己即可颁布JWT令牌,安全将不复存在。
  • 如果条件允许,JWT 不应该使用 HTTP 协议明码传输,而是要使用 HTTPS 协议传输。Https协议更安全。
  • JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

4.3、缺点:

  • JWT 的最大优点是不需要在服务端保存会话信息,最大的缺点也是如此,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效。

五、自言自语

本文就是简单介绍了,具体使用具体情况具体分析啦。

希望本篇文章能让你感到有所收获!!!

我们:待别日相见时,都已有所成

参考

jwt

JSON Web Token 入门教程

目录
相关文章
|
6月前
|
SQL Java 测试技术
在Spring boot中 使用JWT和过滤器实现登录认证
在Spring boot中 使用JWT和过滤器实现登录认证
333 0
|
4月前
|
JSON 安全 数据安全/隐私保护
Python认证新风尚:OAuth遇上JWT,安全界的时尚Icon👗
【10月更文挑战第2天】在当今互联网世界中,数据安全与隐私保护日益重要。Python 作为广泛应用于 Web 开发的语言,其认证机制也不断进化。OAuth 2.0 和 JSON Web Tokens (JWT) 成为当前最热门的安全认证方案,不仅保障数据安全传输,还简化了用户认证流程。本文将介绍 Python 如何结合 OAuth 2.0 和 JWT 打造安全高效的认证体系。
51 3
|
1月前
|
XML JavaScript Java
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
83 11
|
3月前
|
存储 JSON 安全
如何使用 JSON Web Tokens 进行身份验证?
总的来说,JWT 是一种强大而灵活的身份验证方式,通过正确使用和管理,可以为应用提供可靠的身份验证机制,同时提高系统的可扩展性和安全性。在实际应用中,需要根据具体的需求和场景,合理设计和实施 JWT 身份验证方案。
150 63
|
3月前
|
JSON 安全 算法
Spring Boot 应用如何实现 JWT 认证?
Spring Boot 应用如何实现 JWT 认证?
108 8
|
3月前
|
JSON 安全 数据安全/隐私保护
Python认证新风尚:OAuth遇上JWT,安全界的时尚Icon👗
在当今互联网世界中,数据安全和隐私保护至关重要。Python 作为 Web 开发的主流语言,其认证机制也在不断进步。OAuth 2.0 和 JSON Web Tokens (JWT) 是当前最热门的安全认证方案,不仅保障数据安全传输,还简化用户认证流程。本文介绍如何在 Python 中结合 OAuth 2.0 和 JWT,打造一套既安全又高效的认证体系。通过 Flask-HTTPAuth 和 PyJWT 等库,实现授权和验证功能,确保每次请求的安全性和便捷性。
60 3
|
3月前
|
JSON 算法 安全
JWT Bearer 认证在 .NET Core 中的应用
【10月更文挑战第30天】JWT(JSON Web Token)是一种开放标准,用于在各方之间安全传输信息。它由头部、载荷和签名三部分组成,用于在用户和服务器之间传递声明。JWT Bearer 认证是一种基于令牌的认证方式,客户端在请求头中包含 JWT 令牌,服务器验证令牌的有效性后授权用户访问资源。在 .NET Core 中,通过安装 `Microsoft.AspNetCore.Authentication.JwtBearer` 包并配置认证服务,可以实现 JWT Bearer 认证。具体步骤包括安装 NuGet 包、配置认证服务、启用认证中间件、生成 JWT 令牌以及在控制器中使用认证信息
175 2
|
5月前
|
安全 Java 数据安全/隐私保护
|
4月前
|
存储 JSON 前端开发
JSON与现代Web开发:数据交互的最佳选择
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也便于机器解析和生成。它以文本格式存储数据,常用于Web应用中的数据传输,尤其是在客户端和服务器之间。
203 0
|
5月前
|
JSON 安全 数据安全/隐私保护
Python 安全性大揭秘:OAuth 与 JWT,不只是认证,更是信任的传递
【9月更文挑战第4天】在数字化时代,确保应用安全至关重要。Python 作为广泛使用的编程语言,提供了强大的安全认证工具,如 OAuth 和 JWT。OAuth 是一种授权框架,允许第三方应用在有限权限下访问用户资源;JWT 则是一种自包含的数据传输格式,用于安全地传递声明。通过合理配置和使用这些技术,可以有效提升应用安全性,保障用户数据安全。正确管理和定期更新密钥、严格测试 JWT 的生成与验证等最佳实践,对于构建安全可靠的应用至关重要。不断学习新威胁,是维护应用安全的永恒课题。
68 2

热门文章

最新文章

  • 1
    Burp Suite Professional 2025.2 (macOS, Linux, Windows) - Web 应用安全、测试和扫描
    23
  • 2
    AppSpider Pro 7.5.015 for Windows - Web 应用程序安全测试
    19
  • 3
    【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
    53
  • 4
    部署使用 CHAT-NEXT-WEB 基于 Deepseek
    290
  • 5
    【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
    26
  • 6
    java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
    37
  • 7
    零基础构建开源项目OpenIM桌面应用和pc web- Electron篇
    27
  • 8
    【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
    20
  • 9
    FastAPI与Selenium:打造高效的Web数据抓取服务 —— 采集Pixabay中的图片及相关信息
    53
  • 10
    springSecurity学习之springSecurity过滤web请求
    59