1. Khái niệm xác thực Bearer ------------ Xác thực Bearer cũng là một phần của tiêu chuẩn giao thức HTTP.
Trong xác thực Bearer, token được gọi là BEARER_TOKEN hoặc access_token, việc phát hành và xác minh token hoàn toàn do ứng dụng của bạn kiểm soát, không phụ thuộc vào hệ thống hay máy chủ web. Cách thức yêu cầu tiêu chuẩn cho xác thực Bearer như sau:
Authorization: Bearer [BEARER_TOKEN]
Lợi ích khi sử dụng xác thực Bearer:
- CORS: Cookie kết hợp với CORS không thể hoạt động giữa các miền khác nhau. Trong khi đó, xác thực Bearer có thể truyền thông tin người dùng qua header HTTP ở bất kỳ miền nào.
- Thân thiện với thiết bị di động: Khi làm việc trên nền tảng gốc (iOS, Android, WindowsPhone...), việc dùng cookie không phải là lựa chọn tối ưu vì bạn phải tương tác với bộ nhớ cookie. Xác thực Bearer đơn giản hơn nhiều.
- Chống CSRF: Vì không còn dựa vào cookie, nên xác thực Bearer tránh được tấn công CSRF.
- Tiêu chuẩn hóa: Trong xác thực cookie, nếu người dùng chưa đăng nhập sẽ trả về mã
302để chuyển hướng đến trang đăng nhập, điều này khó xử lý trong môi trường không phải trình duyệt. Xác thực Bearer trả về mã401 challengechuẩn.
2. JWT (Json Web Token) ---------------------
Xác thực Bearer dựa trên việc sử dụng token gọi là BEARER_TOKEN, và cách mã hóa token phổ biến nhất hiện nay là JSON WEB TOKEN.
Phần Header
Header thường gồm hai phần:
alg: thuật toán hash được sử dụng, ví dụ như HMAC SHA256 hoặc RSAtyp: loại token, ở đây là JWT
{
"alg": "HS256",
"typ": "JWT"
}
Sau đó mã hóa bằng Base64Url thành phần đầu tiên:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.<second part>.<third part>
Phần Payload
Đây là nơi lưu trữ dữ liệu chính của JWT, chứa nhiều loại thông tin gọi là các claim (đề xuất).
Các claim bao gồm ba loại:
- Reserved claims: Các claim được quy định trước nhưng không bắt buộc, bao gồm
iss(issuer),exp(expiration time),sub(subject),aud(audience)... (dùng ba ký tự để giữ cho JWT gọn gàng) - Public claims: Các claim công khai, có thể tùy ý định nghĩa, nhưng cần tránh trùng với chuẩn IANA JSON Web Token
- Private claims: Các claim riêng tư, dùng để chia sẻ thông tin tùy chỉnh giữa các bên
Một payload đơn giản có thể như sau:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Phần này cũng được mã hóa Base64Url thành phần thứ hai:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.<third part>
Phần Chữ ký (Signature)
Chữ ký dùng để xác minh người gửi và đảm bảo token không bị sửa đổi.
Khi tạo phần này, bạn đã có Header và Payload đã mã hóa, sau đó sử dụng khóa bí mật lưu trên server để ký. Một JWT hoàn chỉnh sẽ trông như thế này:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Lợi ích của JWT:
- Khả năng tương thích: Vì JSON phổ biến, nên JWT có thể được sử dụng trong nhiều ngôn ngữ như Java, JavaScript, Node.js, PHP...
- Gọn nhẹ: Cấu trúc JWT rất đơn giản, kích thước nhỏ, có thể truyền qua header HTTP dễ dàng.
- Dễ mở rộng: JWT tự chứa đầy đủ thông tin cần thiết, không cần lưu trạng thái phiên trên server, dễ dàng mở rộng ứng dụng.
Đối với chi tiết hơn về JWT, có rất nhiều tài liệu trên mạng, nên không trình bày thêm. Tiếp theo, chúng ta sẽ xem cách sử dụng JwtBearer trong ASP.NET Core.
DEMO
1. Thêm gói thư viện JWT
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 2.0.0
2. Cấu hình trong lớp Startup:
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
ValidIssuer = "http://localhost:5200",
ValidAudience = "api",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Consts.Secret))
/***********************************Các tham số mặc định của TokenValidationParameters***********************************/
// RequireSignedTokens = true,
// SaveSigninToken = false,
// ValidateActor = false,
// Thiết lập hai tham số sau thành false sẽ bỏ qua kiểm tra Issuer và Audience, tuy nhiên không khuyến nghị.
// ValidateAudience = true,
// ValidateIssuer = true,
// ValidateIssuerSigningKey = false,
// Yêu cầu claims chứa thời gian hết hạn
// RequireExpirationTime = true,
// Cho phép độ lệch thời gian giữa server và client
// ClockSkew = TimeSpan.FromSeconds(300),
// Kiểm tra thời hạn token, so sánh thời điểm hiện tại với NotBefore và Expires trong claims
// ValidateLifetime = true
};
o.Events = new JwtBearerEvents() {
OnMessageReceived = context => {
// Hỗ trợ nhận token qua query string
context.Token = context.Request.Query["access_token"];
return Task.CompletedTask;
}
};
});
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "JWTDemo v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
3. Tạo action để tạo token:
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody] UserDto userDto)
{
// Xác thực người dùng
var user = _store.FindUser(userDto.UserName, userDto.Password);
if (user == null) return Unauthorized();
// Payload JWT
var key = Encoding.ASCII.GetBytes(Consts.Secret);
var authTime = DateTime.UtcNow;
var expiresAt = authTime.AddDays(7);
var tokenDescriptor = new SecurityTokenDescriptor
{
// Nội dung
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(JwtClaimTypes.Audience,"api"),
new Claim(JwtClaimTypes.Issuer,"http://localhost:5200"),
new Claim(JwtClaimTypes.Id, user.Id.ToString()),
new Claim(JwtClaimTypes.Name, user.UserName),
new Claim(JwtClaimTypes.Email, user.Email),
new Claim(JwtClaimTypes.PhoneNumber, user.PhoneNumber)
}),
// Thời gian hết hạn
Expires = expiresAt,
// Chữ ký
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return Ok(new
{
access_token = tokenString,
token_type = "Bearer",
profile = new
{
sid = user.Id,
name = user.UserName,
auth_time = new DateTimeOffset(authTime).ToUnixTimeSeconds(),
expires_at = new DateTimeOffset(expiresAt).ToUnixTimeSeconds()
}
});
}
4. Thêm tài nguyên được bảo vệ
5. Chạy thử nghiệm
- 5.1 Truy cập trực tiếp vào endpoint WeatherForecast sẽ trả về mã lỗi 401.
- 5.2 Gọi endpoint Authenticate với tham số username=a và pwd=123 để lấy token.
- 5.3 Gửi yêu cầu WeatherForecast kèm theo token, thành công.
Mã nguồn demo: https://gitee.com/xiaoqingyao/authentication-netcore
Nguồn: https://www.cnblogs.com/RainingNight/p/jwtbearer-authentication-in-asp-net-core.html