Token与Oauth2.0
最近在做一个接第三方页面嵌入iframe的项目,用到了Token,这里记录一下相关知识。
简易版Token流程:
- 用户登录,提供用户名和密码等一系列登录信息给后端
- 后端经过数据库等校验确定该用户在平台注册过,于是返回前端结合了用户名和密码等信息,加密构成的Token
- 前端在后续的请求中在
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,有效期是七天,但是使用一次之后会自动过期:
- 后端提供refreshToken和accessToken给前端,前端拿到Token之后使用accessToken验证请求
- 如果十分钟之后accessToken过期,后端会返回一个401告知Token过期,然后这个时候前端会停止正在发送的请求和之前发送失败的请求,都放在一个请求队列中,维持pending态,并开启isRrefeshing锁
- 然后前端给后端发送refreshToken,去获取新的accessToken和refreshToken,前端拿到新的accessToken,再从pending队列中拿到之前的请求,再次请求,同时关闭isRrefreshing锁
- 如果refreshToken过期,那么才会跳转登录态(一般refreshToken过期了,accessToken一定会过期,因为只有accessToken过期,才会验证refreshToken是否过期)
Q:JWT是什么?
A:使用用户登录信息时间戳等签名的json字符串
Q:Oauth是什么意思?
A:是一种第三方授权框架。
Oauth授权过程分为以下几个步骤:
用户访问客户端,客户端向第三方跳转,用户在第三方网站拿到授权码,授权码存在前端
用户携带授权码访问自身的服务器,再从自身的服务器使用授权码+客户端ID+客户端专属认证密钥从第三方网站拿到AccessToken+refreshToken码存储在自身的服务器,再用这个Token码(HTTP)访问第三方网站服务器拿到资源,返回给自身的前端
这个方案更安全:因为TOKEN只存储在后端,前端即使截获授权码,没有客户端认证密钥也拿不到Token;而且授权码有效期极短,一般只有60秒,只做唯一的一次认证授权,授权后资源获取使用refreshTOKEN,refreshToken也过期后再次获取授权
Q: 怎么做到部署第三方网站代理到自己域名的网址上,然后嵌入自己的平台
A: 将第三方网站代理到自有域名并嵌入自有平台,核心是通过 Nginx 反向代理,将第三方网站的内容伪装成自有域名的资源:
- 用户访问
https://your-domain.com/third-party;- Nginx 接收到请求后,偷偷转发到第三方网站
https://third-party.com;- Nginx 获取第三方的响应内容,再返回给用户;
- 对用户和浏览器而言,所有资源都来自
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