session 和 cookie

176 阅读5分钟

起因

可能有不少初级程序员对于 http 协议不太了解,还以为只要在服务端设置了 session,之后的客户端再发起请求的话,就一定能获取到 session 的信息。其实,如果是在 浏览器并且没有跨域问题 的话,这么理解还勉强可以。但是如果遇到跨域问题,就会发现接口一直在报错。

http协议、cookie 和 session

其实,我觉得产生这种误区主要的原因还是对 http 协议不了解,其次就是分不清 cookie 和 session。

那么,就简单地介绍一下这三个东西吧。

http协议

凡是对 http 协议有所了解的人,应该都知道它本身是无状态的。既然是无状态的,服务端就不能通过一个 普通的 http 请求来获取客户端的状态。普通的不行,那 特殊的就可以?

是的,虽然 http 协议本身是无状态的,但是每个 http 请求都会携带请求头和请求体(get请求时是查询参数),服务端程序可以根据请求头和请求体中的特殊信息来判断客户端的状态。在反爬虫方面,通过判断请求头中的 user-agent 来判断是否为浏览器或者爬虫,从而禁止爬虫访问,当然,这是最简单的一种反爬虫机制了,真正的反爬虫肯定要比这个复杂得多。

换言之,只要设置不一样的请求头或者请求体,服务端就可以根据这些不一样的数据来获取客户端的状态了,简单便捷的一个方法就是使用 cookie 作为媒介,只要浏览器存在符合要求的cookie,那么之后的每次请求都会携带cookie 数据,不需要人为参与。还有一种方法就是 URL 重写,这个对于后端的程序员来说可能就有些费劲了,所以采用的不多。

采用 cookie 来作为媒介的方式,我个人认为是不推荐的,因为这样会增加服务器的负担,造成不必要的浪费,而且如果用户禁用了 cookie,可以说这个应用程序就算是崩溃了。现在比较流行的是 jwt,服务端直接将用户数据加密发送给前端,前端在合适的时候再发送给服务端,服务端只要将数据解密就能获取用户信息,就是加密解密可能会有点耗时。还有一种,就是服务端设置唯一的键名(比如说token),然后将数据放在缓存,如redis之类的,将 token 作为媒介,后台通过 token 来进行数据查询。

说到这,有的后端可能还是有点疑惑,我在服务器设置的 session,跟客户端有什么关系?这就不得不说一下 session 和 cookie 的关系了。

cookie 和 session

HttpSession 会话机制 -->Servlet的会话机制的实现: 创建于服务器端,保存于服务器,维护于服务器端,每创建一个新的 session, 服务器端都会分配一个唯一的ID,并且把这个ID保存到客户端的 cookie 中,保存形式是以 JSSESSIONID 来保存的。其他语言的 session 机制大同小异,最基本的都是这种。如果你查看产生 session 的那条请求,你可以在响应头里看到 Set-Cookie 字段,浏览器会根据这个响应头进行相应的处理,从而在客户端生成 cookie。

可以看到,创建 session 的时候不仅跟服务器有关,还会在客户端设置 cookie,所以请求时,如果没有携带 cookie,服务端的 session 就无法获取数据了。

那么,为什么有的程序在关闭浏览器再重新打开的时候,服务端也无法获取 session 的数据呢?难道关闭浏览器,服务端的session 也被清除了吗?并不是的。

cookie类型

cookie 分为 会话cookie 和 持久cookie。 服务端的 session 一般实现的 cookie 是 会话cookie,关闭浏览器进程, 该cookie就会失效了,服务端无法根据 cookie 来获取 session的数据了,也就造成了一种服务端的session也被清除的错觉

在以前的设计中,持久cookie常被用来实现免密登录,可能还曾经是个很不错的卖点吧。现在的前端发展日新月异,除了持久cookie, localStorage或许是一个更好的建议(不喜勿喷)。

跨域cookie

现在最经常使用的跨域结局方案可能是 CORS 了吧,但是不少后端在设置 CORS 的时候,只设置了 Access-Control-Allow-Origin: '*',在不需要携带cookie的场合确实是解决了问题,但是此时并不能携带 cookie 进行请求。

后端的工作并不只是简单的设置 Access-Control-Allow-Credentials: true就可以了,Access-Control-Allow-Credentials: true时,Access-Control-Allow-Origin不能设置为 '*',否则浏览器会认为该请求不安全,自动进行拦截。

有的后端觉得Access-Control-Allow-Origin这个请求头我总不能写死吧,这不现实,要不然我到时候上线还得再改一下?那就取我请求头里的 Origin里,这不就是动态的了。要是后端不知道是什么请求头,也不会设置Access-Control-Allow-Origin: this.header('origin'),那就让他自己琢磨去吧o(╥﹏╥)o