前言
小項目中需要添加 Api 請求權限認證, 並且只是專用網絡內使用,於是只想簡單得認證下是否可以訪問, 順便也是一種學習的過程,簡單記錄一下
要點
實現 IAuthenticationHandler 接口:4 個方法
- 首先會調用 InitializeAsync 獲取到 scheme 和 context
- 然后調用 AuthenticateAsync ,在這里獲取 context 中的 Header 中需要傳過來的驗證信息,然后進行相關驗證,根據不同的結果會分別調用 ChallengeAsync 或者 ForbidAsync
public class HeaderAuth : IAuthenticationHandler {
public AuthenticationScheme Scheme { get; private set; }
public HttpContext CurrentContext { get; private set; }
public Task<AuthenticateResult> AuthenticateAsync() {
var token = CurrentContext.Request.Headers[GuidToken.GUID_TOKEN_KEY].ToString();
var (isValid, tokenEntity) = GuidToken.Valid(token);
if (!isValid || tokenEntity == null) {
return Task.FromResult(AuthenticateResult.Fail("未登錄或授權已過期。"));
}
// 生成 AuthenticationTicket
AuthenticationTicket ticket = new AuthenticationTicket(tokenEntity.ToClaimsPrincipal(), Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
public Task ChallengeAsync(AuthenticationProperties properties) {
CurrentContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return Task.CompletedTask;
}
public Task ForbidAsync(AuthenticationProperties properties) {
CurrentContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return Task.CompletedTask;
}
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) {
Scheme = scheme;
CurrentContext = context;
return Task.CompletedTask;
}
}
GuidToken 類就是我們自定義的 token 管理器
public class GuidToken {
public const string GUID_TOKEN_NAME = "MtGuidTokenAuthentication";
public const string DEFAULT_AUTHENTICATION_TYPE = "local";
public const int TOKEN_LENGTH = 32;
public const string GUEST = "GUEST";
public const string DEFAULT_ROLE = "USER";
public const string DEFAULT_OPENID = "DEFAULT_OPENID";
public const string GUID_TOKEN_KEY = "Token";
private static int expireDuration = 0;
public string OpenId { get; set; }
public string Role { get; set; }
public DateTime Expire { get; set; }
private static readonly Dictionary<string, GuidToken> tokenCache = new Dictionary<string, GuidToken>();
public static (bool, GuidToken) Valid(string token) {
if (string.IsNullOrEmpty(token) || token.Length != TOKEN_LENGTH) {
return (false, null);
}
// 從 Session 中獲取令牌實體
GuidToken tokenEntity = GetTokenCache();
if (tokenEntity == null) {
return (false, null);
} else {
tokenEntity.Expire = DateTime.Now.AddMinutes(expireDuration);
}
return (true, tokenEntity);
GuidToken GetTokenCache() {
if (tokenCache.TryGetValue(token, out var val)) {
if (val.Expire > DateTime.Now) return val;
else tokenCache.Remove(token);
}
return null;
}
}
public static string Create(string openId = DEFAULT_OPENID, string role = DEFAULT_ROLE, int minutes = 30) {
var token = Guid.NewGuid().ToString("N");
expireDuration = minutes;
var entity = new GuidToken {
OpenId = openId,
Role = role,
Expire = DateTime.Now.AddMinutes(expireDuration)
};
tokenCache.Add(token, entity);
return token;
}
/// <summary>
/// 令牌實體 轉 ClaimsPrincipal
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public ClaimsPrincipal ToClaimsPrincipal() {
var claimsIdentity = new ClaimsIdentity(new Claim[] {
new Claim(ClaimTypes.Name, OpenId),
new Claim(ClaimTypes.Role, Role),
}, GuidToken.DEFAULT_AUTHENTICATION_TYPE);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
return claimsPrincipal;
}
}
最后就是使用方式
- 在 Startup 中配置
public void ConfigureServices(IServiceCollection services) {
// 注冊使用
services.AddAuthentication(options => {
options.AddScheme<HeaderAuth>(GuidToken.GUID_TOKEN_NAME, "Default Guid Token");
options.DefaultAuthenticateScheme = GuidToken.GUID_TOKEN_NAME;
options.DefaultChallengeScheme = GuidToken.GUID_TOKEN_NAME;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseCors("any");
app.UseStaticFiles();
// 開啟認證
app.UseAuthentication();
app.UseRouting();
// 開啟授權
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
});
}
- 在控制器中使用標簽
[Authorize]
public class JobController : ControllerBase {}