ASP.NET Core 中的策略方案

借助身份验证策略方案,可使用多种方法更容易地创建单一逻辑身份验证方案。 例如,策略方案可能会对质询使用 Google 身份验证,而对所有其他操作使用 cookie 身份验证。 借助身份验证策略方案,可以实现以下操作:

  • 可以轻松地将任何身份验证操作转发到另一个方案。
  • 根据请求进行动态转发。

使用派生的 AuthenticationSchemeOptions 和关联的 AuthenticationHandler<TOptions> 的所有身份验证方案:

  • 在 ASP.NET Core 2.1 及更高版本中自动成为策略方案。
  • 可以通过配置方案的选项来启用。
public class AuthenticationSchemeOptions
{
    /// <summary>
    /// If set, this specifies a default scheme that authentication handlers should 
    /// forward all authentication operations to, by default. The default forwarding 
    /// logic checks in this order:
    /// 1. The most specific ForwardAuthenticate/Challenge/Forbid/SignIn/SignOut 
    /// 2. The ForwardDefaultSelector
    /// 3. ForwardDefault
    /// The first non null result is used as the target scheme to forward to.
    /// </summary>
    public string ForwardDefault { get; set; }

    /// <summary>
    /// If set, this specifies the target scheme that this scheme should forward 
    /// AuthenticateAsync calls to. For example:
    /// Context.AuthenticateAsync("ThisScheme") => 
    ///                Context.AuthenticateAsync("ForwardAuthenticateValue");
    /// Set the target to the current scheme to disable forwarding and allow 
    /// normal processing.
    /// </summary>
    public string ForwardAuthenticate { get; set; }

    /// <summary>
    /// If set, this specifies the target scheme that this scheme should forward 
    /// ChallengeAsync calls to. For example:
    /// Context.ChallengeAsync("ThisScheme") =>
    ///                         Context.ChallengeAsync("ForwardChallengeValue");
    /// Set the target to the current scheme to disable forwarding and allow normal
    /// processing.
    /// </summary>
    public string ForwardChallenge { get; set; }

    /// <summary>
    /// If set, this specifies the target scheme that this scheme should forward 
    /// ForbidAsync calls to.For example:
    /// Context.ForbidAsync("ThisScheme") 
    ///                               => Context.ForbidAsync("ForwardForbidValue");
    /// Set the target to the current scheme to disable forwarding and allow normal 
    /// processing.
    /// </summary>
    public string ForwardForbid { get; set; }

    /// <summary>
    /// If set, this specifies the target scheme that this scheme should forward 
    /// SignInAsync calls to. For example:
    /// Context.SignInAsync("ThisScheme") => 
    ///                                Context.SignInAsync("ForwardSignInValue");
    /// Set the target to the current scheme to disable forwarding and allow normal 
    /// processing.
    /// </summary>
    public string ForwardSignIn { get; set; }

    /// <summary>
    /// If set, this specifies the target scheme that this scheme should forward 
    /// SignOutAsync calls to. For example:
    /// Context.SignOutAsync("ThisScheme") => 
    ///                              Context.SignOutAsync("ForwardSignOutValue");
    /// Set the target to the current scheme to disable forwarding and allow normal 
    /// processing.
    /// </summary>
    public string ForwardSignOut { get; set; }

    /// <summary>
    /// Used to select a default scheme for the current request that authentication
    /// handlers should forward all authentication operations to by default. The 
    /// default forwarding checks in this order:
    /// 1. The most specific ForwardAuthenticate/Challenge/Forbid/SignIn/SignOut
    /// 2. The ForwardDefaultSelector
    /// 3. ForwardDefault. 
    /// The first non null result will be used as the target scheme to forward to.
    /// </summary>
    public Func<HttpContext, string> ForwardDefaultSelector { get; set; }
}

具有多个方案的 JWT

AddPolicyScheme 方法可以定义多个身份验证方案并实现逻辑,以基于令牌属性(例如颁发者、声明)选择适当的方案。 此方法允许在单个 API 中提高灵活性。

services.AddAuthentication(options =>
{
	options.DefaultScheme = Consts.MY_POLICY_SCHEME;
	options.DefaultChallengeScheme = Consts.MY_POLICY_SCHEME;

})
.AddJwtBearer(Consts.MY_FIRST_SCHEME, options =>
{
	options.Authority = "https://your-authority";
	options.Audience = "https://your-audience";
	options.TokenValidationParameters = new TokenValidationParameters
	{
		ValidateIssuer = true,
		ValidateAudience = true,
		ValidateIssuerSigningKey = true,
		ValidAudiences = Configuration.GetSection("ValidAudiences").Get<string[]>(),
		ValidIssuers = Configuration.GetSection("ValidIssuers").Get<string[]>()
	};
})
.AddJwtBearer(Consts.MY_AAD_SCHEME, jwtOptions =>
{
	jwtOptions.Authority = Configuration["AzureAd:Authority"];
	jwtOptions.Audience = Configuration["AzureAd:Audience"]; 
})
.AddPolicyScheme(Consts.MY_POLICY_SCHEME, displayName: null, options =>
{
	options.ForwardDefaultSelector = context =>
	{
		string authorization = context.Request.Headers[HeaderNames.Authorization];
		if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
		{
			var token = authorization.Substring("Bearer ".Length).Trim();
			var jwtHandler = new JsonWebTokenHandler();

			if(jwtHandler.CanReadToken(token)) // it's a self contained access token and not encrypted
			{
				var issuer = jwtHandler.ReadJsonWebToken(token).Issuer; //.Equals("B2C-Authority"))
				if (issuer == Consts.MY_THIRD_PARTY_ISS) // Third party identity provider
				{
					return Consts.MY_THIRD_PARTY_SCHEME;
				}

				if (issuer == Consts.MY_AAD_ISS) // AAD
				{
					return Consts.MY_AAD_SCHEME;
				}
			}
		}

		// We don't know with it is
		return Consts.MY_AAD_SCHEME;
	};
});

示例

以下示例显示结合较低级别方案的高级别方案。 Google 身份验证用于质询,而 cookie 身份验证用于所有其他操作:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
       .AddCookie(options => options.ForwardChallenge = "Google")
       .AddGoogle(options => { });
}

下面的示例支持基于每个请求的动态选择方案。 也就是如何结合使用 Cookie 和 API 身份验证:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddCookie(options =>
        {
            // For example, can foward any requests that start with /api 
            // to the api scheme.
            options.ForwardDefaultSelector = ctx => 
               ctx.Request.Path.StartsWithSegments("/api") ? "Api" : null;
        })
        .AddYourApiAuth("Api");
}