面试官:Session和JWT有什么区别?

简介: JSON Web Token (JWT) 是一种开放标准,用于安全地在网络上传输信息。JWT 包含头部、载荷和签名三部分,常用于身份验证和授权。与Session相比,JWT有以下优势:无服务器存储状态,支持跨域,适应微服务架构,自包含且可扩展。在Java开发中,可以使用HuTool框架操作JWT,包括生成、验证和解析Token。JWT通过在客户端存储令牌实现无状态认证,与Session的主要区别在于工作原理、存储方式和有效期管理。

Session 和 JWT(JSON Web Token)都是用于在用户和服务器之间建立认证状态的机制,但它们在工作原理、存储方式和安全性等方面存在着一些差异,下面我们一起来看。

1.什么是JWT?

Session 我们已经很熟悉了,那什么是 JWT 呢?

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络上安全传输信息的简洁、自包含的方式。它通常被用于身份验证和授权机制。

JWT 由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。

  1. 头部(Header):包含了关于生成该 JWT 的信息以及所使用的算法类型。
  2. 载荷(Payload):包含了要传递的数据,例如身份信息和其他附属数据。JWT 官方规定了 7 个字段,可供使用:
    1. iss (Issuer):签发者。
    2. sub (Subject):主题。
    3. aud (Audience):接收者。
    4. exp (Expiration time):过期时间。
    5. nbf (Not Before):生效时间。
    6. iat (Issued At):签发时间。
    7. jti (JWT ID):编号。
  3. 签名(Signature):使用密钥对头部和载荷进行签名,以验证其完整性。

    JWT 官网:https://jwt.io/

2.JWT优点分析

JWT 相较于传统的基于会话(Session)的认证机制,具有以下优势:

  1. 无需服务器存储状态:传统的基于会话的认证机制需要服务器在会话中存储用户的状态信息,包括用户的登录状态、权限等。而使用 JWT,服务器无需存储任何会话状态信息,所有的认证和授权信息都包含在 JWT 中,使得系统可以更容易地进行水平扩展。
  2. 跨域支持:由于 JWT 包含了完整的认证和授权信息,因此可以轻松地在多个域之间进行传递和使用,实现跨域授权。
  3. 适应微服务架构:在微服务架构中,很多服务是独立部署并且可以横向扩展的,这就需要保证认证和授权的无状态性。使用 JWT 可以满足这种需求,每次请求携带 JWT 即可实现认证和授权。
  4. 自包含:JWT 包含了认证和授权信息,以及其他自定义的声明,这些信息都被编码在 JWT 中,在服务端解码后使用。JWT 的自包含性减少了对服务端资源的依赖,并提供了统一的安全机制。
  5. 扩展性:JWT 可以被扩展和定制,可以按照需求添加自定义的声明和数据,灵活性更高。

总结来说,使用 JWT 相较于传统的基于会话的认证机制,可以减少服务器存储开销和管理复杂性,实现跨域支持和水平扩展,并且更适应无状态和微服务架构。

3.JWT基本使用

在 Java 开发中,可以借助 JWT 工具类来方便的操作 JWT,例如 HuTool 框架中的 JWTUtil。

HuTool 介绍:https://doc.hutool.cn/pages/JWTUtil/

使用 HuTool 操作 JWT 的步骤如下:

  1. 添加 HuTool 框架依赖
  2. 生成 Token
  3. 验证和解析 Token

    3.1 添加 HuTool 框架依赖

    在 pom.xml 中添加以下信息:
    <dependency>
     <groupId>cn.hutool</groupId>
     <artifactId>hutool-all</artifactId>
     <version>5.8.16</version>
    </dependency>
    

    3.2 生成 Token

    Map<String, Object> map = new HashMap<String, Object>() {
         
    private static final long serialVersionUID = 1L;
    {
         
     put("uid", Integer.parseInt("123")); // 用户ID
     put("expire_time", System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 15); // 过期时间15天
    }
    };
    JWTUtil.createToken(map, "服务器端秘钥".getBytes());
    

    3.3 验证和解析 Token

    验证 Token 的示例代码如下:
    String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2MjQwMDQ4MjIsInVzZXJJZCI6MSwiYXV0aG9yaXRpZXMiOlsiUk9MRV_op5LoibLkuozlj7ciLCJzeXNfbWVudV8xIiwiUk9MRV_op5LoibLkuIDlj7ciLCJzeXNfbWVudV8yIl0sImp0aSI6ImQ0YzVlYjgwLTA5ZTctNGU0ZC1hZTg3LTVkNGI5M2FhNmFiNiIsImNsaWVudF9pZCI6ImhhbmR5LXNob3AifQ.aixF1eKlAKS_k3ynFnStE7-IRGiD5YaqznvK2xEjBew";
    JWTUtil.verify(token, "123456".getBytes());
    
    解析 Token 的示例代码如下:
    String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9.U2aQkC2THYV9L0fTN-yBBI7gmo5xhmvMhATtu8v0zEA";
    final JWT jwt = JWTUtil.parseToken(rightToken);
    jwt.getHeader(JWTHeader.TYPE);
    jwt.getPayload("sub");
    

    3.4 代码实战

    在登录成功之后,生成 Token 的示例代码如下:
    // 登录成功,使用 JWT 生成 Token
    Map<String, Object> payload = new HashMap<String, Object>() {
         
     private static final long serialVersionUID = 1L;
     {
         
         put("uid", userinfo.getUid());
         put("manager", userinfo.getManager());
         // JWT 过期时间为 15 天
         put("exp", System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 15);
     }
    };
    String token = JWTUtil.createToken(payload, AppVariable.JWT_KEY.getBytes());
    
    例如在 Spring Cloud Gateway 网关中验证 Token 的实现代码如下:
    ```java
    import cn.hutool.jwt.JWT;
    import cn.hutool.jwt.JWTUtil;
    import com.example.common.AppVariable;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;

import java.util.List;

/**

  • 登录过滤器(登录判断)
    */
    @Component
    public class AuthFilter implements GlobalFilter, Ordered {
    // 排除登录验证的 URL 地址
    private String[] skipAuthUrls = {"/user/add", "/user/login"};

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

     // 当前请求的 URL
     String url = exchange.getRequest().getURI().getPath();
     for (String item : skipAuthUrls) {
         if (item.equals(url)) {
             // 继续往下走
             return chain.filter(exchange);
         }
     }
     ServerHttpResponse response = exchange.getResponse();
     // 登录判断
     List<String> tokens =
             exchange.getRequest().getHeaders().get(AppVariable.TOKEN_KEY);
     if (tokens == null || tokens.size() == 0) {
         // 当前未登录
         response.setStatusCode(HttpStatus.UNAUTHORIZED);
         return response.setComplete();
     }
     // token 有值
     String token = tokens.get(0);
     // JWT 效验 token 是否有效
     boolean result = false;
     try {
         result = JWTUtil.verify(token, AppVariable.JWT_KEY.getBytes());
     } catch (Exception e) {
         result = false;
     }
     if (!result) {
         // 无效 token
         response.setStatusCode(HttpStatus.UNAUTHORIZED);
         return response.setComplete();
     } else { // 判断 token 是否过期
         final JWT jwt = JWTUtil.parseToken(token);
         // 得到过期时间
         Object expObj = jwt.getPayload("exp");
         if (expObj == null) {
             response.setStatusCode(HttpStatus.UNAUTHORIZED);
             return response.setComplete();
         }
         long exp = Long.parseLong(expObj.toString());
         if (System.currentTimeMillis() > exp) {
             // token 过期
             response.setStatusCode(HttpStatus.UNAUTHORIZED);
             return response.setComplete();
         }
     }
     return chain.filter(exchange);
    

    }

    @Override
    public int getOrder() {

     // 值越小越早执行
     return 1;
    

    }
    }
    ```

    4.JWT实现原理

    JWT 本质是将秘钥存放在服务器端,并通过某种加密手段进行加密和验证的机制。加密签名=某加密算法(header+payload+服务器端私钥),因为服务端私钥别人不能获取,所以 JWT 能保证自身其安全性。

    5.Session VS JWT

    Session 和 JWT 的区别主要有以下几点:

  1. 工作原理不同:Session 机制依赖于服务器端的存储。当用户首次登录时,服务器会创建一个会话,并生成一个唯一的会话 ID,然后将这个 ID 返回给客户端(通常是通过Cookie)。客户端在后续的请求中会携带这个会话 ID,服务器根据会话ID来识别用户并获取其会话信息;而 JWT 是一种无状态的认证机制,它通过在客户端存储令牌(Token)来实现认证。当用户登录时,服务器会生成一个包含用户信息和有效期的 JWT,并将其返回给客户端。客户端在后续的请求中会携带这个 JWT,服务器通过验证 JWT 的有效性来识别用户。
  2. 存储方式不同:Session 信息存储在服务器端,通常是保存在内存或数据库中。这种方式需要服务器维护会话状态,因此在分布式系统或微服务架构中,会话信息的共享和同步可能会成为问题;而 JWT信息存储在客户端,通常是保存在浏览器的本地存储或 HTTP 请求的头部中。这种方式无需服务器维护会话状态,使得 JWT 在分布式系统或微服务架构中更加灵活和易于扩展。
  3. 有效期和灵活性不同:Session 的有效期通常由服务器控制,并且在会话期间用户状态可以在服务器端动态改变。但这也意味着服务器需要管理会话的生命周期;而 JWT 的有效期可以在令牌生成时设置,并且可以在客户端进行缓存和重复使用。这使得 JWT 在需要频繁访问资源且不需要频繁更改用户状态的场景中更加适用。此外,JWT 还支持在令牌中包含自定义的用户信息,提供了更大的灵活性。

    课后思考

    既然 JWT 的有效期是在令牌生成时设置的,那如何实现 JWT 的自动续期呢?又如何将已经泄漏的 JWT 令牌作废呢?

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

相关文章
|
存储 算法 架构师
阿里面试:PS+PO、CMS、G1、ZGC区别在哪?什么是卡表、记忆集、联合表?问懵了,尼恩来一个 图解+秒懂+史上最全的答案
阿里面试:PS+PO、CMS、G1、ZGC区别在哪?什么是卡表、记忆集、联合表?问懵了,尼恩来一个 图解+秒懂+史上最全的答案
|
Java 程序员 调度
Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
684 9
|
安全 Java 程序员
Java面试必问!run() 和 start() 方法到底有啥区别?
在多线程编程中,run和 start方法常常让开发者感到困惑。为什么调用 start 才能启动线程,而直接调用 run只是普通方法调用?这篇文章将通过一个简单的例子,详细解析这两者的区别,帮助你在面试中脱颖而出,理解多线程背后的机制和原理。
1070 12
|
编译器 Android开发 开发者
Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
Lambda表达式和匿名函数都是Kotlin中强大的特性,帮助开发者编写简洁而高效的代码。理解它们的区别和适用场景,有助于选择最合适的方式来解决问题。希望本文的详细讲解和示例能够帮助你在Kotlin开发中更好地运用这些特性。
456 9
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
370 14
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
今日分享的主题是如何区分&和&&的区别,提高自身面试的能力。主要分为以下四部分。 1、自我面试经历 2、&amp和&amp&amp的不同之处 3、&对&&的不同用回答逻辑解释 4、彩蛋
|
Java 关系型数据库 数据库
京东面试:聊聊Spring事务?Spring事务的10种失效场景?加入型传播和嵌套型传播有什么区别?
45岁老架构师尼恩分享了Spring事务的核心知识点,包括事务的两种管理方式(编程式和声明式)、@Transactional注解的五大属性(transactionManager、propagation、isolation、timeout、readOnly、rollbackFor)、事务的七种传播行为、事务隔离级别及其与数据库隔离级别的关系,以及Spring事务的10种失效场景。尼恩还强调了面试中如何给出高质量答案,推荐阅读《尼恩Java面试宝典PDF》以提升面试表现。更多技术资料可在公众号【技术自由圈】获取。
|
存储 缓存 网络协议
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点,GET、POST的区别,Cookie与Session
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点、状态码、报文格式,GET、POST的区别,DNS的解析过程、数字证书、Cookie与Session,对称加密和非对称加密
|
JavaScript
Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(二)
Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单(一)
807 0
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?