面试_java_session、cookie、token

118 阅读11分钟

HTTP协议是无状态的,一次请求结束,连接断开,浏览器再次发送请求,服务器就不知道这个请求是哪个用户发过来的。就比如电商网站里,用户把某个商品加入购物车,切换页面再添加别的商品,这两次添加商品的请求没有关联,浏览器就无法知道用户最终选择了哪些商品。所以就需要一个机制,在用户登录后能够保存住用户的信息和状态,在后续的动作中能够验证用户的身份。

这种机制可以是单独的cookie策略;也可以是session策略或者token策略,而他们都是依赖于cookie的。



一、cookie策略

单独的使用cookie策略很简单。浏览器第一次请求服务器时,服务器会创建一个cookie对象,其中的set-cookie字段里携带一些关键信息,然后将cookie对象放在response响应头里发送给浏览器。浏览器保存这个cookie,并在下一次请求时将此cookie放到request请求头里,服务器拿到后用此判别身份。


1.1 代码演示

下面模拟浏览器第一次请求后,服务器为其创建cookie对象的过程。(不要认为代码是自己在本地写的,这就属于浏览器的动作)

@Controller
@RequestMapping("/alpha")
public class CookieTest {
    
    @RequestMapping(path = "/cookie/set", method = RequestMethod.GET)
    @ResponseBody
    public String setCookie(HttpServletResponse response) {
        // 创建cookie
        Cookie cookie = new Cookie("testCode", CommunityUtil.generateUUID());
        cookie.setPath("/alpha");     // 设置cookie生效的范围
        cookie.setMaxAge(60 * 10);    // 设置cookie的生存时间
        // 发送cookie
        response.addCookie(cookie);

        return "服务器创建cookie对象";
    }
    
}

代码运行后,在页面的“network”中观察response响应头信息。(既然是服务器的动作,就要看response而不是request)


从代码和结果中可以看出。服务器创建的cookie对象,是用其中的set-cookie字段来存储其特殊信息的。而特殊信息,包含以下部分:

  • 自定义的键值对:只能设置一组,一般用来校验用户的身份。本例中是testCode。
  • Max-Age:服务器为该cookie对象设置的生命周期,有生命周期后,浏览器接收后会把该cookie对象保存在硬盘,在有限期内即时浏览器关闭,该cookie也不会丢失,另外,存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口;如果不设置生命周期,那么该cookie对象保存在当前浏览器内存,浏览器一关闭,该cookie就丢失。这两者分别叫做会话cookie与持久cookie。
  • Expires:服务器根据cookie创建时间和生命时长自己计算出的有效期限
  • Path:设置该cookie有效的网页路径范围。只要在该路径范围以及子路径范围内,浏览器就可以在下一次请求时自动携带此cookie,服务器就能认出该用户;超出这个范围,此cookie就无效。例如下面第二次请求: /alpha/cookie/hello ,就能在request请求头里自动携带此cookie

@Controller
@RequestMapping("/alpha")
public class CookieTest {
    
    @RequestMapping(path = "/cookie/hello", method = RequestMethod.GET)
    @ResponseBody
    public String getCookie(@CookieValue("testCode") String testCode) {
        System.out.println(testCode);
        return "浏览器第二次请求,并在本次请求中携带了cookie";
    }
    
}

从页面的“network”中观察request请求头信息,可以看出本次请求自动携带了返回的cookie:

代码运行输出:




二、session策略

用户第一次请求服务器的时候,服务器会为每个用户创建一个session对象存放在服务器内存里(服务器重启会丢失,可持久化到数据库中),每个session中含有唯一的sessionid和用户敏感信息,然后创建一个cookie对象,其中的set-cookie字段里携带包含sessionid在内的一些特殊信息,将cookie对象放在response响应头里发送给浏览器。

浏览器会把这个cookie保存在自身内存,后续的请求中会自动把这个cookie放到request请求头里,服务端会通过cookie中的sessionid找到与该用户对应的session对象,从而完成身份认证,用户就可以自由浏览这个网站的 任意页面


2.1代码演示

代码演示(依旧指代的是服务端的动作):

@Controller
@RequestMapping("/alpha")
public class CookieTest {
    
    @RequestMapping(path = "/session/set", method = RequestMethod.GET)
    @ResponseBody
    public String setSession(HttpSession session) {
        session.setAttribute("username", "admin");
        session.setAttribute("password", "12345");
        return "服务器创建session";
    }
    
}

代码运行后,在页面的“network”中观察response响应头信息:


从代码和结果中可以看出。服务器创建的session对象,是用其中的set-cookie字段来存储特殊信息的。而特殊信息,包含以下部分:

  • JSESSIONID,也就是sessionid
  • Path:设置该session有效的网页路径范围,默认是整个项目,也就是整个网站。指:用户在本网站的任意页面,都可以根据此sessionid证明自己身份有效。例如下面第二次请求: /alpha/cookie/hello ,就能在request请求头里自动携带此cookie
  • 至于生命期:session策略下的cookie,都是没有设置最大生命期限的。所以在关闭浏览器后会丢失cookie,进而丢失其中的sessionid,而一段时间不请求后,session也会自动过期。程序员设置最大周期后,可以实现跨浏览器窗口、跨浏览器共用一个sessionid。
  • 至于键值对:服务器获取到的敏感信息【键值对(可以是多组)】,并保存到服务器自己的内存,在response中不展示,只展示指代该敏感信息的sessionid。


2.2 关于session的创建、存储、生命周期与失效

一个常见的错误是以为session在有浏览器请求时就被创建,然而事实是直到访问JSP、Servlet等程序时才会创建session,只访问HTML、IMAGE等静态资源并不会创建。调用request.getSession(true)可强制生成session。


Tomcat 中的 Session 以 HashMap 格式存放,key 为 sessionId, value 为 org.apache.catalina.Session 类型。开发人员可通过 setAttribute() 方法修改value,可以通过 setMaxInactiveInterval() 设置生命周期,

也可以在web.xml中全局设置生命周期:

<session-config>
    <session-timeout>15</session-timeout>
</session-config>

session有自己默认的有效期(Tomcat 中默认为20min,依应用而定可配置)。当session失效时间到,服务器会销毁之前的session。只要用户在失效时间内有新的请求发送给服务器,服务器就会在当前有效期基础上再延长2个小时。

  • Session的超时时间为maxInactiveInterval属性,可以通过对应的getMaxInactiveInterval()获取,通过setMaxInactiveInterval(longinterval)修改。

  • Session的超时时间也可以在web.xml中修改。另外,通过调用Session的invalidate()方法可以使Session失效。


会话结束 时,服务器会删除此session对象。

  • session过期、用户登出,都会导致会话结束,服务器session丢失

  • 浏览器关闭不会导致服务器session立即丢失,它是导致了 没有设置生命周期的 cookie丢失,而cookie中保存了sessionid,没有sessionid也就无法记录登录状态,才产生“session丢失”的假相。当浏览器存储的cookie失效后,服务端的session不会立即销毁,会有一个延时,服务端会定期清理无效session,不会造成无效数据占用存储空间的问题。

 



2.3 Session与Cookie的异同

  • 存储位置不同,Cookie 保存在浏览器,不安全;Session 保存在服务器端,相对安全。
  • 存储大小不同, 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie。
  • 有效期不同,Cookie 可设置为长时间保持;Session 一般失效时间较短。
  • Cookie 是http的标准;Session 是JavaEE的标准,和http没有直接联系


2.4 session策略的实质与可能产生的问题

本质上是一种key-value的形式。key就是sessionid,全局唯一,相当于用户独一无二的id,存在浏览器的cookie里;value就是这个用户id所代表的用户的敏感信息,存在服务器的session对象里。

无论key还是value,一方没有保持住,服务端都无法分辨用户的身份。


2.4.1 key丢失

如果浏览器禁用掉cookie,它就无法保存sessionid,服务端验证就无法通过。【解决办法:放弃cookie,直接在URL中传递sessionid】


2.4.2 value丢失

value丢失就属于集群系统和分布式系统中的session问题

集群和分布式的引入:

用户请求通过nginx分发到tomcat,tomcat上部署我们的应用。后来用户多了,一个tomcat支撑不住了,就多加几台机器。问题是加完之后程序怎么放置,有水平扩展(分布式)和垂直扩展(集群)两种方式。垂直扩展就是新增的机器直接备份原机器,相当于做了集群;水平扩展意思是拆分应用的功能模块到不同的机器上,通过配置nginx,分析url,将不同的请求负载均衡到不同的服务器上去,相当于做了分布式。


2.4.2.1 分布式系统中的session问题

分布式系统中,每台机器只有一个应用的部分功能,比如一个用户第一次请求被分到了机器A,机器A存下它的session,接下来用户想执行新的功能,请求就要被nginx转发到新机器B上,但机器B上还没有这个用户的session,就需要用户重新登录。


解决方案:

  • 共享session:单独搞一台服务器用来存session,其他服务器都向这台服务器获取session,Redis为内存数据库,读写效率高。其问题是这台服务器挂了,session就全部丢失。

  • Redis集群/主从复制。


2.4.2.2 集群系统中的session问题

集群系统中,session每隔一段时间复制一次,空窗期内如果用户的多次请求被分发到不同机器,就无法实现session共享。


解决方案:

IP-Hash(粘性IP)可以解决此问题,它可以让同一IP的请求都路由到同一台服务器上,即会话粘滞,但它存在单点故障隐患。因为从用户角度来说与服务器的关系是一对一的,但从服务器的角度来说与用户的关系是一对多的,也就是说多个ip会被完全限制在同一台机器上,如果这台机器挂掉,那么访问这个机器的ip用户就要遭殃了,不能再访问应用。




三、token策略

服务端只有一段加密代码,浏览器端使用Cookie技术存id(也就是token)。

浏览器第一次发送账号信息后,服务器通过加密算法针对账号信息生成一个Token回传给浏览器,浏览器获取到Token后存储到cookie或者内存中,在接下来的请求中,将token通过url参数或者HTTP 头部传入到服务器,服务器使用相同的加密规则针对账号信息再生成一个token,与传来的token作比较


3.1 Session与Token的异同

Session和Token机制原理上差不多,都是用户身份验证的一种识别手段,它们都有过期时间的限制,但两者又有一些不同的地方。

  • Session是存放在服务器端的,可以保存在:内存、数据库、NoSQL中。它采用空间换时间的策略来进行身份识别,若Session没有持久化落地存储,一旦服务器重启,Session数据会丢失。
  • Token是放在浏览器存储的,采用了时间换空间策略,因为每次验证都需要一次加密操作。



四、其他

4.1 场景选择

一般购物车功能会采用Session验证,接口校验一般会采用Token验证,具体采用何种方法,需要大家根据自己的业务进行选择。



4.2 关于记住密码与自动登录

使用的是cookie+session,正常来说cookie可以长久保存在自己硬盘,但Session并不会一直存在。过了一定的时间之后,服务器上的Session就会被销毁,以减轻服务器的压力。当服务器上的数据被销毁后,即使客户端上存放了cookie也没有办法“记住我的登录状态”了。

  通用的实现办法是,将用户的用户名和加密之后的密码也通过cookie的方式存放在客户端,当服务器上的Session销毁以后,使用cookie里面存 放的用户名和加密之后的密码重新执行一次登录操作,重建Session,并更新客户端上cookie中存放的Session ID,而这个操作是发生在用户请求一个需要身份验证的页面资源的背后,对于用户来讲是透明的,于是就达到了“记住我的登录状态”的目的了。





www.cnblogs.com/eret9616/p/… zhuanlan.zhihu.com/p/91549283 www.cnblogs.com/jirglt/arch… www.cnblogs.com/binger/arch… www.cnblogs.com/yaowen/p/48… www.nowcoder.com/study/live/… www.zhihu.com/question/19…