Skip to content

项目架构 ​

三个角色 ​

角色目录地址职责
OP(授权服务器 + 资源服务器)backend/Yagang.Identity/Yagang.Identity.Apihttps://localhost:7016OpenIddict 提供 authorize / token / logout / revocation / userinfo;ASP.NET Core Identity 管理用户会话;/weatherforecast 是受保护的示例 API
React 登录前端frontend/yagang-identity-app构建产物输出到 OP 的 wwwroot(同源)登录 / 同意 / 登出 / 外部账号确认四个页面,全部用原生表单 POST 提交
演示客户端demos/yagang-client-demohttp://localhost:5173用 oidc-client-ts 发起授权码 + PKCE 流程,拿 token 调 API

代码怎么分层 ​

判断"某段代码该写在哪里"时,按职责对号入座:

层位置职责
协议端点Controllers/AuthorizationController.cs/connect/authorize、/connect/token、/connect/logout 的透传处理:Challenge、同意判定、签发 code、换 token、撤销由框架内置
外部提供方Controllers/AuthenticationController.cs~/callback/login/{provider}:校验 OpenIddict Client 的回调,把外部身份写进 Identity external cookie
页面后端Controllers/AccountController.csReact 页面要的非协议端点:POST /Account/Login、GET /Account/Antiforgery、外部登录入口 /Account/ExternalProviders + POST /Account/ExternalLogin + /Account/ExternalLoginCallback,以及确认页用的 /Account/ExternalLoginPending + POST /Account/ExternalLoginRegister
资源 APIControllers/WeatherForecastController.cs受 [Authorize](OpenIddict Validation)保护的示例数据
页面frontend/yagang-identity-app/src/pages/*.tsx登录 / 同意 / 登出 / 外部账号确认——UI 与交互全在这里,服务端不渲染任何视图
配置与数据OAuthOptions.cs、appsettings.json、Models/、Data/ApplicationDbContext.cs客户端注册、页面地址、演示账号全部配置驱动;Identity 用户/角色表 + OpenIddict 四张表

分层:三层认证别混淆 ​

初学者最容易晕的是"这个 cookie 和那个 token 到底谁管谁"。这个项目里同时存在三层:

┌──────────────────────────────────────────────────────────────────┐
│ ① Identity 应用 cookie(用户会话)                                │
│    .AspNetCore.Identity.Application,HttpOnly,14 天滑动(默认) │
│    由 SignInManager 种下;AuthorizationController 用它判断"用户是谁"│
│    登录页 / 登录端点只为它服务                                     │
├──────────────────────────────────────────────────────────────────┤
│ ② OpenIddict 授权 code / token(对客户端的凭据)                   │
│    授权端点签发的短命 code;客户端用它换 access/refresh token       │
│    状态存在 oi_tok / oi_ath / oi_app / oi_sco 四张表               │
├──────────────────────────────────────────────────────────────────┤
│ ③ access token(调 API 的通行证,JWS 未加密)                      │
│    由 OpenIddict Validation 校验(本项目走 discovery + jwks 验签) │
│    WeatherForecastController 上的 [Authorize] 认的就是它           │
└──────────────────────────────────────────────────────────────────┘

记忆口诀

cookie 管"人",code 管"这一趟授权",access token 管"调 API"。 同意页只影响 ②(是否写永久授权),跟 ① 的登录状态没关系;登出则要同时清掉 ①(SignInManager.SignOutAsync)并让 ②/③ 失效。

两个 OpenIddict 角色共存 ​

Program.cs 里同时调用了 AddClient 和 AddServer,这不是笔误:

  • AddServer:本进程是"授权服务器"(发 code/token、管端点、管 scope)。
  • AddClient:本进程同时还是"某个外部提供方(GitHub)的客户端"——用户点「使用 GitHub 登录」时, 本进程要去 GitHub 换 code,回调落在 ~/callback/login/{provider}(AuthenticationController)。
浏览器 ──点击「使用 GitHub 登录」──▶ 本进程 Challenge 转发 scheme ──302──▶ github.com/oauth/authorize
                                                                              │
浏览器 ◀──写 Identity cookie(外部身份)── ~/callback/login/github ◀──code────┘
   │
   └─ /Account/ExternalLoginCallback:把外部身份换成真正的 Identity 用户 → 回到原授权请求

端点一览 ​

端点类型谁处理
GET/POST /connect/authorizeOpenIddict Server(透传到 MVC)AuthorizationController.Authorize / .Accept / .Deny
POST /connect/tokenOpenIddict Server(透传)AuthorizationController.Exchange
GET/POST /connect/logoutOpenIddict Server(透传)AuthorizationController.Logout / .LogoutPost
POST /connect/revocationOpenIddict Server框架内置处理器(项目原有的撤销端点)
GET /connect/userinfoOpenIddict Server框架内置处理器(只配置 URI,不需要写控制器)
POST /Account/Login页面后端AccountController
GET /Account/Antiforgery页面后端AccountController
GET /Account/ExternalProviders
POST /Account/ExternalLogin(表单字段 provider)
GET /Account/ExternalLoginCallback
GET /Account/ExternalLoginPending
POST /Account/ExternalLoginRegister
页面后端AccountController
GET/POST /callback/login/{provider}OpenIddict Client(重定向端点透传)AuthenticationController.LogInCallback
GET /weatherforecast普通 APIWeatherForecastController([Authorize] + OpenIddict Validation)
GET /Account/Login
GET /Account/Consent
GET /Account/Logout
GET /Account/ExternalLoginConfirm
React 页面(SPA MapFallback 返回 wwwroot/index.html;/api、/connect、/callback 前缀除外)React 构建产物(wwwroot,Router 路由与处理端点同名)

为什么"页面后端"要自己写?

它们不是 OAuth/OIDC 协议端点,而是"登录页面的后端":登录表单提交、外部提供方按钮列表、外部登录落地、防伪隐藏字段。 本项目不用 ASP.NET Core Identity 自带的 Razor 页面(AddDefaultUI),UI 全部由 React 承担, 所以这几件事由 Controllers/AccountController.cs 实现(详见 AccountController 走读)。

OpenIddict + ASP.NET Core Identity 授权服务器,React 登录页,逐步拆解登录链路