昨天頭痛沒寫完(之前的待補有的已經補上了)
接續昨天提到的AuthenticationMiddleware
AuthenticationMiddleware.cs
public IAuthenticationSchemeProvider Schemes { get; set; }
public async Task Invoke(HttpContext context)
{
context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
{
OriginalPath = context.Request.Path,
OriginalPathBase = context.Request.PathBase
});
var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
{
var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
if (handler != null && await handler.HandleRequestAsync())
{
return;
}
}
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
if (result?.Principal != null)
{
context.User = result.Principal;
}
if (result?.Succeeded ?? false)
{
var authFeatures = new AuthenticationFeatures(result);
context.Features.Set<IHttpAuthenticationFeature>(authFeatures);
context.Features.Set<IAuthenticateResultFeature>(authFeatures);
}
}
await _next(context);
}
IAuthenticationSchemeProvider
負責提供 AuthenticationScheme
物件AuthenticationScheme
物件除了負責記錄驗證的類型外
也負責記錄所使用的IAuthenticationHandler
AuthenticationScheme.cs
public class AuthenticationScheme
{
public string Name { get; }
public string? DisplayName { get; }
public Type HandlerType { get; }
}
HandlerType
物件即為 對應的IAuthenticationHandler
public interface IAuthenticationSchemeProvider
{
Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync();
Task<AuthenticationScheme?> GetSchemeAsync(string name);
Task<AuthenticationScheme?> GetDefaultAuthenticateSchemeAsync();
Task<AuthenticationScheme?> GetDefaultChallengeSchemeAsync();
Task<AuthenticationScheme?> GetDefaultForbidSchemeAsync();
Task<AuthenticationScheme?> GetDefaultSignInSchemeAsync();
Task<AuthenticationScheme?> GetDefaultSignOutSchemeAsync();
void AddScheme(AuthenticationScheme scheme);
bool TryAddScheme(AuthenticationScheme scheme)
void RemoveScheme(string name);
Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync();
}
除了提供取驗證用的 AuthenticationScheme
外
也提供其他IAuthenticationHandler
會用到的方法的預設驗證方法
這邊使用微軟的nuget套件Microsoft.AspNetCore.Authentication.JwtBearer
首先我們要定義一個能取得token 的function
TokenService.cs
public class TokenService
{
private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler;
private readonly IConfiguration _configuration;
public TokenService(JwtSecurityTokenHandler jwtSecurityTokenHandler, IConfiguration configuration)
{
_jwtSecurityTokenHandler = jwtSecurityTokenHandler;
_configuration = configuration;
}
public string CreateJwtToken(Member member)
{
/// 設定使用者身分
var identity = new ClaimsIdentity(new[]
{
new Claim(JwtRegisteredClaimNames.Name, member.Account),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Email, member.Email),
});
var issuer = "issuer";//_configuration["jwt:Issuer"];
var key = "kewernmkjohiusdfy";//_configuration["jwt:key"];
/// 加密
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
/// 設定token
var securityTokenDescriptor = new SecurityTokenDescriptor
{
Issuer = issuer,
Subject = identity,
Expires = DateTime.Now.AddDays(7),
SigningCredentials = signingCredentials
};
/// 產生jwt Token
var securityToken = _jwtSecurityTokenHandler.CreateToken(securityTokenDescriptor);
var token = _jwtSecurityTokenHandler.WriteToken(securityToken);
return token;
}
public class Member
{
public string Account { get; set; }
public string Password { get; set; }
public string Email { get; set; }
}
}
做一個對應的Controller來讓使用者可以取得Token
[ApiController]
[Route("api/[controller]")]
public class TokenController
{
private readonly TokenService _tokenService;
private IReadOnlyList<Member> _fakeMembers = new List<Member>()
{
new() { Account = "Admin", Password = "Admin", Email = "admin@gmail.com" },
new() { Account = "User", Password = "User", Email = "user@gmail.com" }
};
public TokenController(TokenService tokenService)
{
_tokenService = tokenService;
}
[HttpPost]
public string CreateToken(LoginRequest loginRequest)
{
var member = _fakeMembers.FirstOrDefault(x => x.Account == loginRequest.Account && x.Password == loginRequest.Password);
if(member is null) throw new Exception("帳號或密碼錯誤");
return _tokenService.CreateJwtToken(member);
}
public record LoginRequest(string Account, string Password);
}
這個TokenController 仿造了登入取得token
最後需要program中加上jwt的驗證設定跟驗證middleware
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton(new JwtSecurityTokenHandler());
builder.Services.AddSingleton<TokenService>();
builder.Services.AddHealthChecks();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.IncludeErrorDetails = true;
var key = "kewernmkjohiusdfy";//builder.Configuration.GetValue<string>("JwtSettings:SignKey");
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "issuer",//builder.Configuration.GetValue<string>("JwtSettings:Issuer"),
ValidateLifetime = true,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
};
});
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/health").RequireAuthorization();
app.MapControllers();
await app.RunAsync();
實際上前幾篇提的介面的實作都在 AddJwtBearer
中,
Jwt的Default Scheme 是 Bearer
,
所以只要打api的時候request header 帶上 Bearer $jwttoken
就可以使用了
我們在healthcheck中加入驗證
可以透過打healthcheck確認/health