面试中被问到的时候,才发现这块是个知识盲区,于是专门花了一点时间去搜集资料,然后自己理解、再整理出来自己的一篇笔记。来总结一下一些鉴权的方法,还有它们的优缺点。
# 背景
Http 本身是一个无状态的协议,这个不用多说了,单从网络连接上,服务器无法获知客户的身份。
但是,总不能进行与用户相关的操作就需要登录一次吧?所以就需要实现登录之后如何鉴权的问题。
# Cookie-Session 实现鉴权
# Cookie
一般来说,在登录成功后,可以把某些用户信息存入客户端的 Cookie 中,每次发送请求时带着 Cookie,这样,服务器就能够通过 Cookie 来知道对应的客户身份了。
Cookie 一般分为以下两种
- session cookie。这种 cookie 会随着用户关闭浏览器而清除,不会被标记任何过期时间 Expires 或最大时限 Max-Age
- Permanent cookie。和 session cookie 相反,在关闭浏览器后会被持久化存储。
请求一个网站时只能发送该网站下的 Cookie,而不能操作别的网站的 Cookie
cookie 可以由 JavaScript 代码创建:
document.cookie = 'my_cookie_name=my_cookie_value; expires=....' |
也可以由服务端通过设置响应头创建
Set-Cookie: my_cookie_name=my_cookie_value |
浏览器会自动在每个请求中加入相关 domain 下的 cookie。
# Session
Session 是另外一种记录客户状态的机制,不同的是 Cookie 保存在客户端中,Session 保存在服务器上(但其实也要利用到 Cookie,后面的机制就是)。
客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是 Session。客户端浏览器再次访问时只需要从该 Session 中查找该客户的状态就可以了。
服务器用 Session 把客户的信息临时保存在服务器上,在用户离开服务器后,Session 会被销毁。
但是这种机制有可能会对服务器造成相应的压力。
# Cookie-Session 认证
一个经典的场景就是使用 Cookie 存储一个 SessionID(SessionID 由服务端管理,进行创建和计时)。
通过验证 cookie 和 SessionID,服务器便能标记一个用户的访问信息。
认证过程大概是这样的:
- 用户使用密码、或者别的方式登录系统。
- 服务端验证后,创建一个 Session 信息,并且将 SessionID 存到 cookie,发送回浏览器
- 下次客户端再发起请求,自动带上 cookie 信息,服务端通过 cookie 获取 Session 信息进行校验。
# 缺点及安全隐患
- 只能在 web 端使用,在 APP 中,不能使用 cookie 就不能使用了。
- 如果服务器做了负载均衡,下一个操作到了另外一台服务器的时候可能就会丢失
- cookie 不能跨域
- XSS 攻击下,cookie 中的认证信息可能会被盗取。
- 可能会被 CSRF 所利用。cookie 的同源策略只是限制不同源的网站读取其他网站的 cookie,但如果利用 CSRF 攻击,同源策略并不能限制他。
# 安全设置
- HttpOnly cookie。在浏览器端,JavaScript 没有读取 cookie 的权限,防止 XSS。
- Secure cookie,只有在特定安全通道(通常是 HTTPS 下)下,才传递 cookie
- SameSite cookie:在跨域情况下,相关 cookie 无法被请求携带,主要是为了防止 CSRF。
具体还有一些,在我那篇 XSS 和 CSRF 笔记中也有提到,就不细说了
# 改进版的 Cookie-Session 认证
把上述的改进为:
- 不用 cookie 做客户端验证了,用其他的。web 下使用 localStorage,APP 中使用客户端数据库,这样也可以避免了 CSRF(不会自动发送)。
- 服务端不使用 Session,而是把 Session 信息拿出来存到数据库之中。
认证过程:
- 用户登录
- 服务器经过验证,把认证信息存入数据库,并把 key 值发送给浏览器
- 客户端拿到 key 值后存 localStorage
- 下次客户端再次请求后,把 key 值附加到 header 或者请求体内。
- 服务端通过获取的 key 来从数据库中读取信息。
这样做其实还有一个好处,在用户登录后存 key 进入 localStorage 的时候,可以利用监听 storage 事件来实现其他同源页面也保持登录状态。
# 基于 JWT 的 token 验证
JWT(JSON Web Token),即用于验证的信息是由 JSON 数据格式组成,这个 Token 是一个字符串。
上述的认证,还是需要服务端和客户端这边维持一个状态信息,但是基于 JWT 的 token 认证方案可以省去这个过程。
基于 Token 的身份验证是无状态的,我们不将用户信息存在服务器中。
# JWT 的组成
- header(消息头)
- payload(消息体,储存用户 id,用户角色等)+ 过期时间(可选)
- signature(签名)
JWT 是 JSON 格式的数据,前两部分就是 JSON 数据,第三部分 signature 是基于前两部分 header 和 payload 生成的签名。
前两部分分别通过 Base64URL 算法生成两组字符串,再和 signature 结合,三部分结合后通过 “ . ” 分割,就是最终的 token(如 xxxxxx.yyyyy.zzzz)
签名的作用就是保证 JWT 没有被篡改过。签名基于前两个编码过的字符串,以某种不可逆算法加密(如 HMAC 算法)比较。
# 认证过程
- 用户登录系统
- 服务端验证,将认证信息通过指定的算法(如 HS256)进行加密,将加密的结果发送到客户端
- 客户端拿到返回的 token,存到某个地方(可以是 localStorage 或 SessionStorage 或 cookie,后面比较哪种好)
- 下次客户端发送的时候,将 token 附加到 header 中(放 url 中也可以,但是放在 header 中更安全)
- 服务端获取 header 中的 token,通过相同的算法对 token 中的信息进行验证,验证 token 中的 signature 和 payload 是否等同,就可知道是否被中间人更改。
# 存储位置
一般 JWT 在客户端的存储有三种:
- localStorage
- SessionStorage
- cookie(不能设置 Http only)
一般来说比较推荐存放在 session cookie 中(上面提到的临时 cookie),前两种存在跨域读取限制。
就算是存临时 cookie,也和上面传统的 cookie-session 机制是不一样的,我们只是存在那,并不是利用它去鉴权,实际上还是需要我们手动将 cookie 加到 header 上
当然其实也可以存在 localStorage,不过有几点不好就是:
- 当用户关闭浏览器后,JWT 仍然会被存储在本地存储,除非手动更新或清理。
- 任何 JavaScript 都可以轻易获取本地存储的内容
- 无法被 Web Worker 使用
并不是说一定要存在临时 cookie,结合具体场景选择。
# 优点
- 使用 json 作为数据传输,轻量级。
- 服务端无需保存状态,节省服务端资源。也方便横向扩展
- 可以实现单点登录
- 防 CSRF(token 的优势)
# 总结
- token 获取到后需要保存到某个地方待下次使用,最好是临时 cookie,或者 localstorage、sessionStorage
- token 是有有效期的。
- localStorage 和 sessionStorage 的跨域限制较为严格。
- token 每次请求中都会编码到请求中,注意 token 大小。
- 存储敏感信息时记得加密
# 混合 JWT 和 Cookie 进行鉴权
晚点填坑
# OAuth 认证
比较常见的就是用微信登录、QQ 登录或其他比较权威的网站开放的 API 来实现用户登录。
OAuth 允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站(例如,视频编辑网站) 在特定的时段(例如,接下来的 2 小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth 让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容。—— 维基百科