在现代的Web应用中,安全认证是一个核心功能。随着微服务架构的流行,传统的中心化认证方式(如使用独立的安全令牌服务STS)可能不再适用。本文将探讨如何在不依赖于独立身份服务器的情况下,使用JSON Web Tokens(JWT)在ASP.NETWeb API中实现基于令牌的认证机制。
JSON Web Tokens(JWT)是一种基于互联网工程任务组(IETF)标准的令牌认证机制,被Web应用用来在依赖方之间传递声明。简单来说,JWT允许用户输入用户名和密码以获取一个令牌,该令牌允许他们访问特定资源,而无需再次使用用户名和密码。一旦获取了令牌,用户就可以在请求中提供这个令牌,以访问特定资源。
JWT是一个由三个部分组成的JSON编码字符串,这三部分通过点分隔,分别是头部(header)、载荷(payload)和签名(signature)。头部通常包含令牌的类型(即JWT)和所使用的签名算法,如HMAC SHA256或RSA。载荷包含所传递的声明,例如用户ID、用户名、角色等。签名用于验证消息在传输过程中未被篡改,并且,对于使用私钥签名的令牌,还可以验证发送者的身份。
将使用ASP.NETWeb API和一个名为Jwt的库来实现一个基本的认证解决方案。以下是实现的主要步骤:
当用户在应用中注册时,会将他们的详细信息保存到数据库中,并使用保存的信息创建一个JWT令牌,然后将令牌和新用户的详细信息一起发送回客户端应用。新用户详细信息将允许以REST风格再次访问用户。现在他们有了令牌,客户端应用将在用户对服务器的每个请求中通过Authorization头部包含这个令牌。
[AllowAnonymous]
[Route("signup")]
[HttpPost]
public HttpResponseMessage Register(RegisterViewModel model)
{
HttpResponseMessage response;
if (ModelState.IsValid)
{
var existingUser = db.Users.FirstOrDefault(u => u.Email == model.Email);
if (existingUser != null)
{
return Request.CreateResponse(HttpStatusCode.BadRequest, "User already exists.");
}
// 创建用户并保存到数据库
var user = CreateUser(model);
object dbUser;
// 创建令牌
var token = CreateToken(user, out dbUser);
response = Request.CreateResponse(new { dbUser, token });
}
else
{
response = Request.CreateResponse(HttpStatusCode.BadRequest, new { success = false });
}
return response;
}
类似于注册方法,当用户返回登录应用时,会检查数据库中的凭据是否有效,如果是,创建一个令牌并将其发送回客户端应用,连同他们的用户详细信息。从那时起,令牌将包含在他们通过Authorization头部对服务器的每个请求中。
[AllowAnonymous]
[Route("signin")]
[HttpPost]
public HttpResponseMessage Login(LoginViewModel model)
{
HttpResponseMessage response = null;
if (ModelState.IsValid)
{
var existingUser = db.Users.FirstOrDefault(u => u.Email == model.Email);
if (existingUser == null)
{
response = Request.CreateResponse(HttpStatusCode.NotFound);
}
else
{
var loginSuccess = string.Equals(EncryptPassword(model.Password, existingUser.Salt), existingUser.PasswordHash);
if (loginSuccess)
{
object dbUser;
var token = CreateToken(existingUser, out dbUser);
response = Request.CreateResponse(new { dbUser, token });
}
}
}
else
{
response = Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
return response;
}
如何检查他们是否可以访问安全资源?通过ASP.NETWeb API消息处理器来实现。消息处理器基本上会检查从客户端应用发送的请求是否包含带有有效令牌的Authorization头部,如果没有,则将其作为普通请求传播。如果它有一个授权头部,会检查令牌中传递的任何声明或角色,并设置执行身份主体为一个包含令牌中声明的新ClaimsPrincipal。
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage errorResponse = null;
try
{
IEnumerable<string> authHeaderValues;
request.Headers.TryGetValues("Authorization", out authHeaderValues);
if (authHeaderValues == null)
return base.SendAsync(request, cancellationToken);
var bearerToken = authHeaderValues.ElementAt(0);
var token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
var secret = ConfigurationManager.AppSettings.Get("jwtKey");
secret = "secretKey";
Thread.CurrentPrincipal = ValidateToken(token, secret, true);
if (HttpContext.Current != null)
{
HttpContext.Current.User = Thread.CurrentPrincipal;
}
}
catch (SignatureVerificationException ex)
{
errorResponse = request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex.Message);
}
catch (Exception ex)
{
errorResponse = request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message);
}
return errorResponse != null ? Task.FromResult(errorResponse) : base.SendAsync(request, cancellationToken);
}
为了测试这个机制,创建了一个包含书籍列表的安全资源,并在控制器上添加了Authorize属性。在登录之前,无法访问这个资源。登录后,能够访问这个非常安全的Books资源。