ASP.NET Core 3.1 Web应用中实现同时登录多OAuth2认证方案的问题求助
你遇到的核心问题是:当你在AuthorizationPolicyBuilder中传入多个认证scheme时,默认逻辑是用户只需通过其中一个认证即可通过授权,而非同时满足所有指定scheme的要求——这就是为什么现在只要登录其中一个系统,接口就会判定授权成功的原因。
下面提供两种更优雅的解决方案,避免你提到的手动检查的临时方案:
方案1:使用RequireAssertion快速实现多认证检查
你可以直接在授权策略中添加断言,要求用户的身份凭证同时包含两个系统的认证标识。修改Startup.cs中的授权配置:
services.AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() // 断言用户必须同时拥有SystemA和SystemB的认证身份 .RequireAssertion(context => context.User.Identities.Any(id => id.AuthenticationType == "SystemA") && context.User.Identities.Any(id => id.AuthenticationType == "SystemB") ) .Build(); });
之后你的控制器只需使用普通的[Authorize]属性即可,无需再指定AuthenticationSchemes:
[HttpGet] [Route("test")] [Authorize] public IActionResult Test() { return Ok(); }
这个方案简单直接,适合快速实现需求;但如果需要在多个地方复用该逻辑,方案2会更灵活。
方案2:自定义授权需求与处理程序(可复用)
如果你需要在多个策略或接口中复用“同时通过多个认证scheme”的逻辑,可以创建自定义的授权需求和处理程序:
1. 定义授权需求类
这个类用于存储需要检查的所有认证scheme:
public class RequireAllAuthenticationSchemesRequirement : IAuthorizationRequirement { public IEnumerable<string> RequiredSchemes { get; } public RequireAllAuthenticationSchemesRequirement(IEnumerable<string> requiredSchemes) { RequiredSchemes = requiredSchemes ?? throw new ArgumentNullException(nameof(requiredSchemes)); } }
2. 实现授权处理程序
这个处理程序会检查用户是否拥有所有指定scheme的认证身份:
public class RequireAllSchemesHandler : AuthorizationHandler<RequireAllAuthenticationSchemesRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RequireAllAuthenticationSchemesRequirement requirement) { // 检查用户是否同时拥有所有要求的认证scheme的身份 var hasAllRequiredSchemes = requirement.RequiredSchemes.All(scheme => context.User.Identities.Any(identity => identity.AuthenticationType == scheme)); if (hasAllRequiredSchemes) { context.Succeed(requirement); } else { // 添加失败原因,方便调试或返回提示 context.Fail(new AuthorizationFailureReason(this, "用户未完成所有必需系统的认证")); } return Task.CompletedTask; } }
3. 注册处理程序并配置授权策略
在Startup.cs中注册自定义处理程序,并将需求添加到默认策略:
// 注册自定义授权处理程序 services.AddScoped<IAuthorizationHandler, RequireAllSchemesHandler>(); services.AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .AddRequirements(new RequireAllAuthenticationSchemesRequirement(new[] { "SystemA", "SystemB" })) .Build(); });
同样,控制器只需添加[Authorize]即可生效。
关于认证挑战的补充说明
需要注意的是,ASP.NET Core默认一次只能发起一个认证挑战(Challenge)。如果用户仅完成了其中一个系统的登录,授权会失败,但默认不会自动引导用户登录另一个系统。
如果你需要自动引导用户完成缺失的认证,可以在授权失败时做额外处理:比如自定义中间件,或者在控制器中捕获授权失败的情况,检查用户缺少哪个认证scheme,再发起对应的挑战。示例如下:
[HttpGet] [Route("test")] public async Task<IActionResult> Test() { var hasSystemA = User.Identities.Any(id => id.AuthenticationType == "SystemA"); var hasSystemB = User.Identities.Any(id => id.AuthenticationType == "SystemB"); if (!hasSystemA) { return Challenge("SystemA"); } if (!hasSystemB) { return Challenge("SystemB"); } return Ok(); }
虽然这属于你提到的“编程方式检查”,但结合前面的授权策略,可以确保只有当两个认证都完成后才会执行接口逻辑,而挑战逻辑只是引导用户完成缺失的步骤,也算比较优雅的方式。
内容的提问来源于stack exchange,提问作者Stonesvillage

