关于 oauth2 登录,前后端分离的项目,请教我这样的实现是符合安全规范和最佳实践的吗? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
jukanntenn
V2EX    程序员

关于 oauth2 登录,前后端分离的项目,请教我这样的实现是符合安全规范和最佳实践的吗?

  •  
  •   jukanntenn 55 天前 2090 次点击
    这是一个创建于 55 天前的主题,其中的信息可能已经有所发展或是发生改变。

    第一步:用户点击第三方登录按钮,前端向后端发送一个请求,后端生成防 CSRF 攻击的 state ,连同授权地址一块返回,假设是 https://github.com/authorize/?state=abcdefg

    第二步:前端获取到返回的授权地址,解析出 state ,存在 Cookie 中(或者存 LocalStorage ,但不知道通常的做法是存哪里?),将页面跳转至授权地址,地址中的 state 会在后续所有步骤中透传。

    第三步:用户完成授权,授权方将页面重定向到某个地址,这个地址是前端路由,前端在这个路由里解析相关参数,将授权 code ,透传的 state ,连同 Cookie 中存储的 state 发送给后端登录接口。

    第四步:后端接收到请求后,首先校验 Cookie 中的 state 和透传的 state 是否匹配,确保没有 CSRF 攻击。然后用 code 交换授权的用户信息,验证成功后即可登录这个用户。

    几个疑问:

    1. 前后端分离的项目,授权流程是这样的吗?尤其是授权后回调地址,是不是必须得是前端的路由才行?
    2. 防止 CSRF 攻击的 state ,处理方式是我说的这样吗?常规是怎么做的呢?
    10 条回复    2025-08-18 10:05:55 +08:00
    laikicka
        1
    laikicka  
       55 天前   1
    前端跳转的话建议采用授权码 + PKCE 模式
    linauror
        2
    linauror  
       54 天前
    既然是前后端分离项目,那么回调地址需要是前端页面地址,然后再由前端把参数给后端。
    整个流程没问题,但感觉 state 跟防 CSRF 是两码事,最多就是 state 作为一个校验。
    一般做 oauth2 ,会对回调地址做校验,不会涉及到 CSRF 攻击。
    JoeJoeJoe
        3
    JoeJoeJoe  
    PRO
       54 天前
    可以参考下 casdoor 的实现:

    帖子地址: https://v2ex.com/t/803669
    仓库地址: https://github.com/casdoor/casdoor
    demo 地址: https://door.casbin.com/
    chobitssp
        4
    chobitssp  
       54 天前
    前端不需要存 state 后端存到 session 里即可
    用户完成授权后 前端把 code 和 state 同时传给后端验证即可
    这 2 个一般是一次性的 后端验证一次就销毁了
    flmn
        5
    flmn  
       53 天前
    巧了,前一段我刚对这个问题深入研究过。
    先回答你问题:
    1 、是的
    2 、你这样处理不好

    我的流程是:
    1 、用户点击三方登录,前端往后端访问的不是接口请求,而是直接<a>标签指向一个后端地址
    2 、后端收到请求,生成 state 后存 session ,然后返回 redirect 头,前端浏览器收到后跳转到 IdP
    3 、完成验证后,IdP 跳转回系统的前端页面,传 state 和 code
    4 、前端拿到 state 和 code 原样调后端接口,后端从 session 拿出 state 比对,然后用 code 交换授权的用户信息,验证成功后即可登录这个用户
    5 、后端生成你自己管理的验证 token 或者直接用 session 啥的,如果你之前已有用户名/密码登录,正好跟之前的鉴权模式衔接上了。
    jukanntenn
        6
    jukanntenn  
    OP
       53 天前
    @flmn 再请教第 2 步,目前用户还是未登录状态,服务端的 session 是怎么和客户端关联的呢?
    GopherTT
        7
    GopherTT  
       53 天前 via Android
    @jukanntenn session 只是一个存储对象,你可以存任意信息,比如你在登录后通过 req.session.user 存登录信息。第二步只是存储 state 而已。假如你在使用 express-session 本质上,访问路由的使用就已经为你生成 session 对象了,在浏览器会有一个 sessionID 。可以看看 express-session 实现原理。
    jukanntenn
        8
    jukanntenn  
    OP
       53 天前
    @GopherTT 谢谢,那我明白了,本质上就是 state 存服务端,前端只透传 state ,最后服务端校验 state 。
    flmn
        9
    flmn  
       53 天前   1
    第 2 步的 session 直接用你所用 web 框架的 session 支持即可。
    举个例子,我用的是 Spring boot ,访问 session 就是标准 api 的一部分,后端存储就可以用 spring session 这个库,可以配置 session 存在 jdbc 或者 redis 里。
    具体这个 session 如何跟前端连起来的,一般是将 session id 放在 http only 的 cookie 里,但是放 cookie 这个只是实现细节,不用你去放,框架自动管理了。
    litchinn
        10
    litchinn  
       52 天前   1
    感觉你需要明确下 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 参数,授权服务器重定向回客户端时原封不动返回,最后客户端校验
    关于     帮助文档     自助推广系统     博客     API     FAQ &nbp;   Solana     3715 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 32ms UTC 00:51 PVG 08:51 LAX 17:51 JFK 20:51
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86