什么是JWT?深入理解JWT从原理到应用

这篇具有很好参考价值的文章主要介绍了什么是JWT?深入理解JWT从原理到应用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

🎉🎉欢迎来到我的CSDN主页!🎉🎉

🏅我是Java方文山,一个在CSDN分享笔记的博主。📚📚

🌟推荐给大家我的专栏《ELement》。🎯🎯

👉点击这里,就可以查看我的主页啦!👇👇

Java方文山的个人主页

🎁如果感觉还不错的话请给我点赞吧!🎁🎁

💖期待你的加入,一起学习,一起进步!💖💖

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

目录

一、前言

1.什么是JWT

2.为什么要用JWT

2.1 传统Session认证的弊端

2.2 JWT认证的优势

二、JWT结构

2.1.Header

2.2.Payload

2.3.Signature

三、JWT的种类

3.1.nonsecure JWT

3.2.JWS

四、Java中使用JWT

4.1.java-jwt

4.1.1 对称签名

4.1.2 非对称签名

4.2.jjwt-root

4.2.1 对称签名

4.2.2 非对称签名

实际开发中的应用


一、前言

1.什么是JWT

在介绍JWT之前,我们先来回顾一下利用token进行用户身份验证的流程:

  1. 客户端使用用户名和密码请求登录
  2. 服务端收到请求,验证用户名和密码
  3. 验证成功后,服务端会签发一个token,再把这个token返回给客户端
  4. 客户端收到token后可以把它存储起来,比如放到cookie中
  5. 客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带
  6. 服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据

这种基于token的认证方式相比传统的session认证方式更节约服务器资源,并且对移动端和分布式更加友好。其优点如下:

  • 支持跨域访问cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题
  • 无状态token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力
  • 更适用CDN:可以通过内容分发网络请求服务端的所有资料
  • 更适用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会简单很多
  • 无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御

JWT就是上述流程当中token的一种具体实现方式,其全称是JSON Web Token,官网地址:https://jwt.io/

通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。JWT的认证流程如下:

  1. 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探
  2. 后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串
  3. 后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可
  4. 前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)
  5. 后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等
  6. 验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

2.为什么要用JWT

2.1 传统Session认证的弊端

我们知道HTTP本身是一种无状态的协议,这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,认证通过后HTTP协议不会记录下认证后的状态,那么下一次请求时,用户还要再一次进行认证,因为根据HTTP协议,我们并不知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在用户首次登录成功后,在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这是传统的基于session认证的过程

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

然而,传统的session认证有如下的问题:

  • 每个用户的登录信息都会保存到服务器的session中,随着用户的增多,服务器开销会明显增大
  • 由于session是存在与服务器的物理内存中,所以在分布式系统中,这种方式将会失效。虽然可以将session统一保存到Redis中,但是这样做无疑增加了系统的复杂性,对于不需要redis的应用也会白白多引入一个缓存中间件
  • 对于非浏览器的客户端、手机移动端等不适用,因为session依赖于cookie,而移动端经常没有cookie
  • 因为session认证本质基于cookie,所以如果cookie被截获,用户很容易收到跨站请求伪造攻击。并且如果浏览器禁用了cookie,这种方式也会失效
  • 前后端分离系统中更加不适用,后端部署复杂,前端发送的请求往往经过多个中间件到达后端,cookie中关于session的信息会转发多次
  • 由于基于Cookie,而cookie无法跨域,所以session的认证也无法跨域,对单点登录不适用

2.2 JWT认证的优势

对比传统的session认证方式,JWT的优势是:

  • 简洁:JWT Token数据量小,传输速度也很快
  • 因为JWT Token是以JSON加密形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持
  • 不需要在服务端保存会话信息,也就是说不依赖于cookie和session,所以没有了传统session认证的弊端,特别适用于分布式微服务
  • 单点登录友好:使用Session进行身份认证的话,由于cookie无法跨域,难以实现单点登录。但是,使用token进行认证的话, token可以被保存在客户端的任意位置的内存中,不一定是cookie,所以不依赖cookie,不会存在这些问题
  • 适合移动端应用:使用Session进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到Cookie(需要 Cookie 保存 SessionId),所以不适合移动端

因为这些优势,目前无论单体应用还是分布式应用,都更加推荐用JWT token的方式进行用户认证

二、JWT结构

JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串
J W T S t r i n g = B a s e 64 ( H e a d e r ) . B a s e 64 ( P a y l o a d ) . H M A C S H A 256 ( b a s e 64 U r l E n c o d e ( h e a d e r ) + " . " + b a s e 64 U r l E n c o d e ( p a y l o a d ) , s e c r e t ) JWTString = Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)JWTString=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

2.1.Header

JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存

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

2.2.Payload

有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择

iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT

这些预定义的字段并不要求强制使用。除以上默认字段外,我们还可以自定义私有字段,一般会把包含用户信息的数据放到payload中,如下例:

{
  "sub": "1234567890",
  "name": "Helen",
  "admin": true
}

请注意,默认情况下JWT是未加密的,因为只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些非敏感的信息

2.3.Signature

签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名
H M A C S H A 256 ( b a s e 64 U r l E n c o d e ( h e a d e r ) + " . " + b a s e 64 U r l E n c o d e ( p a y l o a d ) , s e c r e t ) HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用.分隔,就构成整个JWT对象

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

注意JWT每部分的作用,在服务端接收到客户端发送过来的JWT token之后:

  • headerpayload可以直接利用base64解码出原文,从header中获取哈希签名的算法,从payload中获取有效数据
  • signature由于使用了不可逆的加密算法,无法解码出原文,它的作用是校验token有没有被篡改。服务端获取header中的加密算法之后,利用该算法加上secretKeyheaderpayload进行加密,比对加密后的数据和客户端发送过来的是否一致。注意secretKey只能保存在服务端,而且对于不同的加密算法其含义有所不同,一般对于MD5类型的摘要加密算法,secretKey实际上代表的是盐值

三、JWT的种类

其实JWT(JSON Web Token)指的是一种规范,这种规范允许我们使用JWT在两个组织之间传递安全可靠的信息,JWT的具体实现可以分为以下几种:

  • nonsecure JWT:未经过签名,不安全的JWT
  • JWS:经过签名的JWT
  • JWEpayload部分经过加密的JWT

3.1.nonsecure JWT

未经过签名,不安全的JWT。其header部分没有指定签名算法

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

并且也没有Signature部分

3.2.JWS

JWS ,也就是JWT Signature,其结构就是在之前nonsecure JWT的基础上,在头部声明签名算法,并在最后添加上签名。创建签名,是保证jwt不能被他人随意篡改。我们通常使用的JWT一般都是JWS

为了完成签名,除了用到header信息和payload信息外,还需要算法的密钥,也就是secretKey。加密的算法一般有2类:

  • 对称加密:secretKey指加密密钥,可以生成签名与验签
  • 非对称加密:secretKey指私钥,只用来生成签名,不能用来验签(验签用的是公钥)

JWT的密钥或者密钥对,一般统一称为JSON Web Key,也就是JWK

到目前为止,jwt的签名算法有三种:

  • HMAC【哈希消息验证码(对称)】:HS256/HS384/HS512
  • RSASSA【RSA签名算法(非对称)】(RS256/RS384/RS512)
  • ECDSA【椭圆曲线数据签名算法(非对称)】(ES256/ES384/ES512)

四、Java中使用JWT

官网推荐了6个Java使用JWT的开源库,其中比较推荐使用的是java-jwtjjwt-root

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

4.1.java-jwt

4.1.1 对称签名

首先引入依赖

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.3</version>
</dependency>

生成JWT的token

public class JWTTest {
    @Test
    public void testGenerateToken(){
        // 指定token过期时间为10秒
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND, 10);

        String token = JWT.create()
                .withHeader(new HashMap<>())  // Header
                .withClaim("userId", 21)  // Payload
                .withClaim("userName", "baobao")
                .withExpiresAt(calendar.getTime())  // 过期时间
                .sign(Algorithm.HMAC256("!34ADAS"));  // 签名用的secret

        System.out.println(token);
    }
}

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

注意多次运行方法生成的token字符串内容是不一样的,尽管我们的payload信息没有变动。因为JWT中携带了超时时间,所以每次生成的token会不一样,我们利用base64解密工具可以发现payload确实携带了超时时间

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

解析JWT字符串

@Test
public void testResolveToken(){
    // 创建解析对象,使用的算法和secret要与创建token时保持一致
    JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("!34ADAS")).build();
    // 解析指定的token
    DecodedJWT decodedJWT = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6ImJhb2JhbyIsImV4cCI6MTU5OTkyMjUyOCwidXNlcklkIjoyMX0.YhA3kh9KZOAb7om1C7o3vBhYp0f61mhQWWOoCrrhqvo");
    // 获取解析后的token中的payload信息
    Claim userId = decodedJWT.getClaim("userId");
    Claim userName = decodedJWT.getClaim("userName");
    System.out.println(userId.asInt());
    System.out.println(userName.asString());
    // 输出超时时间
    System.out.println(decodedJWT.getExpiresAt());
}

运行后发现报异常,原因是之前生成的token已经过期

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

再运行一次生成token的方法,然后在过期时间10秒之内将生成的字符串拷贝到解析方法中,运行解析方法即可成功

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

我们可以将上述方法封装成工具类

public class JWTUtils {
    // 签名密钥
    private static final String SECRET = "!DAR$";

    /**
     * 生成token
     * @param payload token携带的信息
     * @return token字符串
     */
    public static String getToken(Map<String,String> payload){
        // 指定token过期时间为7天
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 7);

        JWTCreator.Builder builder = JWT.create();
        // 构建payload
        payload.forEach((k,v) -> builder.withClaim(k,v));
        // 指定过期时间和签名算法
        String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(SECRET));
        return token;
    }


    /**
     * 解析token
     * @param token token字符串
     * @return 解析后的token
     */
    public static DecodedJWT decode(String token){
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
        DecodedJWT decodedJWT = jwtVerifier.verify(token);
        return decodedJWT;
    }
}

4.1.2 非对称签名

生成jwt串的时候需要指定私钥,解析jwt串的时候需要指定公钥

private static final String RSA_PRIVATE_KEY = "...";
private static final String RSA_PUBLIC_KEY = "...";

/**
     * 生成token
     * @param payload token携带的信息
     * @return token字符串
     */
public static String getTokenRsa(Map<String,String> payload){
    // 指定token过期时间为7天
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.DATE, 7);

    JWTCreator.Builder builder = JWT.create();
    // 构建payload
    payload.forEach((k,v) -> builder.withClaim(k,v));

    // 利用hutool创建RSA
    RSA rsa = new RSA(RSA_PRIVATE_KEY, null);
    // 获取私钥
    RSAPrivateKey privateKey = (RSAPrivateKey) rsa.getPrivateKey();
    // 签名时传入私钥
    String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.RSA256(null, privateKey));
    return token;
}

/**
     * 解析token
     * @param token token字符串
     * @return 解析后的token
     */
public static DecodedJWT decodeRsa(String token){
    // 利用hutool创建RSA
    RSA rsa = new RSA(null, RSA_PUBLIC_KEY);
    // 获取RSA公钥
    RSAPublicKey publicKey = (RSAPublicKey) rsa.getPublicKey();
    // 验签时传入公钥
    JWTVerifier jwtVerifier = JWT.require(Algorithm.RSA256(publicKey, null)).build();
    DecodedJWT decodedJWT = jwtVerifier.verify(token);
    return decodedJWT;
}

4.2.jjwt-root

4.2.1 对称签名

引入依赖

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

使用方法类似,可参考下列代码

public class JwtUtils {
    // token时效:24小时
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    // 签名哈希的密钥,对于不同的加密算法来说含义不同
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

    /**
     * 根据用户id和昵称生成token
     * @param id  用户id
     * @param nickname 用户昵称
     * @return JWT规则生成的token
     */
    public static String getJwtToken(String id, String nickname){
        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setSubject("baobao-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .claim("id", id)
                .claim("nickname", nickname)
            	// HS256算法实际上就是MD5加盐值,此时APP_SECRET就代表盐值
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken token字符串
     * @return 如果token有效返回true,否则返回false
     */
    public static boolean checkToken(String jwtToken) {
        if(StringUtils.isEmpty(jwtToken)) return false;
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     * @param request Http请求对象
     * @return 如果token有效返回true,否则返回false
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            // 从http请求头中获取token字符串
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据token获取会员id
     * @param request Http请求对象
     * @return 解析token后获得的用户id
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if(StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
}

注意:

  • jjwt在0.10版本以后发生了较大变化,pom依赖要引入多个

    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.2</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.2</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
        <version>0.11.2</version>
        <scope>runtime</scope>
    </dependency>
    
  • 标准规范中对各种加密算法的secretKey的长度有如下要求:

    • HS256:要求至少 256 bits (32 bytes)
    • HS384:要求至少384 bits (48 bytes)
    • HS512:要求至少512 bits (64 bytes)
    • RS256 and PS256:至少2048 bits
    • RS384 and PS384:至少3072 bits
    • RS512 and PS512:至少4096 bits
    • ES256:至少256 bits (32 bytes)
    • ES384:至少384 bits (48 bytes)
    • ES512:至少512 bits (64 bytes)

    在jjwt0.10版本之前,没有强制要求,secretKey长度不满足要求时也可以签名成功。但是0.10版本后强制要求secretKey满足规范中的长度要求,否则生成jws时会抛出异常

    什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

  • 新版本的jjwt中,之前的签名和验签方法都是传入密钥的字符串,已经过时。最新的方法需要传入Key对象

public class JwtUtils {
    // token时效:24小时
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    // 签名哈希的密钥,对于不同的加密算法来说含义不同
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHOsdadasdasfdssfeweee";

    /**
     * 根据用户id和昵称生成token
     * @param id  用户id
     * @param nickname 用户昵称
     * @return JWT规则生成的token
     */
    public static String getJwtToken(String id, String nickname){
        String JwtToken = Jwts.builder()
                .setSubject("baobao-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .claim("id", id)
                .claim("nickname", nickname)
                // 传入Key对象
                .signWith(Keys.hmacShaKeyFor(APP_SECRET.getBytes(StandardCharsets.UTF_8)), SignatureAlgorithm.HS256)
                .compact();
        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken token字符串
     * @return 如果token有效返回true,否则返回false
     */
    public static Jws<Claims> decode(String jwtToken) {
        // 传入Key对象
        Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(Keys.hmacShaKeyFor(APP_SECRET.getBytes(StandardCharsets.UTF_8))).build().parseClaimsJws(jwtToken);
        return claimsJws;
    }
}

4.2.2 非对称签名

生成jwt串的时候需要指定私钥,解析jwt串的时候需要指定公钥

private static final String RSA_PRIVATE_KEY = "...";
private static final String RSA_PUBLIC_KEY = "...";

/**
     * 根据用户id和昵称生成token
     * @param id  用户id
     * @param nickname 用户昵称
     * @return JWT规则生成的token
     */
public static String getJwtTokenRsa(String id, String nickname){
    // 利用hutool创建RSA
    RSA rsa = new RSA(RSA_PRIVATE_KEY, null);
    RSAPrivateKey privateKey = (RSAPrivateKey) rsa.getPrivateKey();
    String JwtToken = Jwts.builder()
        .setSubject("baobao-user")
        .setIssuedAt(new Date())
        .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
        .claim("id", id)
        .claim("nickname", nickname)
        // 签名指定私钥
        .signWith(privateKey, SignatureAlgorithm.RS256)
        .compact();
    return JwtToken;
}

/**
     * 判断token是否存在与有效
     * @param jwtToken token字符串
     * @return 如果token有效返回true,否则返回false
     */
public static Jws<Claims> decodeRsa(String jwtToken) {
    RSA rsa = new RSA(null, RSA_PUBLIC_KEY);
    RSAPublicKey publicKey = (RSAPublicKey) rsa.getPublicKey();
    // 验签指定公钥
    Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(jwtToken);
    return claimsJws;
}

五、jwt工具类的使用

 JwtUtils

package com.zking.ssm.jwt;

import java.util.Date;
import java.util.Map;
import java.util.UUID;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

/**
 * JWT验证过滤器:配置顺序 CorsFilte->JwtUtilsr-->StrutsPrepareAndExecuteFilter
 *
 */
public class JwtUtils {
	/**
	 * JWT_WEB_TTL:WEBAPP应用中token的有效时间,默认30分钟
	 */
	public static final long JWT_WEB_TTL = 30 * 60 * 1000;

	/**
	 * 将jwt令牌保存到header中的key
	 */
	public static final String JWT_HEADER_KEY = "jwt";

	// 指定签名的时候使用的签名算法,也就是header那部分,jwt已经将这部分内容封装好了。
	private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
	private static final String JWT_SECRET = "f356cdce935c42328ad2001d7e9552a3";// JWT密匙
	private static final SecretKey JWT_KEY;// 使用JWT密匙生成的加密key

	static {
		byte[] encodedKey = Base64.decodeBase64(JWT_SECRET);
		JWT_KEY = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
	}

	private JwtUtils() {
	}

	/**
	 * 解密jwt,获得所有声明(包括标准和私有声明)
	 * 
	 * @param jwt
	 * @return
	 * @throws Exception
	 */
	public static Claims parseJwt(String jwt) {
		Claims claims = Jwts.parser()
				.setSigningKey(JWT_KEY)
				.parseClaimsJws(jwt)
				.getBody();
		return claims;
	}

	/**
	 * 创建JWT令牌,签发时间为当前时间
	 * 
	 * @param claims
	 *            创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
	 * @param ttlMillis
	 *            JWT的有效时间(单位毫秒),当前时间+有效时间=过期时间
	 * @return jwt令牌
	 */
	public static String createJwt(Map<String, Object> claims, 
			long ttlMillis) {
		// 生成JWT的时间,即签发时间 2021-10-30 10:02:00 -> 30 10:32:00
	
		long nowMillis = System.currentTimeMillis();

		
		//链式语法:
		// 下面就是在为payload添加各种标准声明和私有声明了
		// 这里其实就是new一个JwtBuilder,设置jwt的body
		JwtBuilder builder = Jwts.builder()
				// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
				.setClaims(claims)
				// 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
				// 可以在未登陆前作为身份标识使用
				.setId(UUID.randomUUID().toString().replace("-", ""))
				// iss(Issuser)签发者,写死
				.setIssuer("xw")
				// iat: jwt的签发时间
				.setIssuedAt(new Date(nowMillis))
				// 代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可放数据{"uid":"zs"}。此处没放
				// .setSubject("{}")
				// 设置签名使用的签名算法和签名使用的秘钥
				.signWith(SIGNATURE_ALGORITHM, JWT_KEY)
				// 设置JWT的过期时间
				.setExpiration(new Date(nowMillis + ttlMillis));

		return builder.compact();
	}

	/**
	 * 复制jwt,并重新设置签发时间(为当前时间)和失效时间
	 * 
	 * @param jwt
	 *            被复制的jwt令牌
	 * @param ttlMillis
	 *            jwt的有效时间(单位毫秒),当前时间+有效时间=过期时间
	 * @return
	 */
	public static String copyJwt(String jwt, Long ttlMillis) {
		//解密JWT,获取所有的声明(私有和标准)
		//old
		Claims claims = parseJwt(jwt);

		// 生成JWT的时间,即签发时间
		long nowMillis = System.currentTimeMillis();

		// 下面就是在为payload添加各种标准声明和私有声明了
		// 这里其实就是new一个JwtBuilder,设置jwt的body
		JwtBuilder builder = Jwts.builder()
				// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
				.setClaims(claims)
				// 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
				// 可以在未登陆前作为身份标识使用
				//.setId(UUID.randomUUID().toString().replace("-", ""))
				// iss(Issuser)签发者,写死
				// .setIssuer("csdn")
				// iat: jwt的签发时间
				.setIssuedAt(new Date(nowMillis))
				// 代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可放数据{"uid":"zs"}。此处没放
				// .setSubject("{}")
				// 设置签名使用的签名算法和签名使用的秘钥
				.signWith(SIGNATURE_ALGORITHM, JWT_KEY)
				// 设置JWT的过期时间
				.setExpiration(new Date(nowMillis + ttlMillis));
		return builder.compact();
	}
}




该工具类提供了以下几个方法:

   parseJwt(String jwt):解密JWT,获得所有声明。

  •         参数:jwt - 要解密的JWT令牌。
  •         返回值:包含所有声明的Claims对象。

 createJwt(Map<String, Object> claims, long ttlMillis):创建JWT令牌,签发时间为当前时间。

  •         参数:claims - 创建payload的私有声明。 ttlMillis - JWT的有效时间(单位毫秒)。
  •         返回值:生成的JWT令牌。

 copyJwt(String jwt, Long ttlMillis):复制JWT,并重新设置签发时间和失效时间。

  •         参数:jwt - 被复制的JWT令牌。 ttlMillis - JWT的有效时间(单位毫秒)。
  •         返回值:生成的新的JWT令牌。

 下面我们对这个工具类进行测试。

5.1 生成JWT

public void test1() {// 生成JWT
		
		//JWT Token=Header.Payload.Signature
		//头部.载荷.签名
		//Payload=标准声明+私有声明+公有声明
		
		//定义私有声明
		Map<String, Object> claims = new HashMap<String, Object>();
		claims.put("username", "zss");
		claims.put("age", 18);
		
	    //TTL:Time To Live
		String jwt = JwtUtils.createJwt(claims, JwtUtils.JWT_WEB_TTL);
		System.out.println(jwt);

		//获取Payload(包含标准和私有声明)
		Claims parseJwt = JwtUtils.parseJwt(jwt);
		for (Map.Entry<String, Object> entry : parseJwt.entrySet()) {
			System.out.println(entry.getKey() + "=" + entry.getValue());
		}
		Date d1 = parseJwt.getIssuedAt();
		Date d2 = parseJwt.getExpiration();
		System.out.println("令牌签发时间:" + sdf.format(d1));
		System.out.println("令牌过期时间:" + sdf.format(d2));
	}

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

用于生成JWT。在该方法中,定义了私有声明(claims),并设置了一些键值对,例如用户名和年龄。然后调用了JwtUtils.createJwt()方法生成JWT,并打印输出。

5.2 解析JWT

public void test2() {// 解析oldJwt
		String newJwt="eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ4dyIsImV4cCI6MTY5NzExNjYyMSwiaWF0IjoxNjk3MTE0ODIxLCJhZ2UiOjE4LCJqdGkiOiI2NGUxZGYzMzRmYjI0ZjJkOWQ4MGVkMGUwYjBiYThiNSIsInVzZXJuYW1lIjoienNzIn0.2QpKTmydrVHjUfwTHP-iewiKcszEx6PlIJp4k5EDJNU" ;
		String oldJwt = "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ4dyIsImV4cCI6MTY5NzExNjYyMSwiaWF0IjoxNjk3MTE0ODIxLCJhZ2UiOjE4LCJqdGkiOiI2NGUxZGYzMzRmYjI0ZjJkOWQ4MGVkMGUwYjBiYThiNSIsInVzZXJuYW1lIjoienNzIn0.2QpKTmydrVHjUfwTHP-iewiKcszEx6PlIJp4k5EDJNU" ;
		Claims parseJwt = JwtUtils.parseJwt(newJwt);
		for (Map.Entry<String, Object> entry : parseJwt.entrySet()) {
			System.out.println(entry.getKey() + "=" + entry.getValue());
		}
		Date d1 = parseJwt.getIssuedAt();
		Date d2 = parseJwt.getExpiration();
		System.out.println("令牌签发时间:" + sdf.format(d1));
		System.out.println("令牌过期时间:" + sdf.format(d2));
	}

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

用于解析JWT。在该方法中,使用JwtUtils.parseJwt()方法解析了一个旧的JWT,并将解析结果打印输出。

5.3 复制JWT并延时30分钟

public void test3() {// 复制jwt,并延时30分钟
		String oldJwt = "\t\teyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ4dyIsImV4cCI6MTY5NzExNjYyMSwiaWF0IjoxNjk3MTE0ODIxLCJhZ2UiOjE4LCJqdGkiOiI2NGUxZGYzMzRmYjI0ZjJkOWQ4MGVkMGUwYjBiYThiNSIsInVzZXJuYW1lIjoienNzIn0.2QpKTmydrVHjUfwTHP-iewiKcszEx6PlIJp4k5EDJNU";
		//String newJwt = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDU3NTM2NTUsImlhdCI6MTYwNTc1MTg1NSwiYWdlIjoxOCwianRpIjoiYmNmN2Q1MzQ2YjE3NGU2MDk1MmIxYzQ3ZTlmMzQyZjgiLCJ1c2VybmFtZSI6InpzcyJ9.m1Qn84RxgbKCnsvrdbbAnj8l_5Jwovry8En0j4kCxhc";
		//String oldJwt = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NjI5MDMzNjAsImlhdCI6MTU2MjkwMTU2MCwiYWdlIjoxOCwianRpIjoiZDVjMzE4Njg0MDcyNDgyZDg1MDE5ODVmMDY3OGQ4NjkiLCJ1c2VybmFtZSI6InpzcyJ9.XDDDRRq5jYq5EdEBHtPm7GcuBz4S0VhDTS1amRCdf48";
		String newJwt = JwtUtils.copyJwt(oldJwt, JwtUtils.JWT_WEB_TTL);
		System.out.println(newJwt);
		Claims parseJwt = JwtUtils.parseJwt(newJwt);
		for (Map.Entry<String, Object> entry : parseJwt.entrySet()) {
			System.out.println(entry.getKey() + "=" + entry.getValue());
		}
		Date d1 = parseJwt.getIssuedAt();
		Date d2 = parseJwt.getExpiration();
		System.out.println("令牌签发时间:" + sdf.format(d1));
		System.out.println("令牌过期时间:" + sdf.format(d2));
	}

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

用于复制JWT并延时30分钟。在该方法中,使用JwtUtils.copyJwt()方法复制了一个旧的JWT,并设置了新的过期时间,并将复制后的JWT打印输出。

5.4 测试JWT的有效时间

public void test4() {// 测试JWT的有效时间
		Map<String, Object> claims = new HashMap<String, Object>();
		claims.put("username", "zss");
		String jwt = JwtUtils.createJwt(claims, 3 * 1000L);
		System.out.println(jwt);
		Claims parseJwt = JwtUtils.parseJwt(jwt);
		Date d1 = parseJwt.getIssuedAt();
		Date d2 = parseJwt.getExpiration();
		System.out.println("令牌签发时间:" + sdf.format(d1));
		System.out.println("令牌过期时间:" + sdf.format(d2));
	}

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

用于测试JWT的有效时间。在该方法中,创建了一个带有有效时间为3秒的JWT,并将其打印输出。

5.5 模拟过期JWT的解析

public void test5() {// 三秒后再解析上面过期时间只有三秒的令牌,因为过期则会报错io.jsonwebtoken.ExpiredJwtException
		String oldJwt = "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ4dyIsImV4cCI6MTY5NzExNTczMCwiaWF0IjoxNjk3MTE1NzI3LCJqdGkiOiI3ZTI2NDY0MDU2OTg0YTgzOTJkMzk3ZGE0NDk3YTIyOSIsInVzZXJuYW1lIjoienNzIn0.LeoLGDmwuByqkAq9yErcljEC6wZxGeaIh0l9GhiongU" ;
//		String oldJwt = "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ6a2luZyIsImV4cCI6MTY2MjM0Njg3MSwiaWF0IjoxNjYyMzQ1MDcxLCJhZ2UiOjE4LCJqdGkiOiI4YjllNzc3YzFlMDM0MjViYThmMDVjNTFlMTU3NDQ1MiIsInVzZXJuYW1lIjoienNzIn9.UWpJxPxwJ09PKxE2SY5ME41W1Kv3jP5bZGKK-oNUDuM";
		Claims parseJwt = JwtUtils.parseJwt(oldJwt);
		// 过期后解析就报错了,下面代码根本不会执行
		Date d1 = parseJwt.getIssuedAt();
		Date d2 = parseJwt.getExpiration();
		System.out.println("令牌签发时间:" + sdf.format(d1));
		System.out.println("令牌过期时间:" + sdf.format(d2));
	}

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

用于模拟过期JWT的解析。在该方法中,尝试解析一个过期的JWT,并会抛出io.jsonwebtoken.ExpiredJwtException异常。

六、案例实操

接下面我们就用实际案例来演示使用JWT和没有使用JWT的区别是什么?

先看一下没有使用JWT的效果

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

不难看出,只要我们前端页面只要登陆过一次,将请求路径复制就可以再次打开,这种行为是非常不安全的,我们需要借助JWT进行认证方可进行访问,下面就结合JWT来演示。

前面提到过JWT跨语言那就涉及到跨域问题,首先在我们的过滤器里面加入允许JWT的跨域请求

package com.zking.ssm.util;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 配置tomcat允许跨域访问
 * 
 * @author Administrator
 *
 */
public class CorsFilter implements Filter {
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
	}
	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
			throws IOException, ServletException {
		HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
		HttpServletRequest req = (HttpServletRequest) servletRequest;
		// Access-Control-Allow-Origin就是我们需要设置的域名
		// Access-Control-Allow-Headers跨域允许包含的头。
		// Access-Control-Allow-Methods是允许的请求方式
		httpResponse.setHeader("Access-Control-Allow-Origin", "*");// *,任何域名
		httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE");
		
		//允许客户端发一个新的请求头jwt
		httpResponse.setHeader("Access-Control-Allow-Headers","responseType,Origin,X-Requested-With, Content-Type, Accept, jwt");
		//允许客户端处理一个新的响应头jwt
		httpResponse.setHeader("Access-Control-Expose-Headers", "jwt,Content-Disposition");
		
		//httpResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
		//httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE");
		
		// axios的ajax会发两次请求,第一次提交方式为:option,直接返回即可
		if ("OPTIONS".equals(req.getMethod())) {
			return;
		}
		filterChain.doFilter(servletRequest, servletResponse);
	}
	@Override
	public void destroy() {

	}
}

并在web.xml进行配置过滤器

<!--CrosFilter跨域过滤器-->
  <filter>
    <filter-name>corsFilter</filter-name>
    <filter-class>com.zking.ssm.util.CorsFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>corsFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

这里还需要注意,有一种情况不需要JWT认证,也就是我们登陆的时候,我们JWT本质就是保存我们的用户信息,我们登录都需要JWT那么我们的用户信息应该从哪里获取?所以我们需要对登录请求进行过滤。

package com.zking.ssm.jwt;

import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import io.jsonwebtoken.Claims;

/**
 * * JWT验证过滤器,配置顺序 :CorsFilter-->JwtFilter-->struts2中央控制器
 * 
 * @author Administrator
 *
 */

public class JwtFilter implements Filter {

	// 排除的URL,一般为登陆的URL(请改成自己登陆的URL)
	private static String EXCLUDE = "^/user/userLogin?.*$";

	private static Pattern PATTERN = Pattern.compile(EXCLUDE);

	private boolean OFF = false;// true关闭jwt令牌验证功能

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
	}

	@Override
	public void destroy() {
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;
		//获取当前请求路径。只有登录的请求路径不进行校验之外,其他的URL请求路径必须进行JWT令牌校验
		//http://localhost:8080/ssh2/bookAction_queryBookPager.action
		//req.getServletPath()==/bookAction_queryBookPager.action
		String path = req.getServletPath();
		if (OFF || isExcludeUrl(path)) {// 登陆直接放行
				chain.doFilter(request, response);
				return;
		}

		// 从客户端请求头中获得令牌并验证
		//token=头.载荷.签名
		String jwt = req.getHeader(JwtUtils.JWT_HEADER_KEY);
		Claims claims = this.validateJwtToken(jwt);
		//在这里请各位大哥大姐从JWT令牌中提取payload中的声明部分
		//从声明部分中获取私有声明
		//获取私有声明中的User对象 -> Modules
		Boolean flag=false;
		if (null == claims) {
			// resp.setCharacterEncoding("UTF-8");
			resp.sendError(403, "JWT令牌已过期或已失效");
			return;
		} else {
			
			//1.获取已经解析后的payload(私有声明)
			//2.从私有声明中当前用户所对应的权限集合List<String>或者List<Module>
			//3.循环权限(Module[id,url])
			// OK,放行请求 chain.doFilter(request, response);
			// NO,发送错误信息的JSON
			// ObjectMapper mapper=new ObjectMapper()
			// mapper.writeValue(response.getOutputStream(),json)
			
			String newJwt = JwtUtils.copyJwt(jwt, JwtUtils.JWT_WEB_TTL);
			resp.setHeader(JwtUtils.JWT_HEADER_KEY, newJwt);
			chain.doFilter(request, response);
		}
	}

	/**
	 * 验证jwt令牌,验证通过返回声明(包括公有和私有),返回null则表示验证失败
	 */
	private Claims validateJwtToken(String jwt) {
		Claims claims = null;
		try {
			if (null != jwt) {
				//该解析方法会验证:1)是否过期 2)签名是否成功
				claims = JwtUtils.parseJwt(jwt);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return claims;
	}

	/**
	 * 是否为排除的URL
	 * 
	 * @param path
	 * @return
	 */
	private boolean isExcludeUrl(String path) {
		Matcher matcher = PATTERN.matcher(path);
		return matcher.matches();
	}

	// public static void main(String[] args) {
	// String path = "/sys/userAction_doLogin.action?username=zs&password=123";
	// Matcher matcher = PATTERN.matcher(path);
	// boolean b = matcher.matches();
	// System.out.println(b);
	// }

}

注意这里有一个属性private boolean OFF = false;我们在开发过程中可以通过这个属性来决定我们要不要使用JWT,如果我们开发过程都需要JWT的话测试是非常麻烦的。

再到Controller层进行编写保存JWT的方法

    public JsonResponseBody<?> userLogin(UserVo userVo, HttpServletResponse response){
        if(userVo.getUsername().equals("admin")&&userVo.getPassword().equals("123")){
            //私有要求claim
            Map<String,Object> json=new HashMap<String,Object>();
            json.put("username", userVo.getUsername());
//            生成JWT,并设置到response响应头中
            String jwt=JwtUtils.createJwt(json, JwtUtils.JWT_WEB_TTL);
            response.setHeader(JwtUtils.JWT_HEADER_KEY, jwt);
            return new JsonResponseBody<>("用户登陆成功!",true,0,null);
        }else{
            return new JsonResponseBody<>("用户名或密码错误!",false,0,null);
        }
    }

下面我们结合上一篇博客Vuex的知识对前端代码进行编写

先在state.js定义jwt变量

export default {
  jwt:''
};

再去mutations.js编写设置jwt的方法

export default {
  setJwt: (state, payload) => {
    state.jwt = payload.jwt;
  }
};

 再去getters.js编写获取jwt的方法

export default {
 getJwt: (state) => {
    return state.jwt
  }
};

 再到vue项目对axios的全局配置的请求过滤和响应过滤获取和设置JWT

// 请求拦截器
axios.interceptors.request.use(function(config) {
  let jwt= window.vm.$store.getters.getJwt;
  if(jwt){
    config.headers['jwt']=jwt;
  }
	return config;
}, function(error) {
	return Promise.reject(error);
});

// 响应拦截器
axios.interceptors.response.use(function(response) {
  let jwt=response.headers['jwt'];
  if(jwt){
    window.vm.$store.commit('setJwt',{
      jwt:jwt
    })
  }
	return response;
}, function(error) {
	return Promise.reject(error);
});

我们对比一下现在加了JWT后的效果,登录过后请求主页会将开始后端响应的JWT保存到Vuex,下次发送请求的使用就会带上这个JWT,后端校验如果不是登录请求又没有JWT又不会“放行请求”

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

这时候我们看一下复制请求还能否加载内容

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

这时候复制的请求没有带JWT是过不了后端的校验的,我们的JWT生效了

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session

到这里我的分享就结束了,欢迎到评论区探讨交流!!

💖如果觉得有用的话还请点个赞吧 💖

什么是JWT?深入理解JWT从原理到应用,Vue的快速入门,jwt,jjwt-root,session文章来源地址https://www.toymoban.com/news/detail-716144.html

到了这里,关于什么是JWT?深入理解JWT从原理到应用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深入理解网络中断:原理与应用

    🔭 嗨,您好 👋 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者 📖 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代 🌲文章所在专栏:网络 I/O 🤔 我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识 💬 向我询问任何您想

    2024年02月04日
    浏览(45)
  • 深入理解中文编码:原理、应用与实践

    编码是将信息转换为特定格式以便存储、传输或处理的过程。在计算机科学中,编码通常指的是将文本、图像、音频等数据转换为数字形式的过程。编码的作用在于统一数据格式、提高数据传输效率、确保数据安全性等方面发挥着重要作用。 中文编码相较于英文等西方语言编

    2024年04月12日
    浏览(47)
  • 深入理解Reactor模型的原理与应用

            Reactor意思是“反应堆”,是一种事件驱动机制。          和普通函数调用的不同之处在于:应用程序不是主动的调用某个 API 完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到 Reactor 上,如果相应的时间发生,Reactor将

    2024年02月10日
    浏览(43)
  • 深入理解DES算法:原理、实现与应用

    title: 深入理解DES算法:原理、实现与应用 date: 2024/4/14 21:30:21 updated: 2024/4/14 21:30:21 tags: DES加密 对称加密 分组密码 密钥管理 S盒P盒 安全性分析 替代算法 历史 DES(Data Encryption Standard)算法是由IBM研发,并于1977年被美国国家标准局(NBS,现NIST)确定为数据加密标准。 设计目

    2024年04月14日
    浏览(78)
  • 深入理解Vue的响应式原理

    用过Vue这个框架的人应该都知道,数据驱动是Vue框架的核心,数据双向绑定是它的一大特色,根据官方的解释,我们可以比较清晰地去知道响应式的简单原理。 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.definePro

    2023年04月17日
    浏览(26)
  • 【Python 矩阵:快速入门指南】-深入理解矩阵运算及其常用计算

    【Python 矩阵:快速入门指南】-深入理解矩阵运算及其常用计算 在数据科学和机器学习中,矩阵是一个非常重要的数学概念,它被广泛应用于数据处理、图像处理、自然语言处理等领域。Python作为一门高效且广泛应用的编程语言,提供了许多强大的工具来处理矩阵。本文将介

    2024年02月13日
    浏览(64)
  • “深入理解Spring框架的核心原理与应用“

    标题:深入理解Spring框架的核心原理与应用 摘要:本文旨在深入探讨Spring框架的核心原理与应用。我们将介绍Spring框架的主要特点、IOC容器的工作原理、AOP的实现方式以及Spring MVC的应用等内容,并通过示例代码来演示Spring框架的使用。 Spring框架是一款非常流行的Java开发框架

    2024年02月16日
    浏览(41)
  • 【Linux基本指令(2)】几十条指令快速入手Linux/深入理解什么是指令

    本文思维导图: *号匹配。 功能:可以匹配任何东西。 比如: 可以看到,*号可以匹配任何东西, 2.ctrl + c指令:终止因为程序或者指令异常,而导致我们无法进行指令输入。 3.Linux下一切皆文件 对于显示器来说,显示器文件有写方法(fwrite),有读方法(fread),但是读方法

    2024年02月06日
    浏览(38)
  • 深入理解MD5算法:原理、应用与安全

    在当今数字化时代,数据安全和完整性变得至关重要。消息摘要算法是一种用于验证数据完整性和安全性的重要工具。在众多消息摘要算法中,MD5(Message Digest Algorithm 5)因其快速、广泛应用和相对较高的安全性而备受关注。本书将深入探讨MD5算法的原理、应用和安全性,帮

    2024年04月11日
    浏览(54)
  • 【递归】:原理、应用与案例解析 ,助你深入理解递归核心思想

    递归在计算机科学中,递归是一种解决计算问题的方法,其中解决方案取决于同一类问题的更小子集 例如 递归遍历环形链表 基本情况(Base Case) :基本情况是递归函数中最简单的情况,它们通常是递归终止的条件。在基本情况下,递归函数会返回一个明确的值,而不再进行

    2024年02月21日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包