什么是 SSO,C# 里不直接“实现”而是“接入”
SSO(单点登录)不是某个 C# 类库能一键开启的功能,而是一套跨系统认证协议的协作机制。你在 C# 应用中要做的,是作为 服务提供方(SP) 接入已有的身份提供方(IdP),比如 Azure AD、Okta、Auth0、或自建的 IdentityServer。硬写一套符合 SAML/OIDC 规范的 IdP 在生产环境极不推荐——协议细节多、安全要求高、密钥轮换、签名验证、时钟偏移处理等极易出错。
ASP.NET Core 中最常用的是 OIDC + Microsoft.Identity.Web
现代 C# Web 应用(.NET 6+)基本都走 OpenID Connect(OIDC)路线,配合
Microsoft.Identity.Web包可快速接入 Azure AD 或其他兼容 OIDC 的 IdP。它封装了 token 获取、验证、用户上下文注入等逻辑,避免手写
OpenIdConnectEvents处理 redirect_uri、nonce、state 等易错环节。 安装 NuGet 包:
Microsoft.Identity.Web和
Microsoft.Identity.Web.UI在
Program.cs中注册服务:
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd");配置
appsettings.json中的
AzureAd节点,至少包含:
ClientId、
ClientSecret(或证书)、
Instance(如
https://login.microsoftonline.com/)、
TenantId控制器方法加
[Authorize]即可触发自动重定向到 IdP 登录页
自己搭 IdP?用 IdentityServer6(非 .NET 自带)
如果必须自建 IdP(例如统一管理多个内部系统),不要用已停止维护的 IdentityServer4,改用
IdentityServer6(基于 .NET 6+,开源且持续更新)。它本身是独立服务,C# 客户端只需按 OIDC 标准接入——也就是说,你的业务系统仍是 SP,只是 IdP 换成了你自己的实例。 IdentityServer6 需单独部署(可托管在 Kestrel/IIS/Docker),暴露
/.well-known/openid-configuration端点 客户端应用仍用
Microsoft.Identity.Web,但
Instance改为你的 IdP 地址,
TenantId改为
common或具体租户 ID 关键区别:你需要手动在 IdP 侧注册每个客户端(即你的 C# 应用),配置
ClientId、
RedirectUris、
PostLogoutRedirectUris、是否启用 PKCE 等 切勿在 IdP 中硬编码用户密码——应对接现有目录(如 LDAP、SQL Server 用户表)或通过
IProfileService扩展用户声明
常见坑:Cookie 同站策略、HTTPS 强制、时钟偏差
本地开发时最常卡在三类问题,和代码逻辑无关,纯属部署/配置层面:
SameSite=None+
Secure=true:Chrome/Firefox 要求跨站 Cookie 必须同时设这两个属性,否则登录后跳回应用时
HttpContext.User为空。ASP.NET Core 6+ 默认已适配,但若降级或自定义 Cookie 策略需检查 所有回调地址(
RedirectUri)必须用 HTTPS(生产环境),即使本地用
https://localhost:5001;HTTP 会被 IdP 拒绝 服务器时间与 NTP 时间偏差超过 5 分钟会导致 JWT 签名验证失败(
exp/
nbf检查),错误信息类似:
System.IdentityModel.Tokens.Jwt.SecurityTokenInvalidLifetimeExceptionIdP 返回的
id_token若含中文或特殊字符,某些老版本
System.Text.Json会解析失败,建议显式指定
JsonSerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
真正难的从来不是“怎么写那几行代码”,而是理解谁发 token、谁验 token、谁存 session、谁管登出通知——这些角色划分错了,后面所有配置都会指向奇怪的 401 或循环重定向。
