前言
在 ASP.NET Core 中,我們常使用基于 JWT 的認證:
services.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["JwtToken:Issuer"],
ValidAudience = Configuration["JwtToken:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtToken:SecretKey"]))
};
});
但有時候,我們需要使用自定義認證,比如使用QueryString(htttp://xxx?_key=xxx),隻要請求中包含的
_key
的值正确即可。
AddJwtBearer 實作原理
為了實作自定義認證,我們決定仿照
AddJwtBearer
的實作機制。
AddJwtBearer
實際執行的是
AddScheme
方法:
public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action<JwtBearerOptions> configureOptions)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>());
return builder.AddScheme<JwtBearerOptions, JwtBearerHandler>(authenticationScheme, displayName, configureOptions);
}
JwtBearerHandler
是具體的處理程式,繼承自
AuthenticationHandler<TOptions>
,主要代碼在
HandleAuthenticateAsync
内:
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
...
if (string.IsNullOrEmpty(token))
{
string authorization = Request.Headers.Authorization.ToString();
// If no authorization header found, nothing to process further
if (string.IsNullOrEmpty(authorization))
{
return AuthenticateResult.NoResult();
}
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("Bearer ".Length).Trim();
}
// If no token found, no further work possible
if (string.IsNullOrEmpty(token))
{
return AuthenticateResult.NoResult();
}
}
...
foreach (var validator in Options.SecurityTokenValidators)
{
if (validator.CanReadToken(token))
{
...
var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
{
Principal = principal,
SecurityToken = validatedToken
};
...
tokenValidatedContext.Success();
return tokenValidatedContext.Result!;
}
}
...
}
從
Request.Headers.Authorization
擷取token,然後用
Options.SecurityTokenValidators
驗證token合法後,傳回結果。
Demo
DemoAuthenticationOptions
建立
DemoAuthenticationOptions
,繼承自
AuthenticationSchemeOptions
:
public class DemoAuthenticationOptions : AuthenticationSchemeOptions
{
public const string Scheme = "Demo";
}
DemoAuthenticationHandler
建立
DemoAuthenticationHandler
,繼承自
AuthenticationHandler<DemoAuthenticationOptions>
:
public class DemoAuthenticationHandler : AuthenticationHandler<DemoAuthenticationOptions>
{
public DemoAuthenticationHandler(IOptionsMonitor<DemoAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{ }
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
throw new NotImplementedException();
}
}
實作 HandleAuthenticateAsync 方法
從請求的
Query
中擷取
key
,然後檢查是否合法:
protected async override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Query.TryGetValue("_key", out var keys))
{
return AuthenticateResult.NoResult();
}
var key = keys.FirstOrDefault();
//到資料庫檢索
if (key =="123456")
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "My IO")
};
var identity = new ClaimsIdentity(claims, DemoAuthenticationOptions.Scheme);
var identities = new List<ClaimsIdentity> { identity };
var principal = new ClaimsPrincipal(identities);
var ticket = new AuthenticationTicket(principal, DemoAuthenticationOptions.Scheme);
return AuthenticateResult.Success(ticket);
}
return AuthenticateResult.NoResult();
}
定義擴充方法
定義擴充方法,使用我們上面建立的
DemoAuthenticationHandler
:
public static class AuthenticationBuilderExtensions
{
public static AuthenticationBuilder AddDemoAuthentication(this AuthenticationBuilder authenticationBuilder, Action<DemoAuthenticationOptions> options)
{
return authenticationBuilder.AddScheme<DemoAuthenticationOptions, DemoAuthenticationHandler>(DemoAuthenticationOptions.Scheme, options);
}
}
使用
修改Startup.cs:
services.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = DemoAuthenticationOptions.Scheme;
option.DefaultChallengeScheme = DemoAuthenticationOptions.Scheme;
})
.AddDemoAuthentication(options => { });
結論
當不加
Query
或使用錯誤的
key
時,傳回401 認證失敗:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiATN381dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsQTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5CO3UTO4UjZ2YmMxQWZzEDNyYzX5ITM1ITM4IzLcNDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
僅當使用正确的
key
時,API 通路成功: