Tôi đã trả lời câu hỏi này: Cách bảo mật API Web ASP.NET 4 năm trước bằng cách sử dụng HMAC.
Giờ đây, rất nhiều thứ đã thay đổi trong bảo mật, đặc biệt là JWT đang trở nên phổ biến. Ở đây, tôi sẽ cố gắng giải thích cách sử dụng JWT theo cách đơn giản và cơ bản nhất có thể, vì vậy chúng tôi sẽ không bị lạc khỏi rừng của OWIN, Oauth2, ASP.NET Identity ... :).
Nếu bạn không biết mã thông báo JWT, bạn cần xem qua một chút về:
https://tools.ietf.org/html/rfc7519
Về cơ bản, mã thông báo JWT trông giống như:
<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>
Thí dụ:
chiều cao của chúng tôi để tin về việc để chiều cao về việc để cho đến việc: điều gì đó về việc để chiều cao như vậy
Mã thông báo JWT có ba phần:
- Tiêu đề: Định dạng JSON được mã hóa trong Base64
- Khiếu nại: Định dạng JSON được mã hóa trong Base64.
- Chữ ký: Được tạo và ký dựa trên Tiêu đề và Khiếu nại được mã hóa trong Base64.
Nếu bạn sử dụng trang web jwt.io với mã thông báo ở trên, bạn có thể giải mã mã thông báo và xem nó như dưới đây:
Về mặt kỹ thuật, JWT sử dụng chữ ký được ký từ các tiêu đề và khiếu nại với thuật toán bảo mật được chỉ định trong các tiêu đề (ví dụ: HMACSHA256). Do đó, JWT bắt buộc phải được chuyển qua HTTP nếu bạn lưu trữ bất kỳ thông tin nhạy cảm nào trong các khiếu nại.
Bây giờ, để sử dụng xác thực JWT, bạn không thực sự cần một phần mềm trung gian OWIN nếu bạn có một hệ thống Api Web kế thừa. Khái niệm đơn giản là cách cung cấp mã thông báo JWT và cách xác thực mã thông báo khi có yêu cầu. Đó là nó.
Quay lại bản demo, để giữ cho mã thông báo JWT nhẹ, tôi chỉ lưu trữ username
vàexpiration time
trong JWT. Nhưng theo cách này, bạn phải xây dựng lại danh tính địa phương mới (hiệu trưởng) để thêm nhiều thông tin như: vai trò .. nếu bạn muốn thực hiện ủy quyền vai trò. Nhưng, nếu bạn muốn thêm thông tin vào JWT, điều đó tùy thuộc vào bạn: nó rất linh hoạt.
Thay vì sử dụng phần mềm trung gian OWIN, bạn chỉ cần cung cấp điểm cuối mã thông báo JWT bằng cách sử dụng hành động từ bộ điều khiển:
public class TokenController : ApiController
{
// This is naive endpoint for demo, it should use Basic authentication
// to provide token or POST request
[AllowAnonymous]
public string Get(string username, string password)
{
if (CheckUser(username, password))
{
return JwtManager.GenerateToken(username);
}
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
public bool CheckUser(string username, string password)
{
// should check in the database
return true;
}
}
Đây là một hành động ngây thơ; trong sản xuất, bạn nên sử dụng yêu cầu POST hoặc điểm cuối Xác thực cơ bản để cung cấp mã thông báo JWT.
Làm thế nào để tạo mã thông báo dựa trên username
?
Bạn có thể sử dụng gói NuGet được gọi System.IdentityModel.Tokens.Jwt
từ Microsoft để tạo mã thông báo hoặc thậm chí một gói khác nếu bạn muốn. Trong bản demo, tôi sử dụng HMACSHA256
với SymmetricKey
:
/// <summary>
/// Use the below code to generate symmetric Secret Key
/// var hmac = new HMACSHA256();
/// var key = Convert.ToBase64String(hmac.Key);
/// </summary>
private const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";
public static string GenerateToken(string username, int expireMinutes = 20)
{
var symmetricKey = Convert.FromBase64String(Secret);
var tokenHandler = new JwtSecurityTokenHandler();
var now = DateTime.UtcNow;
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, username)
}),
Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(symmetricKey),
SecurityAlgorithms.HmacSha256Signature)
};
var stoken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(stoken);
return token;
}
Điểm cuối để cung cấp mã thông báo JWT đã hoàn tất. Bây giờ, làm thế nào để xác nhận JWT khi yêu cầu đến? Trong bản demo tôi đã xây dựng
JwtAuthenticationAttribute
kế thừa từ đó IAuthenticationFilter
(chi tiết hơn về bộ lọc xác thực ở đây ).
Với thuộc tính này, bạn có thể xác thực bất kỳ hành động nào: bạn chỉ cần đặt thuộc tính này vào hành động đó.
public class ValueController : ApiController
{
[JwtAuthentication]
public string Get()
{
return "value";
}
}
Bạn cũng có thể sử dụng phần mềm trung gian OWIN hoặc DelegateHander nếu bạn muốn xác thực tất cả các yêu cầu đến cho WebAPI của mình (không dành riêng cho Trình điều khiển hoặc hành động)
Dưới đây là phương pháp cốt lõi từ bộ lọc xác thực:
private static bool ValidateToken(string token, out string username)
{
username = null;
var simplePrinciple = JwtManager.GetPrincipal(token);
var identity = simplePrinciple.Identity as ClaimsIdentity;
if (identity == null)
return false;
if (!identity.IsAuthenticated)
return false;
var usernameClaim = identity.FindFirst(ClaimTypes.Name);
username = usernameClaim?.Value;
if (string.IsNullOrEmpty(username))
return false;
// More validate to check whether username exists in system
return true;
}
protected Task<IPrincipal> AuthenticateJwtToken(string token)
{
string username;
if (ValidateToken(token, out username))
{
// based on username to get more information from database
// in order to build local identity
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username)
// Add more claims if needed: Roles, ...
};
var identity = new ClaimsIdentity(claims, "Jwt");
IPrincipal user = new ClaimsPrincipal(identity);
return Task.FromResult(user);
}
return Task.FromResult<IPrincipal>(null);
}
Quy trình công việc là, sử dụng thư viện JWT (gói NuGet ở trên) để xác thực mã thông báo JWT và sau đó quay lại ClaimsPrincipal
. Bạn có thể thực hiện xác nhận nhiều hơn như kiểm tra xem người dùng có tồn tại trên hệ thống của bạn không và thêm các xác nhận tùy chỉnh khác nếu bạn muốn. Mã để xác thực mã thông báo JWT và lấy lại tiền gốc:
public static ClaimsPrincipal GetPrincipal(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
if (jwtToken == null)
return null;
var symmetricKey = Convert.FromBase64String(Secret);
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
return principal;
}
catch (Exception)
{
//should write log
return null;
}
}
Nếu mã thông báo JWT được xác thực và tiền gốc được trả lại, bạn nên xây dựng một danh tính cục bộ mới và đưa thêm thông tin vào đó để kiểm tra ủy quyền vai trò.
Hãy nhớ thêm config.Filters.Add(new AuthorizeAttribute());
(ủy quyền mặc định) trong phạm vi toàn cầu để ngăn chặn mọi yêu cầu ẩn danh đối với tài nguyên của bạn.
Bạn có thể sử dụng Postman để kiểm tra bản demo:
Mã thông báo yêu cầu (ngây thơ như tôi đã đề cập ở trên, chỉ để dùng thử):
GET http://localhost:{port}/api/token?username=cuong&password=1
Đặt mã thông báo JWT trong tiêu đề cho yêu cầu được ủy quyền, ví dụ:
GET http://localhost:{port}/api/value
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s
Bản demo được đặt ở đây: https://github.com/cuongle/WebApi.Jwt