OAuth2 授权机制详解

OAuth2 授权机制详解

OAuth2 是什么

假设用户在二狗商城中点击"使用 GitHub 登录"。

二狗商城不能要求用户直接输入 GitHub 的账号和密码,否则用户的 GitHub 密码就会暴露给二狗商城。正确流程是:
#mermaid-svg-vRQJwPuoAG1Tu7St{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vRQJwPuoAG1Tu7St .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vRQJwPuoAG1Tu7St .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vRQJwPuoAG1Tu7St .error-icon{fill:#552222;}#mermaid-svg-vRQJwPuoAG1Tu7St .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vRQJwPuoAG1Tu7St .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vRQJwPuoAG1Tu7St .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vRQJwPuoAG1Tu7St .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vRQJwPuoAG1Tu7St .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vRQJwPuoAG1Tu7St .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vRQJwPuoAG1Tu7St .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vRQJwPuoAG1Tu7St .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vRQJwPuoAG1Tu7St .marker.cross{stroke:#333333;}#mermaid-svg-vRQJwPuoAG1Tu7St svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vRQJwPuoAG1Tu7St p{margin:0;}#mermaid-svg-vRQJwPuoAG1Tu7St .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vRQJwPuoAG1Tu7St .cluster-label text{fill:#333;}#mermaid-svg-vRQJwPuoAG1Tu7St .cluster-label span{color:#333;}#mermaid-svg-vRQJwPuoAG1Tu7St .cluster-label span p{background-color:transparent;}#mermaid-svg-vRQJwPuoAG1Tu7St .label text,#mermaid-svg-vRQJwPuoAG1Tu7St span{fill:#333;color:#333;}#mermaid-svg-vRQJwPuoAG1Tu7St .node rect,#mermaid-svg-vRQJwPuoAG1Tu7St .node circle,#mermaid-svg-vRQJwPuoAG1Tu7St .node ellipse,#mermaid-svg-vRQJwPuoAG1Tu7St .node polygon,#mermaid-svg-vRQJwPuoAG1Tu7St .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vRQJwPuoAG1Tu7St .rough-node .label text,#mermaid-svg-vRQJwPuoAG1Tu7St .node .label text,#mermaid-svg-vRQJwPuoAG1Tu7St .image-shape .label,#mermaid-svg-vRQJwPuoAG1Tu7St .icon-shape .label{text-anchor:middle;}#mermaid-svg-vRQJwPuoAG1Tu7St .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vRQJwPuoAG1Tu7St .rough-node .label,#mermaid-svg-vRQJwPuoAG1Tu7St .node .label,#mermaid-svg-vRQJwPuoAG1Tu7St .image-shape .label,#mermaid-svg-vRQJwPuoAG1Tu7St .icon-shape .label{text-align:center;}#mermaid-svg-vRQJwPuoAG1Tu7St .node.clickable{cursor:pointer;}#mermaid-svg-vRQJwPuoAG1Tu7St .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vRQJwPuoAG1Tu7St .arrowheadPath{fill:#333333;}#mermaid-svg-vRQJwPuoAG1Tu7St .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vRQJwPuoAG1Tu7St .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vRQJwPuoAG1Tu7St .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vRQJwPuoAG1Tu7St .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vRQJwPuoAG1Tu7St .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vRQJwPuoAG1Tu7St .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vRQJwPuoAG1Tu7St .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vRQJwPuoAG1Tu7St .cluster text{fill:#333;}#mermaid-svg-vRQJwPuoAG1Tu7St .cluster span{color:#333;}#mermaid-svg-vRQJwPuoAG1Tu7St div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vRQJwPuoAG1Tu7St .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vRQJwPuoAG1Tu7St rect.text{fill:none;stroke-width:0;}#mermaid-svg-vRQJwPuoAG1Tu7St .icon-shape,#mermaid-svg-vRQJwPuoAG1Tu7St .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vRQJwPuoAG1Tu7St .icon-shape p,#mermaid-svg-vRQJwPuoAG1Tu7St .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vRQJwPuoAG1Tu7St .icon-shape .label rect,#mermaid-svg-vRQJwPuoAG1Tu7St .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vRQJwPuoAG1Tu7St .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vRQJwPuoAG1Tu7St .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vRQJwPuoAG1Tu7St :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户访问二狗商城
跳转到 GitHub
用户登录并同意授权
GitHub 返回一次性 Code
二狗商城后端用 Code 换 Access Token
使用 Access Token 调用 GitHub API

这里要区分两个概念:

  • 认证 Authentication:确认当前用户是谁。
  • 授权 Authorization:确认某个客户端可以访问哪些资源。

OAuth2 主要解决授权。很多"微信登录""GitHub 登录",实际上是先通过 OAuth2 获得 Access Token,再调用用户信息接口获得外部用户 ID,最后在本系统中建立本地登录状态。

OAuth2 中有四个角色:

  • Resource Owner:资源所有者,通常就是用户。
  • Client:希望访问用户资源的客户端,例如二狗商城。
  • Authorization Server:授权服务器,负责用户登录、授权确认以及签发 Code 和 Token。
  • Resource Server:资源服务器,保存受保护资源,并负责校验 Access Token。

以 GitHub 登录为例,用户是 Resource Owner,二狗商城是 Client,GitHub 授权服务是 Authorization Server,GitHub 用户信息和仓库 API 是 Resource Server。

Authorization Code 授权码模式

授权码模式是 OAuth2 中最常用、最重要的流程。客户端接入授权服务器前,需要先在授权服务器注册信息:

  • client_id:客户端的公开标识,表示是谁发起授权。
  • client_secret:客户端密钥,用来证明请求来自合法客户端后端。
  • redirect_uri:授权结束后的回调地址。
  • scope:客户端允许申请的权限范围。

client_id 可以出现在浏览器地址中,不属于秘密;client_secret 不能放在浏览器、前端代码、URL 或 App 安装包中,因为这些环境都可能被用户查看。

用户发起授权时,Client 将浏览器重定向到 Authorization Endpoint:

text 复制代码
https://sso.example.com/authorize
    ?response_type=code
    &client_id=order-system
    &redirect_uri=https://order.example.com/callback
    &scope=openid profile
    &state=abc123
    &code_challenge=xxxx
    &code_challenge_method=S256

各参数含义如下:

  • response_type=code:希望授权服务器返回授权码。
  • client_id:哪个客户端发起授权请求。
  • redirect_uri:授权完成后回调到哪个地址。
  • scope:客户端申请访问哪些资源。
  • state:关联授权请求和回调,主要防止 CSRF。
  • code_challenge:由 code_verifier 计算出的 PKCE 挑战值。
  • code_challenge_method=S256:使用 SHA-256 计算挑战值。

scope 表示客户端申请的权限,例如 profile 表示读取基础资料,email 表示读取邮箱,order.read 表示读取订单。最终签发的权限不能超过客户端允许申请的权限、用户同意的权限以及用户实际拥有的权限。

授权服务器收到请求后,会检查 client_idredirect_uriscope 是否合法。用户登录并同意授权后,授权服务器生成一个短期、一次性的 Code,并将浏览器重定向回 Client:

text 复制代码
https://order.example.com/callback
    ?code=code-xyz
    &state=abc123

Client 首先比较返回的 state 是否与之前保存的一致。校验通过后,由 Client 后端使用 Code 请求 Token Endpoint:

这个 state 是 Client 生成的

http 复制代码
POST /oauth2/token HTTP/1.1
Host: sso.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=code-xyz
&client_id=order-system
&redirect_uri=https://order.example.com/callback
&code_verifier=原始随机值

有安全后端的 Client 还可以提交 client_secret

授权服务器会检查:

  • Code 是否存在、是否过期、是否已经使用。
  • Code 是否属于当前 client_id
  • redirect_uri 是否与授权请求时一致。
  • code_verifier 是否能匹配之前的 code_challenge
  • 客户端身份是否可信。

校验成功后,Code 立即失效,授权服务器返回:

json 复制代码
{
  "access_token": "access-xxx",
  "refresh_token": "refresh-yyy",
  "id_token": "id-zzz",
  "token_type": "Bearer",
  "expires_in": 1800,
  "scope": "openid profile"
}

Client 再使用 Access Token 访问 Resource Server:

http 复制代码
GET /api/userinfo HTTP/1.1
Host: api.example.com
Authorization: Bearer access-xxx

完整流程如下:
Resource Server Authorization Server Client 用户/浏览器 Resource Server Authorization Server Client 用户/浏览器 #mermaid-svg-ZObpbCpycIlIoFqO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZObpbCpycIlIoFqO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZObpbCpycIlIoFqO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZObpbCpycIlIoFqO .error-icon{fill:#552222;}#mermaid-svg-ZObpbCpycIlIoFqO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZObpbCpycIlIoFqO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZObpbCpycIlIoFqO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZObpbCpycIlIoFqO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZObpbCpycIlIoFqO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZObpbCpycIlIoFqO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZObpbCpycIlIoFqO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZObpbCpycIlIoFqO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZObpbCpycIlIoFqO .marker.cross{stroke:#333333;}#mermaid-svg-ZObpbCpycIlIoFqO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZObpbCpycIlIoFqO p{margin:0;}#mermaid-svg-ZObpbCpycIlIoFqO .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZObpbCpycIlIoFqO text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-ZObpbCpycIlIoFqO .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ZObpbCpycIlIoFqO .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-ZObpbCpycIlIoFqO .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-ZObpbCpycIlIoFqO .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-ZObpbCpycIlIoFqO #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-ZObpbCpycIlIoFqO .sequenceNumber{fill:white;}#mermaid-svg-ZObpbCpycIlIoFqO #sequencenumber{fill:#333;}#mermaid-svg-ZObpbCpycIlIoFqO #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-ZObpbCpycIlIoFqO .messageText{fill:#333;stroke:none;}#mermaid-svg-ZObpbCpycIlIoFqO .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZObpbCpycIlIoFqO .labelText,#mermaid-svg-ZObpbCpycIlIoFqO .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-ZObpbCpycIlIoFqO .loopText,#mermaid-svg-ZObpbCpycIlIoFqO .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-ZObpbCpycIlIoFqO .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ZObpbCpycIlIoFqO .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ZObpbCpycIlIoFqO .noteText,#mermaid-svg-ZObpbCpycIlIoFqO .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-ZObpbCpycIlIoFqO .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZObpbCpycIlIoFqO .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZObpbCpycIlIoFqO .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZObpbCpycIlIoFqO .actorPopupMenu{position:absolute;}#mermaid-svg-ZObpbCpycIlIoFqO .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-ZObpbCpycIlIoFqO .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZObpbCpycIlIoFqO .actor-man circle,#mermaid-svg-ZObpbCpycIlIoFqO line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-ZObpbCpycIlIoFqO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. 发起授权或第三方登录2. 生成 state 和 code_verifier3. 计算 code_challenge4. 重定向到 /authorize5. 携带 client_id、scope、state、code_challenge6. 校验客户端参数7. 展示登录和授权页面8. 用户登录并同意授权9. 返回 code 和原始 state10. 浏览器访问 callback11. 校验 state12. 后端使用 code + code_verifier 换 Token13. 校验 Code、客户端和 PKCE14. 返回 Access Token 等15. 携带 Access Token 访问资源16. 校验 Token 和 scope17. 返回受保护资源

为什么要使用 Code

授权服务器完成授权时,正在和浏览器通信。如果直接把 Access Token 放进回调 URL:

text 复制代码
https://order.example.com/callback?access_token=xxx

Token 可能进入浏览器历史、Nginx 日志、Referer、监控系统或第三方脚本。

因此授权码模式把流程拆成两步:

  1. 浏览器只负责传递短期、一次性的 Code。
  2. Client 后端再使用 Code 向授权服务器换取真正的 Token。

真正的 Token 通过 Client 后端和授权服务器之间的后端通信返回,不经过浏览器 URL。

为什么通常放在 URL?因为授权完成后,授权服务器需要让浏览器自动跳回 Client,而 HTTP 302 重定向只能通过 Location 指定下一个访问地址,所以通常把 code 拼在回调 URL 上。若只放在认证服务器的响应体中,浏览器不会自动把这段响应体转发给 Client。当然也可以使用 form_post,让浏览器通过 POST 请求体提交 code,只是 URL 回调更简单、通用

state 和 PKCE

state 和 PKCE 解决的不是同一个问题。

state 由 Client 在发起授权前生成,并保存在当前用户的 Session 或安全 Cookie 中。授权服务器回调时将它原样返回,Client 比较前后是否一致。

假设攻击者伪造一个回调地址诱导用户访问,如果回调中的 state 与用户浏览器中保存的不一致,或者用户根本没有发起过这次授权,Client 就会拒绝处理。因此,state 可以防止攻击者伪造授权回调,也能避免多次授权请求之间发生串线。

PKCE 用于防止 Authorization Code 被截获。Client 先生成一个随机的 code_verifier,自己保存,然后计算:

java 复制代码
code_challenge = Base64Url(SHA256(code_verifier))

发起授权时只发送 code_challenge,授权服务器会将它与 Code 绑定;换 Token 时,Client 必须提交原始的 code_verifier。攻击者即使截获了 Code,因为没有 code_verifier,也无法通过校验并换取 Access Token。

  • state:证明这次回调对应当前用户之前发起的授权请求。
  • PKCE:证明兑换 Code 的 Client,就是最初发起授权请求的 Client。

Access Token、Refresh Token 和 ID Token

Access Token 是访问 Resource Server 的凭证:

http 复制代码
Authorization: Bearer <AccessToken>

如果 Access Token 是 JWT,可能包含:

json 复制代码
{
  "sub": "10001",
  "scope": "order.read",
  "iss": "https://sso.example.com",
  "aud": "order-service",
  "exp": 1785493800
}
  • sub:当前用户或主体是谁。
  • scope:允许访问哪些资源。
  • iss:由哪个授权服务器签发。
  • aud:Token 签发给哪个 Resource Server。
  • exp:Token 的过期时间。

授权服务器负责决定并签发 scope,Gateway 或 Resource Server 根据 scope 执行真正的接口鉴权。

如果你的系统使用的 RBAC 三级鉴权,Gateway 可以根据 Token 中的 userId 查询 Redis 或权限服务,获取用户的角色和权限后完成接口鉴权;也可以在登录时将权限写入 Token 的 permissionsscope 中,由 Gateway 直接读取并鉴权。前者权限变更能较快生效,后者性能更好,但 Token 过期前可能存在权限数据不及时的问题。

第三方 OAuth2 中的 scope 更强调用户授权某个 Client 可以访问本平台的哪些资源;内部系统的 Gateway 鉴权更强调当前用户在本系统中具备哪些角色和接口权限。两者可以使用相似的字符串表示,但含义并不完全相同。

Access Token 也可以是不透明随机字符串。但这种 Token 本身看不出用户和权限,Resource Server 需要查询共享存储,或者调用授权服务器提供的 Token Introspection 接口确认是否有效。

Refresh Token 用于 Access Token 过期后申请新的 Access Token。它的有效期一般更长,只能发送给 Authorization Server,不能直接访问普通业务接口。

ID Token 属于 OIDC,主要告诉 Client"当前完成登录的用户是谁"。因为 OAuth2 本身只是授权,没办法完成认证

三种 Token 的区别:

  • Access Token:给 Resource Server 看,用于访问 API。
  • Refresh Token:给 Authorization Server 看,用于刷新 Access Token。
  • ID Token:给 Client 看,用于确认登录用户是谁。

不要使用 ID Token 代替 Access Token 调用业务接口。

OAuth2、OIDC 与第三方登录

OAuth2 主要解决"客户端可以访问哪些资源",OIDC 建立在 OAuth2 之上,补充身份认证能力,解决"当前登录用户是谁"。

OIDC 通常增加:

  • scope 中添加 openid
  • ID Token。
  • UserInfo Endpoint。
  • nonce
  • 标准用户身份字段。

很多"微信登录""GitHub 登录"虽然经常被称为 OAuth2 登录,但实际过程通常是:
#mermaid-svg-WvbQ9XzwaDiE6sPy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WvbQ9XzwaDiE6sPy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WvbQ9XzwaDiE6sPy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WvbQ9XzwaDiE6sPy .error-icon{fill:#552222;}#mermaid-svg-WvbQ9XzwaDiE6sPy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WvbQ9XzwaDiE6sPy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WvbQ9XzwaDiE6sPy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WvbQ9XzwaDiE6sPy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WvbQ9XzwaDiE6sPy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WvbQ9XzwaDiE6sPy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WvbQ9XzwaDiE6sPy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WvbQ9XzwaDiE6sPy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WvbQ9XzwaDiE6sPy .marker.cross{stroke:#333333;}#mermaid-svg-WvbQ9XzwaDiE6sPy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WvbQ9XzwaDiE6sPy p{margin:0;}#mermaid-svg-WvbQ9XzwaDiE6sPy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WvbQ9XzwaDiE6sPy .cluster-label text{fill:#333;}#mermaid-svg-WvbQ9XzwaDiE6sPy .cluster-label span{color:#333;}#mermaid-svg-WvbQ9XzwaDiE6sPy .cluster-label span p{background-color:transparent;}#mermaid-svg-WvbQ9XzwaDiE6sPy .label text,#mermaid-svg-WvbQ9XzwaDiE6sPy span{fill:#333;color:#333;}#mermaid-svg-WvbQ9XzwaDiE6sPy .node rect,#mermaid-svg-WvbQ9XzwaDiE6sPy .node circle,#mermaid-svg-WvbQ9XzwaDiE6sPy .node ellipse,#mermaid-svg-WvbQ9XzwaDiE6sPy .node polygon,#mermaid-svg-WvbQ9XzwaDiE6sPy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WvbQ9XzwaDiE6sPy .rough-node .label text,#mermaid-svg-WvbQ9XzwaDiE6sPy .node .label text,#mermaid-svg-WvbQ9XzwaDiE6sPy .image-shape .label,#mermaid-svg-WvbQ9XzwaDiE6sPy .icon-shape .label{text-anchor:middle;}#mermaid-svg-WvbQ9XzwaDiE6sPy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WvbQ9XzwaDiE6sPy .rough-node .label,#mermaid-svg-WvbQ9XzwaDiE6sPy .node .label,#mermaid-svg-WvbQ9XzwaDiE6sPy .image-shape .label,#mermaid-svg-WvbQ9XzwaDiE6sPy .icon-shape .label{text-align:center;}#mermaid-svg-WvbQ9XzwaDiE6sPy .node.clickable{cursor:pointer;}#mermaid-svg-WvbQ9XzwaDiE6sPy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WvbQ9XzwaDiE6sPy .arrowheadPath{fill:#333333;}#mermaid-svg-WvbQ9XzwaDiE6sPy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WvbQ9XzwaDiE6sPy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WvbQ9XzwaDiE6sPy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WvbQ9XzwaDiE6sPy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WvbQ9XzwaDiE6sPy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WvbQ9XzwaDiE6sPy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WvbQ9XzwaDiE6sPy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WvbQ9XzwaDiE6sPy .cluster text{fill:#333;}#mermaid-svg-WvbQ9XzwaDiE6sPy .cluster span{color:#333;}#mermaid-svg-WvbQ9XzwaDiE6sPy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WvbQ9XzwaDiE6sPy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WvbQ9XzwaDiE6sPy rect.text{fill:none;stroke-width:0;}#mermaid-svg-WvbQ9XzwaDiE6sPy .icon-shape,#mermaid-svg-WvbQ9XzwaDiE6sPy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WvbQ9XzwaDiE6sPy .icon-shape p,#mermaid-svg-WvbQ9XzwaDiE6sPy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WvbQ9XzwaDiE6sPy .icon-shape .label rect,#mermaid-svg-WvbQ9XzwaDiE6sPy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WvbQ9XzwaDiE6sPy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WvbQ9XzwaDiE6sPy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WvbQ9XzwaDiE6sPy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

用户选择 GitHub 登录
通过 OAuth2 获得 GitHub Access Token
调用 GitHub 用户信息接口
获得 githubId
是否绑定本地账号
找到本地用户
创建账号或绑定手机号
创建本系统 Session 或 JWT

GitHub Access Token 只能用于访问 GitHub,不能直接作为二狗商城自己的请求凭证。二狗商城确认用户身份后,需要建立本地 Session,或者由自己的认证中心签发本系统的 Access Token,供后续访问二狗商城接口使用。

两个 Access Token 来源不同,不要混了

OAuth2 负责获得授权结果,并不规定业务系统必须使用 Session 还是 JWT。

相关推荐
百度搜知知学社2 个月前
抖音双模块架构:兼容全安卓版本并支持登录
android·架构·安卓·登录·兼容性·抖音
如来神掌十八式4 个月前
JWT与Sa-Token认证方案对比
登录
如来神掌十八式4 个月前
OAuth2.0 + 授权码模式 + Token+JWT
登录
互联网散修4 个月前
零基础鸿蒙应用开发第三十四节:MVVM架构下的商品管理登录页
架构·harmonyos·mvvm·登录
孔明click335 个月前
Sa-Token v1.45.0 发布 🚀,正式支持 Spring Boot 4、新增 Jackson3/Snack4 插件适配
java·sa-token·开源·springboot·登录·权限认证
hqwest7 个月前
码上通QT实战02--登录设计
开发语言·qt·登录·ui设计·qt控件·qt布局·qt登录
Tancenter7 个月前
支付宝小程序的用户登录/注册流程
小程序·登录·注册·支付宝
hqwest7 个月前
码上通QT实战03--登录逻辑
开发语言·qt·登录·嵌入式实时数据库·界面设计
佛祖让我来巡山8 个月前
⚠️登录认证功能的成长过程:整体概述
安全·登录·springsecurity·登录认证·认证授权