第一步:用户点击第三方登录按钮,前端向后端发送一个请求,后端生成防 CSRF 攻击的 state ,连同授权地址一块返回,假设是 https://github.com/authorize/?state=abcdefg
第二步:前端获取到返回的授权地址,解析出 state ,存在 Cookie 中(或者存 LocalStorage ,但不知道通常的做法是存哪里?),将页面跳转至授权地址,地址中的 state 会在后续所有步骤中透传。
第三步:用户完成授权,授权方将页面重定向到某个地址,这个地址是前端路由,前端在这个路由里解析相关参数,将授权 code ,透传的 state ,连同 Cookie 中存储的 state 发送给后端登录接口。
第四步:后端接收到请求后,首先校验 Cookie 中的 state 和透传的 state 是否匹配,确保没有 CSRF 攻击。然后用 code 交换授权的用户信息,验证成功后即可登录这个用户。
几个疑问:
![]() |
1
laikicka 49 天前 ![]() 前端跳转的话建议采用授权码 + PKCE 模式
|
2
linauror 49 天前
既然是前后端分离项目,那么回调地址需要是前端页面地址,然后再由前端把参数给后端。
整个流程没问题,但感觉 state 跟防 CSRF 是两码事,最多就是 state 作为一个校验。 一般做 oauth2 ,会对回调地址做校验,不会涉及到 CSRF 攻击。 |
![]() |
3
JoeJoeJoe PRO 可以参考下 casdoor 的实现:
帖子地址: https://v2ex.com/t/803669 仓库地址: https://github.com/casdoor/casdoor demo 地址: https://door.casbin.com/ |
4
chobitssp 48 天前
前端不需要存 state 后端存到 session 里即可
用户完成授权后 前端把 code 和 state 同时传给后端验证即可 这 2 个一般是一次性的 后端验证一次就销毁了 |
5
flmn 48 天前
巧了,前一段我刚对这个问题深入研究过。
先回答你问题: 1 、是的 2 、你这样处理不好 我的流程是: 1 、用户点击三方登录,前端往后端访问的不是接口请求,而是直接<a>标签指向一个后端地址 2 、后端收到请求,生成 state 后存 session ,然后返回 redirect 头,前端浏览器收到后跳转到 IdP 3 、完成验证后,IdP 跳转回系统的前端页面,传 state 和 code 4 、前端拿到 state 和 code 原样调后端接口,后端从 session 拿出 state 比对,然后用 code 交换授权的用户信息,验证成功后即可登录这个用户 5 、后端生成你自己管理的验证 token 或者直接用 session 啥的,如果你之前已有用户名/密码登录,正好跟之前的鉴权模式衔接上了。 |
6
jukanntenn OP @flmn 再请教第 2 步,目前用户还是未登录状态,服务端的 session 是怎么和客户端关联的呢?
|
7
GopherTT 48 天前 via Android
@jukanntenn session 只是一个存储对象,你可以存任意信息,比如你在登录后通过 req.session.user 存登录信息。第二步只是存储 state 而已。假如你在使用 express-session 本质上,访问路由的使用就已经为你生成 session 对象了,在浏览器会有一个 sessionID 。可以看看 express-session 实现原理。
|
8
jukanntenn OP @GopherTT 谢谢,那我明白了,本质上就是 state 存服务端,前端只透传 state ,最后服务端校验 state 。
|
9
flmn 47 天前 ![]() 第 2 步的 session 直接用你所用 web 框架的 session 支持即可。
举个例子,我用的是 Spring boot ,访问 session 就是标准 api 的一部分,后端存储就可以用 spring session 这个库,可以配置 session 存在 jdbc 或者 redis 里。 具体这个 session 如何跟前端连起来的,一般是将 session id 放在 http only 的 cookie 里,但是放 cookie 这个只是实现细节,不用你去放,框架自动管理了。 |
![]() |
10
litchinn 47 天前 ![]() 感觉你需要明确下 OAuth2 中的几个[角色]( https://datatracker.ietf.org/doc/html/rfc6749#section-1.1)
不知道在你的场景中授权服务器和资源服务器是不是同一个服务,即使它们是同一个服务你也要清楚它们是两个角色 现在假设他们是分开的两个服务,你的后端作为资源服务器,前端是一个客户端 问题 1: 你的请求从哪里发起的就应该从哪里接受回调,你的前后端应该有不同的 clientId ,并且前端应该是个 public client ,也就是没有 secret ,这需要 PKCE 问题 2: https://datatracker.ietf.org/doc/html/rfc6749#section-10.12 这里说的很清楚 > The binding value enables the client to verify the validity of the request by matching the binding value to the user-agent's authenticated state 由客户端校验,也就是客户端发起 authorization 请求时携带 state 参数,授权服务器重定向回客户端时原封不动返回,最后客户端校验 |