jwt全名是json web token,是基於開放標準RFC 7519的機制,用來提供身分認證與訊息交換。
由於htttp的無狀態特性,server端和client是不會記得每個request是誰發過來的,也不會知道是否已驗證過身分,如果每次user發request都要到DB驗證身分,會增加server與DB的成本。因此因應這個狀況一般會用session cookie的方式解決,另外還有一種就是這篇要介紹的 jwt token,整個驗證流程如下:
關於jwt結構與運作方式,可參考jwt.io的說明
在我們前天建立的Server專案中,從nuget取得官方的Microsoft.AspNetCore.Authentication.JwtBearer擴充套件
修改Startup.cs的ConfigureServices方法,這邊設定要驗證系統的有效期限和符合對稱式加密的簽章:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(option =>
{
option.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["jwt:Key"])),
ClockSkew = TimeSpan.Zero
};
});
}
Start.cs的Configure方法中,加上身分驗證授權的middleware,確保每次進來的request都會進行身分驗證
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//略...
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
}
建立AuthController
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly IConfiguration configuration;
public AuthController(IConfiguration configuration)
{
this.configuration = configuration;
}
[HttpPost("Login")]
public async Task<ActionResult<UserToken>> Login([FromBody] UserInfo userInfo)
{
//驗證方式純為Demo用
if (userInfo.Email == "abc@gmail.com" && userInfo.Password == "abc123")
{
return BuildToken(userInfo);
}
return BadRequest("Login failed");
}
/// <summary>
/// 建立Token
/// </summary>
/// <param name="userInfo"></param>
/// <returns></returns>
private UserToken BuildToken(UserInfo userInfo)
{
//記在jwt payload中的聲明,可依專案需求自訂Claim
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, userInfo.Email),
new Claim(ClaimTypes.Role,"admin")
};
//取得對稱式加密 JWT Signature 的金鑰
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["jwt:Key"]));
var credential = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
//設定token有效期限
DateTime expireTime = DateTime.Now.AddMinutes(30);
//產生token
JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
JwtSecurityToken jwtSecurityToken = new JwtSecurityToken(
issuer: null,
audience: null,
claims: claims,
expires: expireTime,
signingCredentials: credential
);
string jwtToken = jwtSecurityTokenHandler.WriteToken(jwtSecurityToken);
//建立UserToken物件後回傳client
UserToken userToken = new UserToken()
{
StatusCode = System.Net.HttpStatusCode.OK,
token = jwtToken,
ExpireTime = expireTime
};
return userToken;
}
}
現在透過/api/Auth/Login這個Api,只要登入成功就可以取得token,用postman測試看看
在輸入帳密後,也確實取得token了。如果要將Api啟用驗證,只要掛上[Authorize]就可以了
例如我們新增一個ValueController,並加上[Authorize]:
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ValueController : ControllerBase
{
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "aaa", "bbb" };
}
}
使用postman在Authorization頁籤中,TYPE選擇Bearer Token,再將剛剛server回傳的token填入,再發出request,可以看到通過驗證並且取得回傳內容。
順利產生完jwt token之後,明天我們來學習在blazor WebAssembly中,要怎麼使用AuthenticationStateProvider來進行jwt認證。