跳到主要内容

Auth 认证中心模块

Auth 是仓库 auth/ 下可独立运行的 OAuth2/OpenID Connect 授权中心,目标框架为 net10.0,当前引用 OpenIddict.AspNetCore 7.5.0。它支持授权码流、PKCE 和刷新令牌,不属于主 Host 的 /kit_api 路由空间。

本地 launch profile 固定监听:

  • HTTPS:https://localhost:7005
  • HTTP:http://localhost:5005

功能概览

Auth 模块基于 OpenIddict 实现 OAuth2/OpenID Connect 认证服务,提供:

功能域能力
协议端点/connect/authorize/connect/token/connect/userinfo
交互页面/connect/login/connect/consent
会话登出POST /connect/logout(自定义 Cookie 登出,不是已配置的 end-session endpoint)
认证流程授权码流、PKCE、刷新令牌
应用管理OAuth2 客户端应用 CRUD、Scope 管理、Token 管理
声明映射动态声明映射(数据库配置)、默认 Scope(openid/profile/email/roles)
安全密码策略(5 次锁定)、证书签名、Token 撤销与清理
启动引导自动创建 Scope 和应用、非破坏性合并

快速开始

1. 配置数据库

{
"ConnectionStrings": {
"DefaultDB": "Sqlite",
"MySql": "Server=localhost;Port=3306;Database=freekit;User ID=<DB_USER>;Password=<DB_PASSWORD>;",
"Sqlite": "Data Source=freekit.auth.db"
}
}

AddAuthPersistence(...)DefaultDB 解析为 FreeSql DataType,再读取同名连接串。建议使用 Sqlite / MySql 这类枚举名称,避免数字值难以审查。

2. 配置证书

{
"Security": {
"OpenIddict": {
"Certificates": {
"SigningCertificatePath": "certs/signing.pfx",
"SigningCertificatePassword": "<SIGNING_CERT_PASSWORD>",
"EncryptionCertificatePath": "certs/encryption.pfx",
"EncryptionCertificatePassword": "<ENCRYPTION_CERT_PASSWORD>"
}
}
}
}

开发环境在证书文件不存在时回退到开发证书。生产部署必须挂载持久化证书;否则重启或多实例之间无法稳定验证已签发内容。

3. 启动服务

dotnet run --project auth/src/FreeKit.Auth.Host --launch-profile https-dev

启动后访问 https://localhost:7005/connect/login;Discovery 位于 https://localhost:7005/.well-known/openid-configuration,Swagger 位于 https://localhost:7005/swagger

架构概览

OAuth2 端点

/connect/authorize

OAuth2 授权端点,支持授权码流。

参数说明
client_id客户端 ID
redirect_uri回调地址
response_type响应类型(code)
scope请求的 Scope
code_challengePKCE 挑战
code_challenge_method挑战方法(S256)

/connect/token

令牌端点,支持授权码和刷新令牌。

参数说明
grant_type授权类型
code授权码
redirect_uri回调地址
code_verifierPKCE 验证器
refresh_token刷新令牌

/connect/userinfo

返回用户信息,基于授权的 Scope。

Scope返回的 Claims
openidsub
profilename
emailemail
rolesrole

/connect/logout

这是 ConnectController 提供的自定义 POST 接口,要求 Auth Cookie。它清理登录 Cookie,并在当前主体带有 sub 时尝试撤销该主体的有效 Token。Program.cs 没有调用 SetEndSessionEndpointUris(...),因此不要把它当作标准 OIDC end-session 地址。

登录与同意页面

方法路由说明
GET / POST/connect/loginRazor Page,用户名或邮箱 + 密码登录,成功后写入 freekit.auth Cookie
GET / POST/connect/consentConsentType=explicit 且没有覆盖本次 Scope 的有效授权时显示
GET/connect/test-callback仓库内置联调页入口,不是生产客户端回调约定

核心功能

用户认证

支持用户名或邮箱登录,大小写不敏感。

认证流程

  1. 查找用户(用户名/邮箱)
  2. 检查账户锁定状态
  3. 验证密码哈希
  4. 记录失败尝试
  5. 5 次失败后锁定 30 分钟

密码策略

配置说明
最大失败次数5 次
锁定时间30 分钟
密码哈希ASP.NET Identity PasswordHasher
自动重哈希支持

动态声明映射

支持从数据库配置的动态声明映射。

映射实体

字段类型说明
ClaimTypestring声明类型
UserPropertyNamestring用户属性名
SourceTypeClaimSourceType来源类型
IsEnabledbool是否启用
Scopesstring?关联的 Scope

默认映射

声明类型来源需要的 Scope
subAuthUser.Idopenid
nameAuthUser.UserNameprofile
emailAuthUser.Emailemail
role用户角色roles

OAuth2 应用管理

管理 OAuth2 客户端应用。

应用属性

属性说明
ClientId客户端 ID
ClientSecret客户端密钥
DisplayName显示名称
ApplicationType应用类型
ConsentType同意类型
RedirectUris回调地址
PostLogoutRedirectUris登出回调
Scopes允许的 Scope
Permissions权限

Scope 管理

管理 OAuth2 Scope。

内置 Scope

Scope显示名称资源
openidOpenIDidentity
profile用户资料identity
email邮箱信息identity
roles角色信息identity
consoleConsole APIconsole
cmskitCmsKit APIcmskit
imIM APIim

Token 管理

管理访问令牌和刷新令牌。

功能说明
列出 Token分页查询
撤销 Token单个/批量撤销
清理 Token清理已兑换/已撤销的旧 Token

启动引导

OpenIddictBootstrapper 在启动时自动配置:

功能说明
确保 Scope自动创建内置 Scope
确保应用自动创建配置的应用
非破坏性合并不覆盖已有记录

API 控制器

ConnectController

方法路由认证说明
GET/connect/authorizeAnonymous授权端点
POST/connect/tokenAnonymous令牌端点
GET/connect/userinfoOpenIddict用户信息
POST/connect/logoutCookie自定义 Cookie 登出,并按可用 sub 撤销 Token
GET/connect/test-callbackAnonymous本地协议联调页

OpenIddictAdminController

方法路由认证说明
GET/api/identity/openiddict/applicationsAdmin列出应用
GET/api/identity/openiddict/applications/{id}Admin获取应用
POST/api/identity/openiddict/applicationsAdmin创建应用
PUT/api/identity/openiddict/applications/{id}Admin更新应用
DELETE/api/identity/openiddict/applications/{id}Admin删除应用
GET/api/identity/openiddict/scopesAdmin列出 Scope
POST/api/identity/openiddict/scopesAdmin创建 Scope
DELETE/api/identity/openiddict/scopes/{id}Admin删除 Scope
GET/api/identity/openiddict/tokensAdmin列出 Token
POST/api/identity/openiddict/tokens/{id}/revokeAdmin撤销 Token
POST/api/identity/openiddict/tokens/revoke-by-subjectAdmin撤销用户 Token
POST/api/identity/openiddict/tokens/revoke-by-application/{appId}Admin撤销应用 Token
POST/api/identity/openiddict/tokens/pruneAdmin清理旧 Token
GET/api/identity/openiddict/authorizationsAdmin列出授权

配置选项

数据库配置

{
"ConnectionStrings": {
"DefaultDB": "MySql",
"MySql": "Server=localhost;Port=3306;Database=freekit_auth;User ID=<DB_USER>;Password=<DB_PASSWORD>;",
"Sqlite": "Data Source=freekit.auth.db"
}
}

证书配置

{
"Security": {
"OpenIddict": {
"Certificates": {
"SigningCertificatePath": "certs/signing.pfx",
"SigningCertificatePassword": "<SIGNING_CERT_PASSWORD>",
"EncryptionCertificatePath": "certs/encryption.pfx",
"EncryptionCertificatePassword": "<ENCRYPTION_CERT_PASSWORD>"
}
}
}
}

应用配置

{
"Host": {
"IdentityApi": "https://localhost:7005",
"CmsKitClient": "https://localhost:5173"
},
"Security": {
"OpenIddict": {
"Bootstrap": {
"Enabled": true,
"Applications": [
{
"ClientId": "freekit.cmskit",
"DisplayName": "FreeKit CmsKit SPA",
"Type": "public",
"ConsentType": "explicit",
"AllowAuthorizationCodeFlow": true,
"AllowRefreshTokenFlow": true,
"RequirePkce": true,
"RedirectUris": ["{CmsKitClient}/auth/callback"],
"PostLogoutRedirectUris": ["{CmsKitClient}/"],
"Scopes": ["openid", "profile", "email", "roles", "cmskit"]
}
]
}
}
}
}

上例是 public SPA,因此没有 ClientSecret。confidential 服务端客户端才配置 "ClientSecret": "<CLIENT_SECRET>",并从 Secret 管理器注入真实值。

环境变量

变量说明
ASPNETCORE_ENVIRONMENT运行环境
Host__IdentityApiAuth 对外基地址,例如 https://localhost:7005
Host__ConsoleClientConsole 前端基地址,用于 Bootstrap URI 模板
Host__CmsKitClientCmsKit 前端基地址,用于 Bootstrap URI 模板
Security__OpenIddict__Certificates__SigningCertificatePassword签名证书密码;仅通过 Secret 注入

数据库表

表名说明
identity_user用户表
identity_role角色表
identity_user_role用户角色关联
identity_connect_claim_mapping声明映射表
open_iddict_applicationOAuth2 应用表
open_iddict_authorization授权记录表
open_iddict_scopeScope 表
open_iddict_tokenToken 表

与前端集成

1. 配置回调地址

const authServer = 'https://localhost:7005';
const clientId = 'freekit.cmskit';
// 必须与 Auth Bootstrap 的 RedirectUris 完全一致;它属于前端,不属于 Auth Server。
const callbackUrl = 'https://localhost:5173/auth/callback';

2. 获取授权码

const authorize = new URL('/connect/authorize', authServer);
authorize.search = new URLSearchParams({
client_id: clientId,
redirect_uri: callbackUrl,
response_type: 'code',
scope: 'openid profile email roles cmskit',
code_challenge: challenge,
code_challenge_method: 'S256',
state
}).toString();

window.location.assign(authorize);

challenge / verifier 必须是一对 PKCE 值,state 必须是每次登录新生成并在回调时校验的随机值。

3. 交换令牌

const response = await fetch(`${authServer}/connect/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: authorizationCode,
redirect_uri: callbackUrl,
code_verifier: verifier,
client_id: clientId
})
});

浏览器 public client 不发送 client_secret。服务端 confidential client 如需 Secret,示例值使用 <CLIENT_SECRET>,真实值只能从 Secret 管理器读取。

4. 刷新令牌

const response = await fetch(`${authServer}/connect/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: clientId
})
});

客户端要获得刷新令牌,还必须在 Bootstrap 权限与授权请求中包含 offline_access

源码定位

  • auth/src/FreeKit.Auth.Host/Program.cs:7.5.0 Server 注册、证书、真实端点和中间件
  • auth/src/FreeKit.Auth.Host/Properties/launchSettings.json:7005 / 5005
  • auth/src/FreeKit.Auth.Host/Controllers/ConnectController.cs:授权、Token、userinfo 与自定义 logout
  • auth/src/FreeKit.Auth.Host/Pages/Login.cshtmlPages/Consent.cshtml:交互页面路由
  • auth/src/FreeKit.Auth/OpenIddict/OpenIddictBootstrapper.cs:内置 Scope、客户端权限和 URI 模板
  • auth/src/FreeKit.Auth.Host/AuthPersistenceExtensions.cs:独立 FreeSql 持久化选择

相关文档