我正在使用ASP.NetCore 2.2(可能很快会升级到3.0)。我有一个Azure App Service应用程序。
我希望有一个API,客户端将使用API令牌(客户端密钥)进行身份验证,以便它们可以运行而无需交互式授权。
UI部分将需要Azure Active Directory身份验证。
如何将这两种不同的auth方法连接到我的ASP.Net Core应用程序中?
答案 0 :(得分:0)
首先,我们需要一个 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
}
}
然后一个接一个地注册多个身份验证方案:
// 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
});
最后,为了同时启用多种身份验证方案,我们需要覆盖默认策略:
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身份验证。
在使用客户机密测试上述请求时,