OpenID Connect (OIDC) 是建立在OAuth 2.0协议之上的身份层,它让客户端能够验证终端用户的身份,并以可互操作和REST-like的方式获取用户的基本信息。本文将全面介绍如何在C#应用程序中实现OpenID Connect集成。
1. OpenID Connect基础概念
1.1 OIDC与OAuth 2.0的关系
OpenID Connect扩展了OAuth 2.0,添加了:
- 身份认证标准
- ID令牌(ID Token)
- 用户信息端点(UserInfo Endpoint)
- 标准化的作用域(scopes)和声明(claims)
1.2 核心组件
- ID Token:JWT格式,包含用户身份信息
- UserInfo Endpoint:获取用户详细信息的API
- 发现文档:/.well-known/openid-configuration
- JWKS Endpoint:提供验证令牌的公钥
2. 准备工作
2.1 注册OpenID Connect应用
在身份提供者(如Azure AD、IdentityServer、Okta等)注册应用,获取:
- 客户端ID (Client ID)
- 客户端密钥 (Client Secret)
- 重定向URI (Redirect URI)
- 元数据端点(通常为/.well-known/openid-configuration)
2.2 安装必要的NuGet包
Install-Package Microsoft.AspNetCore.Authentication.OpenIdConnect
Install-Package IdentityModel
Install-Package Microsoft.IdentityModel.Protocols.OpenIdConnect
3. ASP.NET Core集成
3.1 基本配置
在Startup.cs
中配置OIDC认证:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://your-identity-provider.com";
options.ClientId = "your-client-id";
options.ClientSecret = "your-client-secret";
options.ResponseType = "code";
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("offline_access"); // 用于获取刷新令牌
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = context =>
{
// 在重定向前可以修改请求
return Task.CompletedTask;
},
OnTokenResponseReceived = context =>
{
// 处理令牌响应
return Task.CompletedTask;
},
OnUserInformationReceived = context =>
{
// 处理用户信息
return Task.CompletedTask;
}
};
});
services.AddControllersWithViews();
}
3.2 触发认证流程
创建登录控制器:
public class AccountController : Controller
{
public IActionResult Login(string returnUrl = "/")
{
if (!User.Identity.IsAuthenticated)
{
return Challenge(new AuthenticationProperties
{
RedirectUri = returnUrl
}, OpenIdConnectDefaults.AuthenticationScheme);
}
return Redirect(returnUrl);
}
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
return RedirectToAction("Index", "Home");
}
}
4. 高级配置
4.1 PKCE支持
Proof Key for Code Exchange (PKCE) 增强安全性:
options.UsePkce = true;
options.CodeChallengeMethod = "S256";
4.2 自定义声明映射
options.ClaimActions.MapUniqueJsonKey("website", "website");
options.ClaimActions.MapUniqueJsonKey("gender", "gender");
4.3 令牌验证自定义
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "https://your-identity-provider.com",
ValidAudience = "your-client-id",
NameClaimType = "name",
RoleClaimType = "role"
};
5. 使用令牌访问API
5.1 获取访问令牌
public async Task<string> GetAccessToken()
{
return await HttpContext.GetTokenAsync("access_token");
}
5.2 调用受保护API
public async Task<IActionResult> CallApi()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.GetAsync("https://api.example.com/data");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
return View("ApiResult", content);
}
throw new Exception($"API调用失败: {response.StatusCode}");
}
6. 实现自己的OpenID Connect提供者
使用IdentityServer4创建OIDC提供者:
6.1 安装IdentityServer4
Install-Package IdentityServer4
Install-Package IdentityServer4.AspNetIdentity
6.2 配置IdentityServer
public class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResource("custom", new[] { "custom_claim" })
};
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope("api1", "My API")
};
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RedirectUris = { "https://localhost:5001/signin-oidc" },
PostLogoutRedirectUris = { "https://localhost:5001/signout-callback-oidc" },
FrontChannelLogoutUri = "https://localhost:5001/signout-oidc",
AllowOfflineAccess = true,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"api1",
"custom"
}
}
};
}
6.3 设置IdentityServer中间件
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddAspNetIdentity<ApplicationUser>();
}
7. 安全最佳实践
- 始终使用HTTPS:所有通信必须加密
- 验证令牌:验证签名、颁发者、受众和有效期
- 使用PKCE:防止授权码拦截攻击
- 安全存储令牌:使用安全机制存储刷新令牌
- 限制作用域:只请求必要的作用域
- 实现适当的令牌过期:设置合理的令牌生命周期
- 防范CSRF:使用state参数和防伪令牌
- 定期轮换密钥:定期更新客户端密钥
8. 常见问题解决
- 无效的重定向URI:确保在身份提供者和应用中配置完全匹配
- 作用域不足:检查请求的作用域是否已被授权
- 令牌验证失败:验证签名、颁发者和受众
- 用户信息缺失:检查
GetClaimsFromUserInfoEndpoint
设置和请求的作用域 - 跨域问题:确保正确配置CORS
9. 总结
通过本文,你已经掌握了在C#应用程序中集成OpenID Connect的完整流程。OpenID Connect提供了比单纯OAuth 2.0更强大的身份认证能力,是现代应用程序身份管理的理想选择。无论是作为客户端还是服务端实现,遵循本文的指南都能帮助你构建安全、可靠的身份认证系统。
记住,安全是一个持续的过程,随着OpenID Connect规范的更新和新威胁的出现,保持你的实现与时俱进非常重要。建议定期检查OIDC提供者的文档更新和安全公告,确保你的实现始终符合最佳实践。