如何为API和UI设置不同的身份验证

时间:2019-10-10 23:46:24

标签: asp.net-core

我正在使用ASP.NetCore 2.2(可能很快会升级到3.0)。我有一个Azure App Service应用程序。

我希望有一个API,客户端将使用API​​令牌(客户端密钥)进行身份验证,以便它们可以运行而无需交互式授权。

UI部分将需要Azure Active Directory身份验证。

如何将这两种不同的auth方法连接到我的ASP.Net Core应用程序中?

1 个答案:

答案 0 :(得分:0)

操作方法

  1. 首先,我们需要一个 AuthenticationHandler Options 来使用API​​令牌(客户端密钥)对请求进行身份验证。假设您已经创建了这样的身份验证处理程序和选项:

    public class ClientSecretAuthenOpts : AuthenticationSchemeOptions
    {
        public const string DefaultAuthenticationSchemeName = "ClientSecret";
    }
    
    public class ClientSecretAuthenticationHandler : AuthenticationHandler<ClientSecretAuthenOpts>
    {
        public ClientSecretAuthenticationHandler(IOptionsMonitor<ClientSecretAuthenOpts> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) 
            : base(options, logger, encoder, clock) { }
    
        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            // ... authenticate request 
        }
    }
    
  2. 然后一个接一个地注册多个身份验证方案

    // add mulitple authentication schemes
    services.AddAuthentication(AzureADDefaults.AuthenticationScheme)           // set AzureAD as the default for users (using the UI)
        .AddAzureAD(options => Configuration.Bind("AzureAD", options))         // setup AzureAD Authentication
        .AddScheme<ClientSecretAuthenOpts,ClientSecretAuthenticationHandler>(  // setup ClientSecret Authentication
            ClientSecretAuthenOpts.DefaultAuthenticationSchemeName,
            opts=>{ }
        );
    
    // post configuration for OIDC
    services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>{
        options.Authority = options.Authority + "/v2.0/";         // Microsoft identity platform
        options.TokenValidationParameters.ValidateIssuer = false; // accept several tenants 
    });
    
  3. 最后,为了同时启用多种身份验证方案,我们需要覆盖默认策略

    services.AddAuthorization(opts => {
        // allow AzureAD & our own ClientSecret Authentication at the same time
        var pb = new AuthorizationPolicyBuilder(
            ClientSecretAuthenOpts.DefaultAuthenticationSchemeName,
            "AzureAD"
        );  
        opts.DefaultPolicy = pb.RequireAuthenticatedUser().Build();
    });
    

演示和测试

假设您的API令牌(客户端机密)在“请求标头”中的发送方式如下:

GET https://localhost:5001/Home/Privacy HTTP/1.1
Api-Subscription-Id: Smith
Api-Subscription-Key: top secret

为避免对标题名称进行硬编码,我在选项中添加了两个属性:

public class ClientSecretAuthenOpts : AuthenticationSchemeOptions
{
    public const string DefaultAuthenticationSchemeName = "ClientSecret";
    public string ApiClientIdHeadername {get;set;}= "Api-Subscription-Id";
    public string ApiClientTokenHeaderName {get;set;}= "Api-Subscription-Key";
}

为了对上述请求进行身份验证,我创建了一个自定义身份验证处理程序,如下所示:

public class ClientSecretAuthenticationHandler : AuthenticationHandler<ClientSecretAuthenOpts>
{
    public ClientSecretAuthenticationHandler(IOptionsMonitor<ClientSecretAuthenOpts> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) 
        : base(options, logger, encoder, clock) { }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // if there's no header for Client ID & Client Sercet, skip
        if(
            Context.Request.Headers.TryGetValue(Options.ApiClientIdHeadername, out var clientIdHeader) &&
            Context.Request.Headers.TryGetValue(Options.ApiClientTokenHeaderName, out var clientSecretHeader) 
        ){
            // validate client's id & secret
            var clientId = clientIdHeader.FirstOrDefault();
            var clientKey = clientSecretHeader.FirstOrDefault();
            var (valid, id) = await ValidateApiKeyAsync(clientId, clientKey);

            if(!valid){
                return AuthenticateResult.Fail($"invalid token:{clientKey}");
            }else{
                var principal = new ClaimsPrincipal(id);
                var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), this.Scheme.Name);
                return AuthenticateResult.Success(ticket);
            }
        }
        return AuthenticateResult.NoResult();
    }

    private Task<(bool, ClaimsIdentity)> ValidateApiKeyAsync(string clientId,string clientSecret)
    {
        ClaimsIdentity id = null;
        // fake: need check key against the Database or other service
        if(clientId=="Smith" && clientSecret == "top secret"){
            id = new ClaimsIdentity(
                new Claim[]{ 
                    new Claim(ClaimTypes.NameIdentifier, "client id from db or from the request"),
                    new Claim("Add Any Claim", "add the value as you like"),
                    // ... 
                }
                ,this.Scheme.Name
            );
            return Task.FromResult((true, id));
        }
        return Task.FromResult((false,id));
    }

}

测试

假设我们有一个带有[Authorize]属性的控制器Action

[Authorize]
public IActionResult Privacy()
{
    return Ok("hello,world");
}

在浏览器(UI,无标题)中访问URL时,如果用户未登录,则该用户将被重定向到Azure AD身份验证。

在使用客户机密测试上述请求时,

我们将收到“ hello,world”响应: enter image description here