【实战详解】Cookie与Session详解

知识概括:

  1. Http请求
  2. cookie
  3. session

1. HTTP会话

要了解cookie与session,首先要认识http请求最重要的基本性质:http是无状态的。也就是说,http协议不会认识两次http请求是否有关联,两次http请求是否来自同一个客户端。每个请求都应该是一个完全独立的个体。

但同时,http拥有另一个基本性质:http是可扩展的。体现在http的请求头中会包含cookies字段。cookies可以保存到浏览器,浏览器通过对请求头写入cookie,让多个http请求共享相同的上下文信息,提供给服务器进行判断,注意此时http仍然是无状态的,是需要第三方,也就是服务器去保存状态。

2. Cookie详解

2.1 介绍详解

我们通过springboot与前端的交互来实践cookie的生命周期。

先看图一:

在这里插入图片描述

我们可以得到下面信息:

  1. cookie是一个key-value的容器,一条cookie只能放一个key-value【new一个cookie对象时传入的参数】。

  2. cookie是具有生效范围一说的,也就是规定当前这条cookie在访问什么接口的时候会自动被写入到请求头。例如我setPath(“/”),表示我当前网站访问任意的后端接口,request对象的请求头里都会存在cookie对象。而如果我setPath(“/alpha”),则表示只有访问/alpha相关接口的时候请求才会携带cookie。因此cookie的发送是通过setPath然后让浏览器自动携带的,而不是前端选择性携带的。

  3. cookie可以设置生存时间,我们知道cookie是存放在浏览器的文件里的,生命周期从前端接收响应开始算起,一到时间立刻自动销毁。

  4. cookie是由响应体去创建的,所以创建的cookie是add到response响应体中

再来看图二和图三:
在这里插入图片描述在这里插入图片描述

我们又可以得到更多的信息:

  1. cookie确实是由响应体创建的,也就是服务器会把创建cookie的指令写入响应体,响应体到达浏览器后执行set-cookie。通过set-cookie去设置cookie的内容,cookie的生存时间和cookie的path

  2. 请求头中的cookie确实是自动携带的

接口获取请求头中携带的cookie:

参数里指定即可cookie的key即可。因为请求头里可能不止一条cookie。

在这里插入图片描述

当然你可以从request对象里获取所有的cookie【这种方法获得的是Cookie[],必须要遍历才知道自己要的cookie到底在哪】。这里提供一个现成的cookie获取工具。

public class CookieUtil {
    public static String getValue(HttpServletRequest request, String name) {
        if(request == null || name == null) {
            throw new IllegalArgumentException("参数不可为空");
        }

        Cookie[] cookies = request.getCookies();
        if(cookies != null) {
            for(Cookie cookie : cookies) {
                if(cookie.getName().equals(name)) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

    //删除cookie,删除的关键是把其寿命设置为0ms即可
    public static void removeCookie(HttpServletRequest request, HttpServletResponse response, String name){
        Cookie[] cookies = request.getCookies();
        if (null != cookies) {
            for (int i = 0; i < cookies.length; i++) {
                if (cookies[i].getName().equals(name)){
                    cookies[i].setPath("/");
                    cookies[i].setMaxAge(0);
                    response.addCookie(cookies[i]);
                    break;
                }
            }
        }
    }
}

总结:

  • cookie是一条key-value键值对数据,并且value只能是字符串,不可以是对象
  • 服务器创建cookie时可以设置生效范围setPath(“/”)作为cookie的内在属性,cookie的path属性会决定cookie在浏览器访问接口的时候是后自动携带到请求头中。
  • 服务器创建cookie时可以设置其生命周期。参数单位为ms毫秒
  • cookie实际再用户的浏览器中才会被响应体创建并保存到用户的浏览器中。

2.2 用于实战

看完2.1就应该清楚cookie的一个 “产业链” 了,也就是cookie的流转了

下面以登录凭证,也就是实现“记住登录状态”业务来加深理解

  • 前端输入表单,点击登录按钮

在这里插入图片描述

  • 后端接收参数并与数据库匹配,检查用户是否合法
  • 如果合法,设置一个凭证【随机字符串,可以用UUID类生成】作为cookie传入,写入到response对象中。进入了response对象不需要任何操作,response对象会自动传到前端。一定要记住!这个凭证我们需要存储我们后面才能进行匹配。可以调用dao类存储到mysql中,也可以直接存到redis中。

在这里插入图片描述

  • 前端收到响应头并创建cookie,同时登录成功进入到首页

在这里插入图片描述

  • 刷新当前首页,可以看到浏览器自动把cookie携带到请求头中,这是基于cookie的path属性自动携带

在这里插入图片描述

  • 因此后端对应的所有接口都需要多加一个@CookieValue(“code”) String code参数去接收用户登录凭证。因为一个接口就是一个服务,我们只对合法用户提供服务,因此我们判断只要当前凭证合法就提供当前接口服务,否者如果没有凭证或者凭证错误或者凭证过期,都要返回错误,如果前端有router则让前端跳转到登录页面。如果前后端不分离则直接重定向到登录页面。

在这里插入图片描述

  • 但是每个接口都截取cookie这样会十分麻烦。建议使用拦截器从request中接收cookie并进行凭证的合法性验证,即与数据库匹配。若凭证合法才放行让其访问controller类,否则在interceptor类即拦截器类就会终止请求。

    • 在interceptor类的preHandle(即进入controller之前会进入该方法)中如下:【拦截器自行学习】
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //requets请求中会自带所有的与本网站相关的cookie,利用刚刚的cookieUtil工具
        String cookie = CookieUtil.getValue(request, "ticket");
        if(cookie == null) {
            //没有凭证,跳转登录页面。我使用的vue,前端在响应体数据为null的情况下会跳转路由,所以return false即可。
            return false;
        }
        //如果有cookie,查询凭证是否是数据库合法凭证
        //我采用的是把cookie存入到redis中。这里看整体逻辑即可。
        String redisKey = RedisKeyUtil.getTicketKey(cookie);
        TicketCookie ticket = (TicketCookie) redisTemplate.opsForValue().get(redisKey);
        if(ticket == null) {
            //凭证不合法,即没有记录
            return false;
        }
        //如果合法,判断凭证是否过期
        if(ticket.getStatus() == 1) {
            //过期了,不给进
            //return false会直接终止线程,也就是前端会显示200,但是线程终止,无服务提供
            return false;
        }
        
        //我们存储凭证到数据库的时候,肯定要存储该cookie是谁的嘛,所以从数据库拉凭证的时候可以获取当前用户uid,这里提供user方便后面controller中需要使用。
        //把当前用户塞到request对象中,让其他服务获取
        request.setAttribute("user", userService.queryUserById(ticket.getUid()));
        return true;
    }
    

到这,cookie的凭证使用流程应该还挺清晰了。

3. Session详解

session的生命周期

  • 用户在第一次访问servlet的时候会建立session
  • tomcat会把长期未使用的session清除,默认20分钟,可修改【一旦访问计时器就清零】
  • 调用Session的invalidate方法可以清楚该request的所有session
  • session可以设置失效时间
  • 存放sessionid的Cookie为服务器自动生成的,它的maxAge属性一般为-1,表示仅当前浏览器内有效,并且各浏览器窗口间不共享,关闭浏览器就会失效,所以也可以理解为关闭客户端session就会失效。因此同一机器的两个浏览器窗口访问服务器时,会生成两个不同的Session

首先是创建session存数据:

我们在登录成功后,可以,当然前提是该接口方法中写上HttpSessionSession对象。在方法参数中写一个session,就会默认创建session和返回包含sessionID的cookie。创建session的过程交给tomcat处理,返回sessionID的过程也交给响应对象去处理。

在这里插入图片描述

什么叫sessionID?只要你当前请求访问的某个接口创建了session,tomcat就会自动分配一个sesionID作为该新session的唯一标识符,sessionID是session的一个自带属性,不需要我们指定,我们只需要指定里面的数据【图中的id是数据而不是sessionID】!

sessionID在tomcat自动创建并分配赋值到session对象后,tomcat还会往当前处理请求的响应体中塞一个cookie,类似于response.addCookie行为。这个cookie就是sessionID,并且这个cookie会在访问携带有session参数的controller方法中自动携带进请求头。【为什么是JSESSIONID? J表示java,tomcat默认名称】

在这里插入图片描述

然后是在session中取数据:

我们作为开发者,是不需要对session容器进行查找的。只需要指定一个session对象,tomcat就能找到对应的实例对象返回给我们。

也就是说,只要请求访问带有的session的方法,会自动携带一个cookie,包含sessionID的cookie。

这个cookie一旦到达服务器,立刻交给tomcat去找到对应的session实例赋值给当前方法参数。
在这里插入图片描述

总结:

解决几个疑问当总结了:

session为什么能记录当前用户信息?多个用户创建session不会访问错吗?

举例session存储用户信息:

  • 一个浏览器对应一个用户,当一个登录请求发送过来的时候,springboot会开启一个线程【一个用户请求开一个线程】
  • 登录成功后,服务端把当前用户的登录信息保存到session中并返回jsession给当前用户,当前请求的线程结束。
  • 也就是说如果多人登录,是由多条线程处理,并且每条线程都会创建session,所以会有n个session存在服务器,并且这n个session的数据内容都不相同。都会返回sessionID到浏览器。
  • 前端接受sessionID这个cookie存储到浏览器中,当用户再次请求携带有session参数的接口方法的时候,会携带独有唯一的sessionID,不同用户的sessionID一定不同,也就一定能在n个session中区分开来。【如果要用session中的数据,就一定要在方法参数中写session】
  • 因此,一个用户一个线程,一个线程创建一个session,一个session对应一条唯一的sessionID Cookie。整个流程,每个session都对应一个用户,都是唯一的。

打开两个浏览器窗口访问应用程序会使用同一个session还是不同的session?

记住,请求是无状态的,一个浏览器就当做一个用户,一个用户请求就会对应一个线程。

session为什么没见有项目经常使用?

在现在追求高并发的时代,session的弊端有很多。

  • 如果只有一个服务器,那用session没问题
  • 但是如果是分布式部署,session无法共享。
    • 解决1:一个ip固定分配给一个服务器。但是又有问题:难实现负载均衡
    • 解决2:同步session。同步冲突问题与性能问题。数据冗余问题
    • 解决3:共享session。专门一个新的服务器存session,其他服务器向该服务器获取。但是单机容易挂掉
    • 解决4:有cookie用cookie,重要信息存数据库集群【默认已经可靠】。解决了共享问题。并发大的时候会有性能瓶颈
    • 成熟解决5:有cookie用cookie,重要信息存redis集群【默认已经可靠】。能很好解决并发问题。
  • 所以session现在对于大型网站来说用的少了,有cookie用cookie

4. 总结

先认识这两个东西。

然后现在主流使用都是用cookie凭证,就是刚刚介绍cookie中使用的方法,而session,应该会被淘汰吧。

用户凭证存储在数据库中肯定还会存这条凭证是谁的,我们只需要在拦截器中截取对应cookie。与数据库匹配凭证信息,获取凭证的owner的id,然后查询一下即可。在拦截器中就可以把user对象放入request对象中提供给controller使用。这完全替代了session的功能。凭证存储在redis集群中还能有很好的并发能力。

回顾一下:

  • 前端输入表单,点击登录按钮

  • 后端接收参数并与数据库匹配,检查用户是否合法

  • 创建cookie】如果合法,设置一个凭证【随机字符串,可以用UUID类生成】作为cookie传入,设置path为setPath(“/”), 写入到response对象中。进入了response对象不需要任何操作,response对象会自动传到前端。一定要记住!这个凭证我们需要存储我们后面才能进行匹配。可以调用dao类存储到mysql中,也可以直接存到redis中。

  • 前端收到响应头并创建cookie,同时登录成功进入到首页

  • 刷新当前首页,可以看到浏览器自动把cookie携带到请求头中,这是基于cookie的path属性自动携带

  • 因为每个接口都截取cookie这样会十分麻烦。建议使用拦截器从request中接收cookie并进行凭证的合法性验证,即与数据库匹配。若凭证合法才放行让其访问controller类,否则在interceptor类即拦截器类就会终止请求。匹配成功后,从凭证中获取当前用户信息,在数据库中找到对应的用户对象add进request对象中给放行后的controller方法使用。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玖等了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值