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

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

QQ截图20220525200311.png


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 存放在服务器哪里,都会增加服务器的负担。这种方案优点就是简单,缺点就是扩展性不好,安全性较差,容易增加服务器的负担。


  1. 第二种解决方式其实就是 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,如果验证通过就会返回相应的资源。整个流程就是这样的:


微信截图_20220525200626.png


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 入门教程


目录
相关文章
|
3月前
|
XML JSON 前端开发
【Web前端揭秘】XML与JSON:数据界的双雄对决,你的选择将如何改写Web世界的未来?
【8月更文挑战第26天】本文深入探讨了XML和JSON这两种广泛使用的数据交换格式在Web前端开发中的应用。XML采用自定义标签描述数据结构,适用于复杂层次数据的表示,而JSON则以键值对形式呈现数据,更为轻量且易解析。通过对两种格式的示例代码、结构特点及应用场景的分析,本文旨在帮助读者更好地理解它们的差异,并根据实际需求选择最合适的数据交换格式。
61 1
|
6天前
|
存储 JSON 安全
如何使用 JSON Web Tokens 进行身份验证?
总的来说,JWT 是一种强大而灵活的身份验证方式,通过正确使用和管理,可以为应用提供可靠的身份验证机制,同时提高系统的可扩展性和安全性。在实际应用中,需要根据具体的需求和场景,合理设计和实施 JWT 身份验证方案。
26 8
|
2月前
|
JSON 算法 安全
Web安全-JWT认证机制安全性浅析
Web安全-JWT认证机制安全性浅析
30 2
|
3月前
|
Java Spring 容器
彻底改变你的编程人生!揭秘 Spring 框架依赖注入的神奇魔力,让你的代码瞬间焕然一新!
【8月更文挑战第31天】本文介绍 Spring 框架中的依赖注入(DI),一种降低代码耦合度的设计模式。通过 Spring 的 DI 容器,开发者可专注业务逻辑而非依赖管理。文中详细解释了 DI 的基本概念及其实现方式,如构造器注入、字段注入与 setter 方法注入,并提供示例说明如何在实际项目中应用这些技术。通过 Spring 的 @Configuration 和 @Bean 注解,可轻松定义与管理应用中的组件及其依赖关系,实现更简洁、易维护的代码结构。
52 0
|
3月前
|
JSON Java API
【Azure Developer】如何验证 Azure AD的JWT Token (JSON Web 令牌)?
【Azure Developer】如何验证 Azure AD的JWT Token (JSON Web 令牌)?
|
3月前
|
前端开发 JavaScript
【Azure 环境】前端Web通过Azure AD获取Token时发生跨域问题(CORS Error)
【Azure 环境】前端Web通过Azure AD获取Token时发生跨域问题(CORS Error)
|
3月前
|
API
【Azure API 管理】在 Azure API 管理中使用 OAuth 2.0 授权和 Azure AD 保护 Web API 后端,在请求中携带Token访问后报401的错误
【Azure API 管理】在 Azure API 管理中使用 OAuth 2.0 授权和 Azure AD 保护 Web API 后端,在请求中携带Token访问后报401的错误
|
3月前
|
JSON 数据格式
【应用服务 App Service】在Azure Web App的部署文件中,是否可以限制某些文件无法被访问?(如json)
【应用服务 App Service】在Azure Web App的部署文件中,是否可以限制某些文件无法被访问?(如json)
|
存储 JSON 安全
解锁互联网安全的新钥匙:JWT(JSON Web Token)
解锁互联网安全的新钥匙:JWT(JSON Web Token)
214 0
|
6月前
|
JSON 算法 数据安全/隐私保护
聊聊 JSON Web Token (JWT) 和 jwcrypto 的使用
本文介绍了 JSON Web Token (JWT) 和 Python 中的 `jwcrypto` 库。JWT 是一种用于安全传输信息的紧凑型令牌,常用于身份验证。它由 Header、Payload 和 Signature 三部分组成,具有紧凑性、自包含和安全性等特点。`jwcrypto` 库提供了 JWT 的生成、验证、加密、解密及签名功能。通过该库,可以使用 RSA 等算法创建和验证 JWT,同时管理密钥对。安装 `jwcrypto` 可用 `pip install jwcrypto`,并示例展示了如何生成签名 JWT 和密钥对。
聊聊 JSON Web Token (JWT) 和 jwcrypto 的使用
下一篇
无影云桌面