在基于OpenID Connect与Amazon Cognito的ASP.NET Core Web应用中扩展/signin-oidc端点以实现登录成功事件触发及用户档案创建
处理ASP.NET Core + Amazon Cognito登录成功事件与扩展回调端点
你好!针对你的需求,我分两种场景给出解决方案——首先是更推荐的利用认证事件钩子来触发登录后的操作,其次是如果必须自定义/signin-oidc端点时的实现方式。
一、推荐方案:通过OpenIdConnectEvents捕获登录成功事件
ASP.NET Core的OIDC中间件提供了丰富的事件钩子,完全不需要重写默认的/signin-oidc端点就能实现登录成功后的逻辑(比如创建用户档案)。具体步骤如下:
- 配置OpenIdConnectOptions时添加事件处理
在Program.cs(或Startup.cs,取决于你的.NET版本)的认证配置中,找到AddOpenIdConnect的部分,添加Events配置:
builder.Services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie() .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => { // 你的Cognito基础配置 options.Authority = "https://cognito-idp.{region}.amazonaws.com/{userPoolId}"; options.ClientId = "your-client-id"; options.ClientSecret = "your-client-secret"; options.ResponseType = "code"; // 对应你的权限范围 options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("email"); // 登录成功后的事件处理 options.Events = new OpenIdConnectEvents { // 令牌验证通过后触发(此时用户已完成登录) OnTokenValidated = async context => { // 获取Cognito返回的核心用户信息 var userId = context.Principal.FindFirstValue(ClaimTypes.NameIdentifier); // 对应Cognito的sub字段 var email = context.Principal.FindFirstValue(ClaimTypes.Email); var userName = context.Principal.FindFirstValue(ClaimTypes.Name); // 调用自定义服务,检查并创建用户档案 var userProfileService = context.HttpContext.RequestServices.GetRequiredService<IUserProfileService>(); await userProfileService.EnsureUserProfileExistsAsync(userId, email, userName); // 如需扩展用户Claims,可在此操作 // var identity = (ClaimsIdentity)context.Principal.Identity; // identity.AddClaim(new Claim("custom-claim", "value")); }, // 也可使用OnUserInformationReceived事件(获取用户信息端点数据后触发) // OnUserInformationReceived = async context => // { // var userInfo = context.User; // var userId = userInfo["sub"].ToString(); // // 执行档案创建逻辑 // } }; });
- 实现用户档案服务
创建接口和实现类,封装检查/创建用户档案的业务逻辑:
public interface IUserProfileService { Task EnsureUserProfileExistsAsync(string userId, string email, string userName); } public class UserProfileService : IUserProfileService { private readonly ApplicationDbContext _dbContext; public UserProfileService(ApplicationDbContext dbContext) { _dbContext = dbContext; } public async Task EnsureUserProfileExistsAsync(string userId, string email, string userName) { var existingProfile = await _dbContext.UserProfiles.FirstOrDefaultAsync(p => p.UserId == userId); if (existingProfile == null) { var newProfile = new UserProfile { UserId = userId, Email = email, UserName = userName, CreatedAt = DateTime.UtcNow }; _dbContext.UserProfiles.Add(newProfile); await _dbContext.SaveChangesAsync(); } } }
这种方案的优势是完全利用框架扩展点,无需改动默认回调端点,符合ASP.NET Core的设计理念,安全性和可维护性更高。
二、自定义/signin-oidc端点(仅特殊需求使用)
如果你确实需要完全控制/signin-oidc的逻辑,可以通过以下步骤实现:
- 禁用默认OIDC回调处理
在AddOpenIdConnect配置中,修改回调路径或跳过未识别请求,避免默认中间件拦截:
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => { // 其他配置... options.SkipUnrecognizedRequests = true; options.CallbackPath = "/custom-signin-oidc"; // 让默认中间件不处理原/signin-oidc })
- 创建自定义控制器处理回调
手动实现令牌验证、用户身份创建和档案逻辑:
[Route("[controller]")] public class AuthController : Controller { private readonly IConfiguration _configuration; private readonly IUserProfileService _userProfileService; public AuthController(IConfiguration configuration, IUserProfileService userProfileService) { _configuration = configuration; _userProfileService = userProfileService; } [HttpGet("signin-oidc")] public async Task<IActionResult> SignInOidc() { // 1. 获取授权码 var code = Request.Query["code"]; if (string.IsNullOrEmpty(code)) { return BadRequest("Authorization code is missing."); } // 2. 向Cognito请求令牌 var tokenClient = new HttpClient(); var tokenRequest = new HttpRequestMessage(HttpMethod.Post, $"{_configuration["Auth:Cognito:Authority"]}/oauth2/token"); tokenRequest.Content = new FormUrlEncodedContent(new Dictionary<string, string> { ["grant_type"] = "authorization_code", ["client_id"] = _configuration["Auth:Cognito:ClientId"], ["client_secret"] = _configuration["Auth:Cognito:ClientSecret"], ["code"] = code, ["redirect_uri"] = Url.Action("SignInOidc", "Auth", null, Request.Scheme) }); var tokenResponse = await tokenClient.SendAsync(tokenRequest); tokenResponse.EnsureSuccessStatusCode(); var tokenData = await tokenResponse.Content.ReadFromJsonAsync<TokenResponse>(); // 3. 验证ID Token并生成ClaimsPrincipal var validationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = _configuration["Auth:Cognito:Authority"], ValidateAudience = true, ValidAudience = _configuration["Auth:Cognito:ClientId"], ValidateLifetime = true, IssuerSigningKeys = await GetCognitoSigningKeysAsync() }; var handler = new JwtSecurityTokenHandler(); var principal = handler.ValidateToken(tokenData.IdToken, validationParameters, out _); // 4. 执行用户档案创建逻辑 var userId = principal.FindFirstValue(ClaimTypes.NameIdentifier); var email = principal.FindFirstValue(ClaimTypes.Email); var userName = principal.FindFirstValue(ClaimTypes.Name); await _userProfileService.EnsureUserProfileExistsAsync(userId, email, userName); // 5. 登录用户(生成认证Cookie) await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal); // 6. 重定向到原请求路径或首页 var returnUrl = Request.Query["returnUrl"] ?? "/"; return Redirect(returnUrl); } private async Task<IEnumerable<SecurityKey>> GetCognitoSigningKeysAsync() { var httpClient = new HttpClient(); var jwksResponse = await httpClient.GetFromJsonAsync<JsonWebKeySet>($"{_configuration["Auth:Cognito:Authority"]}/.well-known/jwks.json"); return jwksResponse!.Keys.Select(k => k); } } // 辅助类:反序列化令牌响应 public class TokenResponse { [JsonPropertyName("access_token")] public string AccessToken { get; set; } = string.Empty; [JsonPropertyName("id_token")] public string IdToken { get; set; } = string.Empty; [JsonPropertyName("token_type")] public string TokenType { get; set; } = string.Empty; [JsonPropertyName("expires_in")] public int ExpiresIn { get; set; } }
⚠️ 注意:这种方式需要手动处理令牌验证、签名密钥获取、错误处理等细节,容易引入安全风险,仅在有特殊自定义需求时使用。
内容的提问来源于stack exchange,提问作者Justin
相关产品推荐
相关产品推荐

