JWT 重点讲解

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: JWT 重点讲解

1. JWT 是什么

官网:JSON Web Tokens - jwt.io

JSON Web Token,简称 JWT,读音是 [d3pt] (jot 的发音),是一个基于 RFC 7519 的开放数据标准,它定义了一种宽松且紧凑的数据组合方式。其作用是 JWT 是一种加密后数据载体,可在各应用之间进行数据传输。

而传递的数据在“加密之前”,就是 JSON 字符串,自然也就可以传递“对象”

你也可以理解为,“一段 JSON 字符串”,浓缩后的数据

推荐学习视频:JWT_哔哩哔哩_bilibili

这是一个常见的登录逻辑,原本我们传统的 Cookie-Session 机制,用户登录成功后,之后携带 Cookie 中的 JSESSIONID 去访问服务器的会话记录,而 JWT 机制的话,则是登录成功后,返回一个 JWT 字符串,之后携带这个 JWT 就可以去访问其他接口了。

JWT 和 JSESSIONID 都可以说是访问令牌 Token,但 JSESSIONID 是访问会话记录,而 JWT 则本身就是数据!

但这也是对 JWT 的粗浅认知,接下来才是重头戏~

2. JWT 的组成

例子:

eyJhbGciOiJIUzI1NiJ9
.eyJqdGkiOiI0YWU1MDA2ZjAxNzU0NmM4ODE1NGNlNWZhMmQ4ODMwNCIsInN1YiI6IntcbiAgICBcImlkXCI6IDVcbn0iLCJpc3MiOiJzZyIsImlhdCI6MTcwODQxNzI4OSwiZXhwIjoxNzA4NTAzNjg5fQ
.EJXUxVDBcTHCBLrvrS_B40vgttlwZK2s1lB87FEjKNI

这就是一个 JWT

2.1 第一部分 HEADER

eyJhbGciOiJIUzI1NiJ9

是两个信息:

  1. alg:类型(JWT)
  2. typ:加密算法(HS256)

一般很固定就是这俩(除非设置为其他的)

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

使用 Base64 加密,构成 JWT 第一部分:HEADER

eyJhbGciOiJIUzI1NiJ9

2.2 第二部分 PAYLOAD

即载荷(也是 JSON 格式),有两种:

  1. 标准载荷(建议使用)

  1. 自定义载荷

可以是任何信息(一个键值对集合,Java 中就是个 Map,只不过这个 Map 要以某种特殊的形式存在于 JWT中),可以添加用户的相关消息等等~

可以理解为(标准载荷的一个属性是自定义载荷):

{
    "data": [
        {"k1": v1},
        {"k2": v2}
    ],
    "exp": 1777777777,
    "iss": "macaku"
    // ...
}

我的习惯是,将业务数据,转化为 JSON 作为 sub 的值,然后获取 sub 的值就行了~

  • 机制就在这,爱咋玩咋玩

但是这也与我的另一个习惯相关,就是 JWT 存的一般是 ID 之类的不带具体信息的数据,说是“SubObject”也没问题,这样我感觉用法可以跟灵活,也防止敏感信息泄露,之后总结再来看这个会更清楚!

使用 Base64 加密,构成 JWT 第二部分:PAYLOAD

eyJqdGkiOiI0YWU1MDA2ZjAxNzU0NmM4ODE1NGNlNWZhMmQ4ODMwNCIsInN1YiI6IntcbiAgICBcImlkXCI6IDVcbn0iLCJpc3MiOiJzZyIsImlhdCI6MTcwODQxNzI4OSwiZXhwIjoxNzA4NTAzNjg5fQ

2.3 第三部分 SIGNATURE

即签名,算法如下:

signature = HMACSHA256(header.payload, secret)

其中

  • HMACSHA256
  • 就是 HEADER 里的那个加密算法
  • header.payload 就是前两个部分
  • eyJhbGciOiJIUzI1NiJ9
    .eyJqdGkiOiI0YWU1MDA2ZjAxNzU0NmM4ODE1NGNlNWZhMmQ4ODMwNCIsInN1YiI6IntcbiAgICBcImlkXCI6IDVcbn0iLCJpc3MiOiJzZyIsImlhdCI6MTcwODQxNzI4OSwiZXhwIjoxNzA4NTAzNjg5fQ
  • secret
  • 是服务器必须自行保存好的密钥

有 secret 不代表,解密不了,因为主要数据是 payload,有没有 secret 压根就不影响信息暴露。

其作用就是配合 signature

  • 如果 header.payloadsecret 加密后得到 signature,则说明这个 JWT 是值得信赖的,是服务器颁布的,避免伪造 JWT~
  • ”是我颁布的“ 并且没有过期,则校验通过~

算出来的字符串用 Base 64 加密后构成第三部分:SIGNATURE

EJXUxVDBcTHCBLrvrS_B40vgttlwZK2s1lB87FEjKNI

3. JWT 在线生成与解析

JWT Token在线编码生成 - ToolTT在线工具箱

JWT Token在线解析解码 - ToolTT在线工具箱

  • 不到 secret 也能解析

{
    "data": [
        {
            "tooltt": "https://tooltt.com"
        },
        {
            "username": "mms"
        },
        {
            "gender": "boy"
        }
    ],
    "iat": 1708450751,
    "exp": 1709135999,
    "aud": "all",
    "iss": "mms",
    "sub": "{\"id\":1}"
}

4. JWT 的特点

4.1 无状态

JWT 一旦生成就是固定的,本身就是数据,并且无需在服务器存储

这让我想起《斗罗大陆》,“武魂殿会发给非武魂殿所属,但是被武魂殿认可的强者1块“教皇令”,见到此令就如同见到教皇亲临,拥有长老的权力,可见其效力之大。”

🤣🤣🤣

谁拿着 JWT ,只要这个 JWT 是我通过鉴定是我颁布的,那我就认定这个人。

这并不会在服务器保存什么”登录状态“之类的会话,来证明 JWT 是什么或者有效

  • 有缓存也只是为了提高效率罢了

”时效性“不代表状态,是 JWT 提供的机制罢了,其实照样能解析,但是这个的存在,给 JWT 提供了有效期的特点

因为是固定的,那么是一定能解析出来的,并且解析出来的东西也是固定的,而不是因为某个状态,不能用啦,比如 Cookie-Session 机制,要是服务器把会话删了,Cookie 连根毛都不能兑换。

当然,也可以”打破无状态性“,自定义业务场景咯,例如 JWT 黑名单等等…

  • 但是 JWT 仍然可以解析,只不过对令牌的校验逻辑不仅仅是 ”是我颁布的就行“

4.2 可自定义

JWT 的载荷是可以自定义的,灵活度提高

4.3 扩展性强

JWT 有一套标准规范,因此容易在不同平台和语言之间共享和解析

  • (例如如果第三方登录,可以把平台 A 的 JWT 给平台 B,解析后校验,用户则在平台 B 登录,生成平台 B 的 JWT)
  • 微服务架构,少不了多语言开发同个项目

4.4 调试性好

由于 JWT 的内容是以 Base64 编码后的字符串形式存在的,因此非常容易进行调试和分析(结构明了,解密简单)

4.5 安全性取决于密钥管理

密钥丢了,别人就可以伪造 JWT,威胁安全性

JWT 的安全性取决于密钥的管理。如果密钥被泄露或者被不当管理,那么 JWT 将会受到攻击。因此,在使用 JWT 时,一定要注意密钥的管理,包括生成、存储、更新、分发等等。

4.6 无法撤销

一旦生成,就是固定的字符串,要刷新也是换个新的,而不是让这个字符串转为另一个状态对应另一份数据!

4.7 需要缓存到客户端(需要传输给客户端)

JWT 包含用户信息和权限信息,一般需要在客户端缓存,要防止被窃取。

时间太久,很容易被别人拿到 JWT,从而干坏事,所以 JWT 引入时效性~

4.8 载荷大小有限制

由于 JWT 需要进行网络传输,对载荷大小有限制,不建议超过 1KB,会很影响性能

前面提到的 JWT 存 ID 之类的不带具体信息的数据,

载荷大小有限制 也是 一部分原因,

避免暴露敏感信息也是 一个,

无状态性和无法撤销 也是一个,如果用户信息有变动,JWT 的无状态性也代表这个消息可能更新不及时,且无法更新,只能刷新令牌了,还要让前端接下来请求换个 JWT,还不如存个不变的 ID,然后查服务器的缓存和数据库~

  • 没有打破无状态性,因为这里还是以”是我颁布的“为 JWT 的校验逻辑

5. JWT 的优缺点

5.1 优点

  1. 无状态:
  • JWT 本身不需要存储在服务器上,因此可以实现无状态的身份验证和授权
  1. 可扩展性
  • JWT 的载荷可以自定义,因此可以根据需求添加任意信息
  1. 可靠性:
  • JWT 使用数字签名来保证安全性,因此具有可靠性
  1. 跨平台性:
  • JWT 支持多种编程语言和操作系统,因此具有跨平台性
  1. 高效性
  • 由于 JWT 不需要查询数据库,因此具有高效性

5.2 缺点

  1. 安全性取决于密钥管理:
  • JWT 的安全性取决于密钥的管理,如果密钥被泄露或者被不当管理,那么 JWT 将会受到攻击。
  1. 无法撤销令牌:
  • 由于 JWT 是无状态的,一旦JT被签发,就无法撤销。
  1. 需要传输到客户端:
  • 由于 JWT 包含了用户信息和授权信息,因此 JWT 需要传输到客户端,这意味着 JWT 有被攻击者窃取的风险。
  1. 载荷大小有限制:
  • 由于 JWT 需要传输到客户端,因此载荷大小也有限制,差不多就十个左右的键值对。

6. JWT 实践

JSON Web Token Libraries - jwt.io

  • 大部分的业务逻辑去遵守 JWT 规范,用 JWT 官方提供的接口方便开发

找个库去用即可,我选择的是:

<!--jjwt-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.64</version>
</dependency>

6.1 一次性验证

用户注册成功后发一份激活邮件或者其他业务需要邮箱激活操作,都是可以使用 JWT。

原因:

  • JWT 时效性:
  • 让该链接具有时效性(比如约定2小时内激活)
  • JWT 不可篡改性(signature的保证):
  • 防止篡改以激活其他账户

6.2 JWT 令牌登录

就是前面说的,见到这个令牌就相当于见到用户~

也是JWT被诟病最多的地方,因为]WT令牌存在各种不安全。

  1. JWT令牌存储与客户端,容易泄露并被伪造身份搞破坏。
  2. JWT 被签发,就无法撤销,当破坏在进行时,后端无法马上禁止。

上面问题可通过监控异常 JWT 访问,设置黑名单+强制下线等方式尽量避免损失。

6.3 JWT 工具类

<dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.11</version>
        </dependency>

据实际进行代码调整~

/**
 * JWT工具类
 */
@Slf4j
public class JwtUtil {
    public static final String JWT_HEADER = "Token";
    //设置秘钥明文
    private static final String JWT_KEY = SpringUtil.getProperty("key.jwt");
    private static final String applicationName = SpringUtil.getProperty("spring.application.name");
    public static final Long JWT_TTL = 1L; // 一天有效期
    public static final Long JWT_MAP_TTL = 6L; // 六小时
    public static final TimeUnit JWT_TTL_UNIT = TimeUnit.DAYS;
    public  static final TimeUnit JWT_MAP_TTL_UNIT = TimeUnit.HOURS;
    public static final String JWT_LOGIN_WX_USER = "jwtLoginWxUser:";
    public static final String JWT_LOGIN_EMAIL_USER = "jwtLoginEmailUser:";
    public static final String JWT_RAW_DATA_MAP = "jwtRawDataMap:";
    public static String getUUID(){
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        return token;
    }
    /**
     * 生成jwt
     * @param subject token中要存放的数据(json格式)
     * @return
     */
    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID(), null);// 设置过期时间
        return builder.compact();
    }
 
    /**
     * 生成jwt
     * @param subject token中要存放的数据(json格式)
     * @param ttlMillis token超时时间
     * @return
     */
    public static String createJWT(String subject, Long ttlMillis, TimeUnit timeUnit) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID(), timeUnit);// 设置过期时间
        return builder.compact();
    }
 
    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid, TimeUnit timeUnit) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if (Objects.isNull(ttlMillis) || Objects.isNull(timeUnit)) { // 只有其中一个也等于没有
            ttlMillis = JWT_TTL_UNIT.toMillis(JwtUtil.JWT_TTL);
        } else {
            ttlMillis = timeUnit.toMillis(ttlMillis);
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
//                .setClaims() // 设置自定义载荷
                .setId(uuid)              //唯一的ID
                .setSubject(subject)   // 主题  可以是JSON数据
                .setIssuer(applicationName)     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                .setExpiration(expDate); // 失效时间
    }
 
    /**
     * 创建token
     * @param id
     * @param subject
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis, TimeUnit timeUnit) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id, timeUnit);// 设置过期时间
        return builder.compact();
    }
 
    /**
     * 生成加密后的秘钥 secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }
    /**
     * 解析
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody(); // 获得载荷
    }
    /**
     * 解析
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public static String parseJWTRawData(String jwt) {
        return parseJWT(jwt).getSubject();
    }
    public static <T> T getJwtKeyValue(String jwt, String key, Class<T> clazz) {
        return parseJWT(jwt).get(key, clazz);
    }
    public static Date getExpiredDate(String jwt) {
        Date result = null;
        try {
            result = parseJWT(jwt).getExpiration();
        } catch (ExpiredJwtException e) {
            result = e.getClaims().getExpiration();
        }
        return result;
    }
    public static String refreshToken(String jwt) {
        return createJWT(parseJWTRawData(jwt));
    }
    // 过期的话 parseJWT(jwt)会抛异常,也就是 “没抛异常能解析成功就是校验成功”,但是异常 ExpiredJwtException 的 getClaims 方法能获取到 payload
    public static boolean isTokenExpired(String jwt) {
        return getExpiredDate(jwt).before(new Date());
    }
    public static boolean validateToken(String jwt) {
        try {
            parseJWT(jwt);
        } catch (ExpiredJwtException e) {
            return false;
        }
        return true;
    }
}

7. JWT 避坑指南

JWT 无非就是泄露问题~

  • 毕竟它是无状态的,见到令牌就是见到用户

7.1 Redis 校验实现令牌泄露保护(Redis 处理泄露的 JWT )

因为 JWT 是无状态的,当 JWT 令牌颁发后,在有效时间内,是无法进行销毁,所以就存在很大隐患:令牌泄露

避坑:

  • 颁发 JWT 令牌时,在 Redis 中也缓存一份,当判定某个 JWT 泄露了,立即移除 Redis 中的 JWT
  • 当接口发起请求时,强制用户重新进行身份验证,直至验证成功。

7.2 临检 JWT 限制敏感操作

因为 JWT 是无状态的,泄露可能性大,进行一些敏感操作时需要进行临检操作。

避坑:

  • 强制重新检查用户身份,生成一个新的短时的 JWT 去进行敏感操作(当然,这个短时 JWT 与登录用户的 JWT 应该不同!并且登录用户的 JWT 不能用于进行敏感操作),判断操作者是用户本人

7.3 异常 JWT 监控判断:超频识别与限制

当 JWT 令牌被盗取,一般会出现高频次的系统访问。针对这种情况,监控用户端在单位时间内的请求次数,当单位时间内的请求次数超出预定阈值值,则判定该用户 JWT 令牌异常。

避坑:

  • 当判断 JWT 令牌异常,直接进行限制(比如:IP限流,JWT黑名单等)。

7.4 地域检查判断 JWT 泄露

一般用户活动范围是固定,意味着 JWT 客户端访问 IP 相对固定,JWT 泄露之后,可能会先异地登录的情况。

避坑:

  • 对 JWT 进行异地访问检查,有效时间内,IP 频繁变动或地域变动范围很大可判断为 JWT 泄露。

7.5 客户端检查判断 JWT 泄露

如果在很多设备上登录(请求携带不同设备机械码),很有可能,这个 JWT 泄露了

避坑:

  • 在 JWT 中保存生效的机器码可以是哪些(范围限制)
  • 在服务器保存这个 JWT 绑定的机械码(范围限制)
  • 限制 JWT 只能在 n 个不同设备上登录(次数限制)

7.6 JWT 令牌泄露预防:限时、限数、限频

JWT令牌泄露是无法避免,但是我们可以进行泄露识别,做好泄露后补救保证系统安全

避坑:

  • 对客户端进行合理限制,比如限制每个客户端的 JWT 令牌数量、访问频率、JWT令牌时效等,以降低 JWT令牌泄露的风险。
  • 减少JWT令牌时效也可以减少滥用时间~

8. JWT 常见面试题

8.1 什么是 JWT ?解释一下它的结构

JWT 是一种开放标准,用于在网络上安全地传输信息。它由三部分组成:头部、载荷和签名。头部包含令牌的元数据,载荷包含实际的信息(例如用户ID、角色等),签名用于验证令牌是否被篡改。

8.2 JWT 优点有哪些?它与传统的 session-based 身份验证相比有什么优缺点?

JWT 的优点包括无状态、可扩展、跨语言、易于实现和良好的安全性。相比之下,传统的 session-based 身份验证需要在服务端维护会话状态,使得服务端的负载更高,并且不适用于分布式系统

JWT 还不会出现 CSRF 漏洞

8.3 在 JWT 的结构中,分别有哪些部分?每个部分的作用是什么?

JWT 的结构由三部分组成:头部、载荷和签名。头部包含令牌类型和算法,载荷包含实际的信息,签名由头部、载荷和密钥生成。

8.4 JWT 如何工作?从开始到验证过程的完整流程是怎样的?

JWT的工作流程分为三个步骤:生成令牌、发送令牌、验证令牌。在生成令牌时,服务端使用密钥对头部和载荷进行签名。在发送令牌时,将令牌发送给客户端。在验证令牌时,客户端从令牌中解析出头部和载荷,并使用相同的密钥验证签名。

可能包含第四个步骤:刷新令牌,当发觉令牌快过期了,生成新的令牌,发送新的令牌给客户端,之后客户端发的是新的令牌,验证令牌(由于无状态性,旧令牌依旧有效,除非自定义业务使其有了状态)

8.5 什么是 JWT 的签名?为什么需要对 JWT 进行签名?如何验证 JWT 的签名?

JWT 的签名是由头部、载荷和密钥生成的,用于验证令牌是否被篡改。签名使用HMAC算法或RSA算法生成。在验证JWT的签名时,客户端使用相同的密钥和算法生成签名,并将生成的签名与令牌中的签名进行比较。

8.6 什么是 JWT 的令牌刷新?为什么需要这个功能?

令牌刷新是一种机制,用于解决 JWT 过期后需要重新登录的问题。在令牌刷新中,服务端生成新的 JWT,并将其发送给客户端。客户端使用新的 JWT 替换旧的 JWT,从而延长令牌的有效期。

8.7 JWT 是否加密?如果是,加密的部分是哪些?如果不是,那么它如何保证数据安全性?

JWT 本身并不加密(虽然说 Base64 加密,但那只不过是浓缩罢了,一点都不秘密),而是使用签名来保证数据的完整性和认证信息的可靠性(通过 header 提供的加密算法,计算 signature 就涉及加密)

但可以在载荷中包含敏感信息,为了保护这些信息,可以使用 JWE (SON Web Encryption)对载荷进行加密。如果不加密,则需要在生成 JWT 时确保不在载荷中包含敏感信息。

8.8 在 JWT 中,如何处理 Token 过期的问题?有哪些方法可以处理?

JWT 过期后,客户端需要重新获取新的 JWT 。(过期也能获得payload的,通过抛出的异常 ExpiredJwtException 的 getClaims 方法能获取到 payload)

自定义refresh token机制来解决快要过期的问题。

8.9 JWT 和 OAuth2.0 有什么关系?它们之间有什么区别?

JWT 和 OAuth2.0 都是用于身份验证和授权的开放标准。JWT 是一种身份验证机制,而 OAuth2.0 是一种授权机制。JWT 用于在不同的系统中安全地传输信息,OAuth2.0 用于授权第三方应用程序访问受保护的资源。

8.10 JWT 在什么场景下使用较为合适?它的局限性是什么?

JWT 在单体应用或微服务架构中的使用比较合适。它的局限性包括无法撤销、令牌较大、无法处理并发等问题。

性和认证信息的可靠性(通过 header 提供的加密算法,计算 signature 就涉及加密)

但可以在载荷中包含敏感信息,为了保护这些信息,可以使用 JWE (SON Web Encryption)对载荷进行加密。如果不加密,则需要在生成 JWT 时确保不在载荷中包含敏感信息。

8.8 在 JWT 中,如何处理 Token 过期的问题?有哪些方法可以处理?

JWT 过期后,客户端需要重新获取新的 JWT 。(过期也能获得payload的,通过抛出的异常 ExpiredJwtException 的 getClaims 方法能获取到 payload)

自定义refresh token机制来解决快要过期的问题。

8.9 JWT 和 OAuth2.0 有什么关系?它们之间有什么区别?

JWT 和 OAuth2.0 都是用于身份验证和授权的开放标准。JWT 是一种身份验证机制,而 OAuth2.0 是一种授权机制。JWT 用于在不同的系统中安全地传输信息,OAuth2.0 用于授权第三方应用程序访问受保护的资源。

8.10 JWT 在什么场景下使用较为合适?它的局限性是什么?

JWT 在单体应用或微服务架构中的使用比较合适。它的局限性包括无法撤销、令牌较大、无法处理并发等问题。

适合去做“一次性验证”与“登录令牌”

目录
相关文章
|
7月前
|
JSON 安全 Java
JWT的原理及实际使用
JWT的原理及实际使用
72 0
|
10天前
|
存储 JSON 算法
基于M实现的JWT解决方案
基于M实现的JWT解决方案
18 0
|
8月前
|
存储 JSON 算法
JWT的原理及实际应用
JWT的原理及实际应用
97 1
|
8月前
|
存储 JSON 前端开发
了解什么是JWT的原理及实际应用
了解什么是JWT的原理及实际应用
1210 0
|
7月前
|
存储 JSON 算法
关于一篇什么是JWT的原理与实际应用
关于一篇什么是JWT的原理与实际应用
64 0
|
10月前
|
XML JSON 安全
认证姿势-Jwt-基础篇
认证姿势-Jwt-基础篇
79 0
|
11月前
|
安全 NoSQL Redis
oatuth2.0 + JWT 案例
上一节,我们讲述了Oauth2.0 配合security的使用,配合redis去存token,或者配合Jwt去存token。这篇文章,我们主要来系统的串起来讲一下,从宏观的层面来讲述一下单点登录,并且来实现一个demo。
|
12月前
|
存储 JSON 算法
JWT的原理
JWT的原理
122 0
|
JSON 算法 数据安全/隐私保护
JWT---JWT基础知识点
JWT---JWT基础知识点
JWT---JWT基础知识点
|
存储 JSON NoSQL
浅析单点登录(重点讲解OAuth2+JWT)
浅析单点登录(重点讲解OAuth2+JWT)
541 0
浅析单点登录(重点讲解OAuth2+JWT)

热门文章

最新文章