理解 Web 中的Session
===================================
Session 工作原理是什么?
===================================
因为 http 协议是无状态的, 对于服务器端来讲, 如何为不同的访问用户提供不一样的体验呢? 比如邮箱系统, 只有登录用户才能收发邮件.
这就需要服务器能识别每一个客户端访问, 知道哪些访问是来自一个同一个客户端, 显然这个事情光靠服务器端是做不到的, 需要浏览器配合才行, 浏览器端 cookie 概念就这么产生了.
Session 工作原理:
(1) 当一个 Session 第一次被启动时, 服务器端先产生一个唯一的 session id, 并为该 session id 分配一个内存区, 用来保存该 session 相关的信息. 然后将该 id 存储到浏览器的 cookie 中, cookie name 为 JSESSIONID.
(2) 浏览器每次访问, 都会带上本域下所有的 cookie, 包括这个 JSESSIONID cookie(应该是放到 Http Header 上的), 服务器端通过对比 JSESSIONID 和服务器内存中的 session id, 就能识别哪些访问是来自同一个客户端.
(3) 服务器端在每次访问中, 都可以在 JSESSIONID 对应的 session 内存区中记录一些信息, 比如最后一次的访问时间, 比如访问 IP 值, 以满足业务需求.
===================================
Session 的用途有哪些?
===================================
1. 核心功能: 是识别每一个访问.
2. 基本功能: 做登录验证, 然后就能得到登录 id, 进而能完成权限验证.
核心功能和基本功能都由 Spring Web 框架提供, 或 Shiro 这样安全框架提供, 我们的代码不用直接处理这些事情.
3. 利用 Session, 可以衍生出更多的功能, 比如保存访客上次访问的 IP 和上次访问时间, 比如保存 user Id 和 user name, 这里稍微要多想一下:
(1). 这些数据既可以保存到 DB 中, 也可以保存到 Session 内存中, user id 和 user name 在 DB 中本来就保存着, 有必要在 session 中保存吗?
Session 的优点是访问速度快, 缺点是用户量大的情况下内存占用是个问题.
(2). 这些数据既可以保存到 cookie , 也可以保存到 Session 内存中, 哪个更适合?
Cookie 的优点是: 不占用服务器端内存, Session 占服务器内存并且重启后就没了, cookie 可以被js访问到, 常用的网页埋点就是把一些标记信息保存到cookie, 然后由js提交到埋点服务器中.
Cookie 的缺点是, cookie 数据如果较大, 会消耗较多的网络带宽; 用户随意修改 cookie 值, 这样的话, cookie的数据就不太可靠了; 另外, 用户换个浏览器或者浏览器清个缓存, 原有的cookie就没法用了.
这些问题都没有标准答案, 按照具体情形做选择吧.
===================================
JSESSIONID Cookie 是到底是在什么时候生成?
===================================
JSESSIONID Cookie 并不是在第一次访问网站时候立即生成的, 只有访问 url 对应视图函数中有 Session 相应操作时, 浏览器端才会创建 JSESSIONID cookie 的.
当然, 一般网站在首页访问的视图函数基本都会有 session 操作, 所以给我们的错觉是, 一旦新访问一个网站, JSESSIONID cookie 就自动产生了.
比如访问 http://localhost:8080/ 主页, 访问第一个测试代码的首页, 并不会创建 JSESSIONID cookie.
// 该 url 请求没有任何与 session 相关的操作, 浏览器端不会创建该网站的 JSESSIONID cookie @GetMapping("/") public String index(HttpServletRequest request) throws SQLException { return "OK"; } // 在 url 请求中, 有获取 session 对象的指令, 浏览器端会创建该网站的 JSESSIONID cookie @GetMapping("/") public String index(HttpServletRequest request) throws SQLException { request.getSession(); //获取 session 对象 return "OK"; } // 在 url 请求中, 视图函数要注入 session 对象, 浏览器端会创建该网站的 JSESSIONID cookie @GetMapping("/") public String index(HttpSession session) throws SQLException { return "OK"; }
===================================
JSESSIONID 的 ID 值会登录后会变化吗?
===================================
假设在登录之前, 浏览器端已经有了 JSESSIONID cookie. 现在问题来了, 在用户登录之后, 该 JSESSIONID ID 值是否变化?
答案是: 早期网站, 登录前后 JSESSIONID 是不变的, 但后来发现, 如果登录前后 JSESSIONID 不变, 会有中间人攻击风险. 所以现在的安全框架在登录完成后, 会自动修改 JSESSIONID 值. 我们开发网站不需要再专门修改 JSESSIONID.
和登录不一样, 登录退出 JSESSIONID 的 Id 值并不会改变.
===================================
Session 和 cookie 之间的关系?
===================================
前面已经提到为了标识同一个客户端访问, 光靠服务器端的 Session 是不够的, 还需要客户端 JSESSIONID Cookie 支持. 这体现了 Session 和 cookie 之间最重要的一个关系.
另外, 在 Web 开发中 Session 和 Cookie 有一些很类似的方法, 往往会引起混淆, 有必要区别一下.
1. Session:
- 保存在服务器端内存中, 服务器重启所有的 session 都没了.
- session.setAttribute() 是在服务端内存中保存一个属性, 并不会将它保存到浏览器端的 Cookie 中.
- 可以在一个 session id 下新建多个属性.
- session 的有效期是针对所有 session id 全局级别的, 不是单个 session id 级, 也不是属性级的.
- Session 中 id 和对应的属性, 可以大致理解为从属关系, 这些属性属于一个 id.
2. Cookie:
- 保存在浏览器本地存储中, 服务器重启并不会影响 cookie.
- 一个网站下可以包含多个 cookie , 最重要的那个是 JSESSIONID Cookie. 新建一个 cookie 并不会在服务器端生成一个对应的属性.
- 每个 cookie 都可以设置自己的有效期, 自己的 HttpOnly 属性.
- 一个网站的 JSESSIONID Cookie 和其他 cookie 并没有从属关系
===================================
Spring 中 Session 和 cookie 的操作方式
===================================
Spring Session操作很简单:
- //获取 session 对象, 或者视图函数中直接注入 HttpSession 对象
- request.getSession()
- //设置一个 session 属性
- session.setAttribute()
- //读取一个 session 属性
- session.getAttribute()
1. 在视图函数中使用 @CookieValue 修饰形参, 可注入一个 cookie 属性值.
@RequestMapping("/read") @ResponseBody public String read(@CookieValue(value = "foo", defaultValue = "hello") String fooCookie) { System.out.println(fooCookie); return fooCookie; }
2. 使用 HttpServletResponse.addCookie() 完成 cookie 的写入.
@RequestMapping("/write") @ResponseBody public String write(HttpServletResponse response) { Cookie foo = new Cookie("foo", "bar"); //bake cookie foo.setMaxAge(1000); //set expire time to 1000 sec response.addCookie(foo); //put cookie in response return "write done"; }
==================================
ajax 调用过程中是否会附加cookie信息?
==================================
如果 web 应用通过 jquery ajax 调用后台 api url, 调用过程是否会附加cookie信息, 要看情况:
1. 如果是同域, ajax 请求会自动带上本域的cookie, 这台web服务器本身也有相应的 session 信息, 所以ajax请求自然能通过 session-cookie 完成身份验证, 整个过程非常自然.
2. 如果 ajax调用的api 是另一个域下的url, 因为不是同域, 需要手动在ajax调用时加上cookie, 可参考:https://blog.csdn.net/wzl002/article/details/51441704, 服务器端返回response也需要做相应的处理, 另外也需要引入分布式session存储.
==================================
SSO/JWT/Session/OAuth2和web项目安全检查的关系
==================================
1. OAuth2: 它是一个第三方认证机制, 适合To C的应用场景, 比如我们开发一个app, 可以借用微信/微博用户认证开放接口, 达到免注册登陆, 企业内部系统一般没必要引入该技术.
2. SSO: 有两个作用, 1: 保持登陆状态, 2: 识别 username
3. JWT: 它是一个 token 生成技术, 可以包含 username 等信息.
4. Session: Session 一般用在web application中, 用来识别访问者.
一般项目认证方式:
1. 对于 web application 项目, 一般使用 Session 来识别访问者, 开发容易, 可以采用普通的 Login form 登陆, 也可以采用SSO登陆.
SSO 登陆详细过程, 可参考张开涛博客<<shiro 单点登录>> https://www.vxzsk.com/1197.html
2. 对于微服务项目, 一般和 OAuth2/Session 无关, 多数基于 jwt 的无状态认证机制, 通过 jwt 来识别访问者, 不过还是推荐将jwt前移到api 网关较好, 微服务仅仅关注业务逻辑.
完整微服务认证过程是: SSO登陆验证通过后, SSO回调到统一auth服务来生成一个jwt token, 并传给客户端, 客户端可将 jwt token保存到本地(web 客户端可保存到 cookie 中), 每次客户端请求需要将 jwt token 加到 http Authorization header上, 服务器端仅需要 jwt 算法就可以完成验证(通常不会再查询数据库验证), 并能提取 username 信息.
详细过程和源码, 可参考下面几个文章:
张开涛博客<<shiro 无状态Web集成>>
https://www.vxzsk.com/1233.html
详细shiro jwt 构建无状态分布式鉴权体系
https://www.jianshu.com/p/0a5d3d07a151
https://www.jianshu.com/p/b0a577708a7b
Shiro+JWT+Spring Boot Restful简易教程
https://www.jianshu.com/p/f37f8c295057