OAuth2 授权码模式从登录到鉴权的完整流程现在大部分应用都支持第三方登录Google、GitHub、微信扫码。背后的协议基本都是 OAuth2用的最多的就是授权码模式Authorization Code Flow。这篇文章把整个流程从头到尾拆一遍前端和服务端分别要做什么接口怎么设计token 怎么维护有什么坑一次性说清楚。授权码模式在解决什么问题传统的 session 登录用户在你这输入账号密码你存 session用户拿着 cookie 访问。但在第三方登录的场景下用户的账号密码存在认证中心比如 Google你不能让用户把密码给你你再去 Google 验证。授权码模式的核心思路是认证中心确认用户身份后给你的服务端一个临时的授权码服务端用这个码去换 token。用户密码不会经过你的应用。完整流转过程假设用户用 GitHub 登录一个笔记应用来看每一步发生了什么。第一步用户点击使用 GitHub 登录浏览器跳转到 GitHub 的授权页面。前端处理这步只需要一个链接跳转GET https://github.com/login/oauth/authorize? response_typecode client_idYOUR_CLIENT_ID redirect_urihttps://your-app.com/auth/callback scopeuser:email statexyz123第二步用户在 GitHub 上确认授权GitHub 通过 302 重定向把浏览器带到你指定的 redirect_uri并带上一个 code 参数。GET https://your-app.com/auth/callback?codeabc123statexyz123这是整个流程中关键的一步。code 是一次性的有效期通常只有几分钟。第三步服务端收到 code 后在后台用这个 code 去换 access_token。这一步是服务端到服务端的请求用户的浏览器不参与。POST https://github.com/login/oauth/access_token client_idYOUR_CLIENT_ID client_secretYOUR_CLIENT_SECRET codeabc123 redirect_urihttps://your-app.com/auth/callback为什么要经过一个 code 再换 token而不是直接返回 token因为如果直接返回 token回调 URL 里就带了 token浏览器历史记录、referer 都有可能泄露。多了一层 code确保只有你的服务端能用 client_secret 把 code 换成 token。第四步拿着 token 去认证中心获取用户信息。GET https://api.github.com/user Authorization: Bearer gho_xxxxx第五步查本地数据库。根据 GitHub 返回的 id 查找用户是否存在不存在就创建一条新记录。第六步生成你自己的 session token 或 JWT写 cookie返回给前端。后续请求就用这个 token 了。关键接口设计授权跳转接口前端需要一个接口告诉后端我要去登录了。后端返回从哪跳转。GET /api/auth/login?providergithub响应{redirect_url:https://github.com/login/oauth/authorize?...}也可以前端直接硬编码跳转地址但后端统一管理的好处是 client_id、scope、redirect_uri 都集中在后端前端不用关心这些配置。回调处理接口GET /api/auth/callback?codexxxstateyyy这是后端最核心的接口。处理逻辑1. 验证 state 是否匹配防止 CSRF 攻击 2. 用 code 换 token 3. 用 token 获取用户信息 4. 查询或创建本地用户 5. 生成本地 session token 6. set-cookie 或返回 token 7. 重定向到前端首页获取当前用户信息GET /api/auth/me Cookie: session_tokenxxx响应{id:1,email:userexample.com,name:张三,avatar:https://...,role:user}刷新 token认证中心返回的 access_token 通常有时间限制比如两小时过期。同时会返回一个 refresh_token用来换新的 access_token。POST /api/auth/refresh Cookie: session_tokenxxx后端做的事情1. 查当前用户对应的 refresh_token 2. 调用认证中心的 refresh 接口 POST https://github.com/login/oauth/access_token grant_typerefresh_token refresh_tokenxxx 3. 拿到新的 access_token 和 refresh_token 4. 更新本地存储登出POST /api/auth/logout Cookie: session_tokenxxx清除本地 session同时可以考虑调用认证中心的撤销 token 接口如果有的话。数据库设计存认证信息的表CREATETABLEuser_oauth_accounts(idSERIALPRIMARYKEY,user_idINTEGERREFERENCESusers(id),providerVARCHAR(50)NOTNULL,-- github, google, wechatprovider_account_idVARCHAR(255)NOTNULL,-- 认证中心的用户idaccess_tokenTEXT,refresh_tokenTEXT,token_expires_atTIMESTAMP,UNIQUE(provider,provider_account_id));用户表CREATETABLEusers(idSERIALPRIMARYKEY,emailVARCHAR(255),display_nameVARCHAR(255),avatar_urlTEXT,roleVARCHAR(50)DEFAULTuser,created_atTIMESTAMPDEFAULTNOW(),updated_atTIMESTAMPDEFAULTNOW());provider_account_id 用 unique 约束能防止重复绑定。token 字段加密存储会更安全至少不要明文落库。几种容易出错的情况state 参数不能省。如果没有 state攻击者可以构造一个回调链接诱导用户点击然后绑定攻击者的第三方账号到用户的账户上。服务端生成一个随机 state 存 session 里回调时比对。redirect_uri 要做白名单校验。认证中心允许你配置多个回调地址但服务端收到回调请求时要确认这个回调地址是你预先配置过的。否则攻击者可以构造一个指向恶意页面的回调链接。token 过期处理。access_token 过期后请求认证中心接口会 401。后端要做一层重试逻辑检测到 401 后用 refresh_token 换新的再重试原请求。如果 refresh_token 也过期了要求用户重新登录。多次登录的账户合并。同一个用户用 GitHub 登录一次、Google 又登录一次如果邮箱一样要不要合并成同一个本地账号这个问题要提前想清楚策略。大部分应用的做法是以 provider_account_id 为准不做自动合并用户可以手动绑定多个第三方账号。code 只能用一次。授权码是一次性的用完后立即失效。如果收到重复的 code 请求说明有人在重放攻击直接拒绝。前后端分离时的注意事项如果前端是 SPAReact/Vue后端是 API 服务回调地址指向后端后端处理完回调后需要重定向到前端页面。这时传递 session 信息的方式有两种第一种后端重定向到前端页面set-cookie 为 httpOnly cookie。后续请求自动携带 cookie。这种方式安全不需要前端关心 token 存储。第二种后端重定向时把 token 作为 URL 参数拼到前端页面的地址上302 Location: https://your-frontend.com/dashboard?tokenxxx这种方式省事但 token 会留在浏览器历史记录里。而且前端拿到 token 后要自己存到 localStorage 或 sessionStorage增加了 XSS 攻击面。推荐第一种用 httpOnly cookie。SPA 不需要手动管理 token请求自动携带也避免了 XSS 窃取 token 的风险。后端校验 cookie 中的 session_token 即可。刷新 token 的策略access_token 有效期通常两小时左右refresh_token 可以持久一些几天甚至几周。服务端需要在 access_token 过期前主动刷新或者在请求认证中心接口遇到 401 时触发刷新。后者更常见请求认证中心 API → 401 → 用 refresh_token 换新的 access_token → 更新数据库 → 用新 token 重试原请求 → 返回结果给客户端这种对前端完全透明前端不需要关心 token 什么时候过期。需要定一个定时任务清理过期的 refresh_token避免数据库里堆积大量无用的记录。总结授权码模式的核心就几个环节前端引导用户跳转认证中心 → 认证中心回调带着 code → 服务端用 code 换 token → 用 token 取用户信息 → 创建/更新本地用户 → 发 session cookie。理解每一步谁在调用谁、数据流向哪里实现起来就不复杂。关键是要把 state 校验、redirect_uri 白名单、token 刷新这些安全相关的细节处理到位。