基于session的会话管理实践
本文最后更新于:8 个月前
分别探讨了前后端同源和跨域条件下的session cookie会话管理
基于session的会话管理实践(含cookie跨域解决方案)
之前做的项目中,使用的都是基于token的会话管理方式,以致于对更为传统的、据说现在依然在广泛使用的基于session的会话管理机制没有太多了解。所以特意通过一次实践,来深入了解掌握基于session的会话管理方法
项目仓库:基于sessionID cookie的会话管理实践(含cookie跨域解决方案),使用express-session (github.com)
一、理论基础
1.什么是会话管理
会话管理基于一个背景,即http是无状态的,在一次请求结束后,下一次请求服务端不知道请求是由哪个用户发送过来的。
采用各种方法,让服务器记住用户的身份,就是会话管理
2.基于session的会话管理
会话管理有三种方式:(1)基于server的session,(2)cookie-based,(3)token-based
基于session的会话管理,是目前仍在大量使用的方式,适合单体服务网站使用。
用户在服务端完成身份认证后,由服务端生成和保存一份session记录,其中保存了用户相关的信息,如身份、权限等。在返回认证响应式,将session ID保存到cookie中返回给客户端(实际上是添加set-cookie
请求头,通知浏览器把session ID保存到cookie中)。之后,浏览器再发送请求时,会自动将cookie携带。服务端可以获取cookie中携带的 session ID,读取保存在服务端的数据,从而识别出用户身份。
优点:是有状态的会话管理,服务端可以决定会话继续会终止
缺点:(1)cookie可能存在跨域的问题需要解决;(2)存在CSRF的风险;(3)占用服务端资源;(4)如果服务端是分布式的,需要在多台服务器设备之间共享session
二、实践工具
后端:
- 框架:Express
- session管理中间件:express-session
- session储存中间件:connect-mongo
- CORS解决方案库:cors
前端:
- 纯HTML
- XHR库:Axios(通过
<script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
引入html) - 响应式框架:petite-vue(提供页面响应式机制,编写更方便)
三、搭建服务端
新建项目
1 |
|
安装中间件
1 |
|
要解决的几个关键问题:(1)怎么生成sessionID cookie?(2)怎么将会话信息写入session?(3)怎么读取session保存的会话信息?
1 |
|
1 |
|
1 |
|
四、搭建前端页面
前端只需要搭建一个简单的页面,能够分别发送:不用携带cookie的请求、获取cookie的请求、需要携带cookie用于鉴权的请求几种即可。
首先搭建一个同源的网页。
1 |
|
Express框架搭建了静态托管服务,(因为不会写jade),所以在/public
目录下创建一个index.html文件,用来实现上述几种请求
1 |
|
接下来,输入:http://localhost:3000/
,即可打开网页
直接访问“获取用户信息”,由于没有cookie,所以失败401
登陆之后,查看响应头,可以看到Set-Cookie
字段,这个就是express-session生成并发送过来的
然后发送userinfo请求的时候,可以看到浏览器自动将cookie携带上了,服务端由此可以获取sessionID,进而读取服务端本地的session信息,进行身份识别。
同源条件下的session会话管理至此就完成了,实现起来还是比较简单。下面继续探究跨域条件下的session会话管理该如何操作
五、跨域情况下的session会话管理
为了模拟出一个跨域环境,我们在后端项目之外,新建一个HTML,内容与上面那个html文件一致,这里不再重复
此时,为了解决跨域请求的问题,我们在服务端进行CORS配置,这里采用现成的解决方案 cors - npm (npmjs.com)
在Express后端项目中,安装cors
1 |
|
在app.js文件中引入
1 |
|
这里只是为了从简处理,直接放行了所有跨域请求的origin源。实际生产环境下强烈不建议这么做!
这里的表现与同源条件下稍有不同!
- login的响应头中依然携带了
Set-Cookie
字段 - 但是这次userinfo请求却没有携带cookie,通过检查cookie发现,浏览器并没有保存cookie(cookie为空)
原因:跨站点的cookie,Set-Cookie标头没有指定 SameSite 属性!!默认为 Lax,不能被浏览器保存。关于SameSite
属性,更多的介绍,可以看这篇文章
遇到新的问题:当设置SameSite:None
时,需要搭配设置Secure
属性(只能在https协议下携带cookie)
至此,后面的探究路基本是断了,因为:
- html是直接在本地打开的,协议为 file
- 后端服务直接在本地运行的,协议为http
解决方案有两种:
- 第一种,通过Nginx为网页架设正向代理,让跨域变成同源,也就不存在cookie跨域问题了
- 第二种,为了让前后端都能通过https协议访问,需要将两者部署到服务器上,然后设置https协议的路径,继续向下探究
接下来,分别对两种解决方案进行实践。
六、cookie跨域方案一:正向代理服务器
网页静态资源部署到服务器A上,同时在服务器A上部署一个Nginx服务器,在Nginx设置一个代理,通过这个代理去访问真正的后端url。这个代理就称为网页的正向代理
对于网页来说,接收的数据,包括cookie,来自于Nginx中的正向代理。而这个代理与网页是同源的,因为它们都在同一台服务器上(本质是因为使用相同的协议、域名、端口),所以就不会构成跨域了。
而对于代理来说,因为它并不是浏览器,所以也就不存在跨域问题一说了。
分别将 网页静态html 和 后端项目 上传到服务器。
这里最核心的步骤是Nginx的配置
1 |
|
1 |
|
响应头上携带上了set-cookie
浏览器顺利保存了cookie
发送请求时,顺利携带上了cookie
七、cookie跨域方案二:SameSite + Secure
- SameSite,用来防止CSRF攻击,有三个值:
- Lax(默认值),不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外
- Strict,完全禁止第三方(即跨源的) Cookie
- None,关闭这个属性,前提是同时设置
Secure
属性为true
- Secure,指定浏览器只有在加密协议 HTTPS 下,才能将这个 Cookie 发送到服务器
为了让前后端都能通过https协议访问,需要将两者部署到服务器上。并配置https协议的路径
1 |
|
等等,这时前后端不就已经同源了吗?那还费劲去配置 SameSite
、Secure
、withcredentials
干什么??
结束~
八、express-session配置
官方链接:express-session - npm (npmjs.com)
属性 | 说明 |
---|---|
cookie | 设置sessionID cookie的对象属性,支持设置的属性见下一章 cookie属性 默认值: { path: '/', httpOnly: true, secure: false, maxAge: null } |
genid | 生成新会话ID的函数,默认为使用uid安全库生成ID的函数 |
name | session ID cookie的名称,用来在服务端读取它,默认值:connect.sid |
proxy | true、false、undeined,设置secure为ture时信任反向代理,默认为undefined(继承自Express) |
resave | 是否将 sessionID cookie保存回客户端,即使cookie没有发生修改,默认值为true(官方不推荐使用) |
rolling | 布尔值,为true时将快速过期会话。默认为false |
saveUninitialized | 布尔值,强制将“未初始化”的会话保存到存储中。false减少服务器储存使用量。默认为true(官方不推荐使用) |
secret(必须设置) | 字符串or字符串数组,cookie签名的密钥,最好用环境变量设置(不要公开到仓库) |
store | 服务端session保存的位置,默认保存在内存中 |
unset | 默认值:keep,存储中的会话将被保留,但在请求过程中所做的修改将被忽略,不会被保存 |
九、cookie属性
下面介绍的虽然只是express-session的cookie配置项,但它与原生cookie的属性基本是一致的
属性 | 说明 |
---|---|
domain | 设置cookie的域,用来解决子域名cookie跨域问题,默认不设置 设置了domain,子域名会携带当前cookie;反之不携带 |
expires | 接受Date对象,用来设置cookie的到期时间,默认不设置,官方不推荐使用! expires和maxAge都存在时,以后面声明的那个为准(在原生cookie中Max-Age比Expires优先级高) |
httpOnly | 布尔值,指定cookie无法通过JavaScript脚本拿到,默认为true |
maxAge | 时间戳毫秒数,设置从现在开始的有效时长,默认为 null(cookie是session cookie,只在本次会话期间保存) |
path | 指定发送请求时,哪些路径要携带cookie,只要是路径以path开头,就会携带cookie,默认为/ |
sameSite | 布尔值or字符串,(尚未完全标准化),用来防止 CSRF 和 用户追踪,取值如下: (1)true:等同于strict (2)false:不设置该属性 (3)’strict‘:完全禁止第三方cookie (4)’lax‘:除了get请求外,不携带第三方cookie (5)’none‘:关闭该属性 |
secure | 布尔值,只有在https协议下,才携带cookie,默认为false |