Token、Oauth2.0与单点登录

30 阅读6分钟
Token与Oauth2.0

最近在做一个接第三方页面嵌入iframe的项目,用到了Token,这里记录一下相关知识。

简易版Token流程:

  1. 用户登录,提供用户名和密码等一系列登录信息给后端
  2. 后端经过数据库等校验确定该用户在平台注册过,于是返回前端结合了用户名和密码等信息,加密构成的Token
  3. 前端在后续的请求中在Authorization 请求头提供给后端这个Token,用于验证该用户信息以及维持登录态

但是以上引发了另一个问题:

Q: Token是在请求中等于是明文传输,也就是可以轻而易举得拦截,存在信息冒充和顶替的风险,应该怎处理这种安全泄露问题?

(1.1) 缩短Token的有效期,如果Token只有10分钟,那么等到Token被窃取并且使用的时候,Token可能就已经过期了

(1.2) 使用IP,使用上下文场景,异地登陆等场景辅助校验是否Token被窃取使用

Q: 如果Token只有十分钟,那么如果有个场景让用户填特别复杂的表单,等到用户终于完成前置冗余的操作,准备提交表单,突然弹出信息,需要重新登录,前置信息可能作废,怎么办

服务端在提供Token的时候会提供accessToken和RefreshToken,其中accessToken就是上文用于信息验证的Token,有效期是10分钟,refreshToken则会存储在cookies,有效期是七天,但是使用一次之后会自动过期:

  1. 后端提供refreshToken和accessToken给前端,前端拿到Token之后使用accessToken验证请求
  2. 如果十分钟之后accessToken过期,后端会返回一个401告知Token过期,然后这个时候前端会停止正在发送的请求和之前发送失败的请求,都放在一个请求队列中,维持pending态,并开启isRrefeshing锁
  3. 然后前端给后端发送refreshToken,去获取新的accessToken和refreshToken,前端拿到新的accessToken,再从pending队列中拿到之前的请求,再次请求,同时关闭isRrefreshing锁
  4. 如果refreshToken过期,那么才会跳转登录态(一般refreshToken过期了,accessToken一定会过期,因为只有accessToken过期,才会验证refreshToken是否过期)

Q:JWT是什么?

A:使用用户登录信息时间戳等签名的json字符串

Q:Oauth是什么意思?

A:是一种第三方授权框架。

Oauth授权过程分为以下几个步骤:

  1. 用户访问客户端,客户端向第三方跳转,用户在第三方网站拿到授权码,授权码存在前端

  2. 用户携带授权码访问自身的服务器,再从自身的服务器使用授权码+客户端ID+客户端专属认证密钥从第三方网站拿到AccessToken+refreshToken码存储在自身的服务器,再用这个Token码(HTTP)访问第三方网站服务器拿到资源,返回给自身的前端

  3. 这个方案更安全:因为TOKEN只存储在后端,前端即使截获授权码,没有客户端认证密钥也拿不到Token;而且授权码有效期极短,一般只有60秒,只做唯一的一次认证授权,授权后资源获取使用refreshTOKEN,refreshToken也过期后再次获取授权

Q: 怎么做到部署第三方网站代理到自己域名的网址上,然后嵌入自己的平台

A: 将第三方网站代理到自有域名并嵌入自有平台,核心是通过 Nginx 反向代理,将第三方网站的内容伪装成自有域名的资源

  1. 用户访问 https://your-domain.com/third-party
  2. Nginx 接收到请求后,偷偷转发到第三方网站 https://third-party.com
  3. Nginx 获取第三方的响应内容,再返回给用户;
  4. 对用户和浏览器而言,所有资源都来自 your-domain.com,不存在跨域问题,可直接嵌入 iframe
单点登录

目的是:一次登录,同一家公司下的所有系统都实现自动登录,不用重复输账号密码

现在有三个网址,业务A a.com,业务B b.com,SSO网址 sso.com

(1)用户登录业务A网址,发现A本地没有登录态(Cookie里面没有Token),重定向到SSO网址,query里面携带来源的业务网址a.com

https://sso.com/login?redirect=https://a.com

(2)SSO中心也发现没有登录态(Cookie里面没有Token),用户输入账号信息,获取全局登录会话Cookie,比如sso-session-id=xxx放在Cookie里面,和业务登录的Token没有任何关系,同时SSO后端直接302重定向到业务A系统,重定向地址把Ticket拼在URL里面

Ticket有效期只有30s-3分钟,超时作废;会被浏览器加密;Ticket加密信息是由业务系统url和用户id一起生成的,只能在该用户和业务系统下使用,由此保证安全性。

302 Location: https://a.com/callback?ticket=J8S72KSL91JD

同时SSO后端会存储一份信息:

ticket: "TICKET-ABC123"
userId: 1001
service: "https://a.com"
used: false
expireAt: 2025-xx-xx 10:00:03

(3)业务后端从url里获取ticket,业务后端后台调用SSO校验接口,会传递上ticket和请求的业务网址:

POST https://sso.com/validateTicket 
Body: { ticket: "xxx", service: "a.com" }

然后业务后端获取SSO后端提供的用户信息,业务后端再生成自己的Token,Token通常通过set-Cookie上传递过来;也有直接通过json传过来的,也就是说SSO只管登录,保持用户信息的统一,不负责鉴权

(4)业务系统B再想访问SSO.com的时候,发现SSO.com的Cookie里面已经有了sso-session-id=xxx,会自动在给SSO的后端请求里带上,于是会自动由业务系统B和用户信息一起加密生成一个Ticket,重定向到业务系统B,后续的过程同业务系统A

Set-Cookie
  • 对于 refreshToken:必须Set-Cookie + HttpOnly + Secure + SameSite 保护,禁止前端读取;

  • 对于 accessToken:可通过 Set-Cookie(非 HttpOnly)或 JSON 返回(存内存),但优先结合场景选择更安全的方式。

(1) HttpOnly:js脚本无法读取,比如document.cookie无法读取到cookie;也禁止修改该Cookie

(2) Secure: 仅允许在Https的协议下传输,Http请求下不会传输该信息

(3) SameSite:strict 禁止跨域请求携带Cookie;none 跨站请求携带Cookie

# 传递 refreshToken(核心:HttpOnly + Secure + SameSite,前端无法读取)
Set-Cookie: refreshToken=xxx; Path=/; Domain=a.com; HttpOnly; Secure; SameSite=Strict; Max-Age=604800

# 传递 accessToken(可选:非 HttpOnly,前端可读取,或直接存内存)
Set-Cookie: accessToken=xxx; Path=/; Domain=a.com; Secure; SameSite=Strict; Max-Age=3600

核心原因: 防止XSS攻击,在返回的请求JSON里面,前端js脚本是可以完全爬取到请求的内容的,但是如果在Set-Cookie里面设置了HttpOnly + Secure + SameSite,前端的js脚本就没办法爬取了;除此之外还是自动携带该Token