ASP.NET Web API 的 JWT 身份验证 [英] JWT authentication for ASP.NET Web API
问题描述
我试图在我的 Web API 应用程序中支持 JWT 不记名令牌(JSON Web 令牌),但我迷路了.
我看到了对 .NET Core 和 OWIN 应用程序的支持.
我目前在 IIS 中托管我的应用程序.
如何在我的应用程序中实现此身份验证模块?有什么方法可以使用类似于我使用表单/Windows 身份验证的方式的
从技术上讲,JWT 使用从标头和声明中签名的签名,并使用标头中指定的安全算法(例如:HMACSHA256).因此,如果您在其声明中存储任何敏感信息,则必须通过 HTTP 传输 JWT.
现在,为了使用 JWT 身份验证,如果您拥有旧版 Web Api 系统,则您实际上并不需要 OWIN 中间件.简单的概念是如何提供 JWT 令牌以及如何在请求到来时验证令牌.就是这样.
在我创建的 演示 (github) 中,为了保持 JWT 令牌的轻量级,我只存储 username
和 expiration time
.但是这样的话,你必须重新构建新的本地身份(principal)来添加更多的信息,比如角色,如果你想做角色授权等. 但是,如果你想在 JWT 中添加更多信息,这取决于你:它非常灵活.
您可以简单地使用控制器操作提供 JWT 令牌端点,而不是使用 OWIN 中间件:
public class TokenController : ApiController{//这是演示的天真端点,它应该使用基本身份验证//提供令牌或POST请求[允许匿名]公共字符串获取(字符串用户名,字符串密码){如果(检查用户(用户名,密码)){返回 JwtManager.GenerateToken(username);}抛出新的 HttpResponseException(HttpStatusCode.Unauthorized);}public bool CheckUser(字符串用户名,字符串密码){//应该检查数据库返回真;}}
这是一个幼稚的行为;在生产中,您应该使用 POST 请求或基本身份验证端点来提供 JWT 令牌.
如何根据用户名
生成token?
您可以使用 Microsoft 提供的名为 System.IdentityModel.Tokens.Jwt
的 NuGet 包来生成令牌,如果您愿意,甚至可以使用其他包.在演示中,我将 HMACSHA256
与 SymmetricKey
一起使用:
//////使用以下代码生成对称密钥///var hmac = new HMACSHA256();///var key = Convert.ToBase64String(hmac.Key);///</总结>private const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";公共静态字符串 GenerateToken(字符串用户名,int expireMinutes = 20){var symmetricKey = Convert.FromBase64String(Secret);var tokenHandler = new JwtSecurityTokenHandler();var now = DateTime.UtcNow;var tokenDescriptor = 新的 SecurityTokenDescriptor{主题 = 新的 ClaimsIdentity(new[]{新索赔(ClaimTypes.Name,用户名)}),Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),SigningCredentials = 新的 SigningCredentials(新的对称安全密钥(对称密钥),SecurityAlgorithms.HmacSha256Signature)};var stoken = tokenHandler.CreateToken(tokenDescriptor);var token = tokenHandler.WriteToken(stoken);返回令牌;}
提供 JWT 令牌的端点已完成.
请求到来时如何验证JWT?
在演示中,我已经构建了JwtAuthenticationAttribute
继承自 IAuthenticationFilter
(有关此处).
使用此属性,您可以验证任何操作:您只需将此属性放在该操作上即可.
public class ValueController : ApiController{[JwtAuthentication]公共字符串 Get(){返回值";}}
如果要验证 WebAPI 的所有传入请求(不特定于控制器或操作),也可以使用 OWIN 中间件或 DelegateHander
以下是认证过滤器的核心方法:
private static bool ValidateToken(string token, out string username){用户名 = 空;var simplePrinciple = JwtManager.GetPrincipal(token);var identity = simplePrinciple.Identity as ClaimsIdentity;if (identity == null || !identity.IsAuthenticated)返回假;var usernameClaim = identity.FindFirst(ClaimTypes.Name);username = usernameClaim?.Value;if (string.IsNullOrEmpty(username))返回假;//更多验证以检查用户名是否存在于系统中返回真;}受保护的任务<IPrincipal>AuthenticateJwtToken(字符串令牌){字符串用户名;if (ValidateToken(token, out username)){//根据用户名从数据库中获取更多信息//为了建立本地身份var claim = new List{新索赔(ClaimTypes.Name,用户名)//如果需要,添加更多声明:角色,...};var identity = new ClaimsIdentity(claims, Jwt");IPrincipal user = new ClaimsPrincipal(identity);返回 Task.FromResult(user);}return Task.FromResult(null);}
工作流程是使用 JWT 库(上面的 NuGet 包)验证 JWT 令牌,然后返回 ClaimsPrincipal
.您可以执行更多验证,例如检查用户是否存在于您的系统中,并根据需要添加其他自定义验证.
验证 JWT 令牌并取回主体的代码:
public static ClaimsPrincipal GetPrincipal(string token){尝试{var tokenHandler = new JwtSecurityTokenHandler();var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;如果(jwtToken == null)返回空;var symmetricKey = Convert.FromBase64String(Secret);varvalidationParameters = new TokenValidationParameters(){RequireExpirationTime = 真,ValidateIssuer = 假,ValidateAudience = 假,IssuerSigningKey = 新的对称安全密钥(对称密钥)};SecurityToken securityToken;var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);返还本金;}捕获(异常){//应该写日志返回空;}}
如果验证了 JWT 令牌并返回了主体,您应该构建一个新的本地身份并将更多信息放入其中以检查角色授权.
记得在全局范围内添加 config.Filters.Add(new AuthorizeAttribute());
(默认授权),以防止对您的资源进行任何匿名请求.
您可以使用 Postman 来测试 demo:
请求令牌(如我上面提到的那样幼稚,仅用于演示):
GET http://localhost:{port}/api/token?username=cuong&password=1
将 JWT 令牌放在授权请求的标头中,例如:
GET http://localhost:{port}/api/value授权:承载eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s
演示可以在这里找到:https://github.com/cuongle/WebApi.Jwt一个>
I'm trying to support JWT bearer token (JSON Web Token) in my web API application and I'm getting lost.
I see support for .NET Core and for OWIN applications.
I'm currently hosting my application in IIS.
How can I achieve this authentication module in my application? Is there any way I can use the <authentication>
configuration similar to the way I use forms/Windows authentication?
I answered this question: How to secure an ASP.NET Web API 4 years ago using HMAC.
Now, lots of things changed in security, especially that JWT is getting popular. In this answer, I will try to explain how to use JWT in the simplest and basic way that I can, so we won't get lost from jungle of OWIN, Oauth2, ASP.NET Identity... :)
If you don't know about JWT tokens, you need to take a look at:
https://www.rfc-editor.org/rfc/rfc7519
Basically, a JWT token looks like this:
<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>
Example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjE0Nzc1NjY5MjQsImlhdCI6MTQ3NzU2NTcyNH0.6MzD1VwA5AcOcajkFyKhLYybr3h13iZjDyHm9zysDFQ
A JWT token has three sections:
- Header: JSON format which is encoded in Base64
- Claims: JSON format which is encoded in Base64.
- Signature: Created and signed based on Header and Claims which is encoded in Base64.
If you use the website jwt.io with the token above, you can decode the token and see it like below:
Technically, JWT uses a signature which is signed from headers and claims with security algorithm specified in the headers (example: HMACSHA256). Therefore, JWT must be transferred over HTTPs if you store any sensitive information in its claims.
Now, in order to use JWT authentication, you don't really need an OWIN middleware if you have a legacy Web Api system. The simple concept is how to provide JWT token and how to validate the token when the request comes. That's it.
In the demo I've created (github), to keep the JWT token lightweight, I only store username
and expiration time
. But this way, you have to re-build new local identity (principal) to add more information like roles, if you want to do role authorization, etc. But, if you want to add more information into JWT, it's up to you: it's very flexible.
Instead of using OWIN middleware, you can simply provide a JWT token endpoint by using a controller action:
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;
}
}
This is a naive action; in production you should use a POST request or a Basic Authentication endpoint to provide the JWT token.
How to generate the token based on username
?
You can use the NuGet package called System.IdentityModel.Tokens.Jwt
from Microsoft to generate the token, or even another package if you like. In the demo, I use HMACSHA256
with 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;
}
The endpoint to provide the JWT token is done.
How to validate the JWT when the request comes?
In the demo, I have built
JwtAuthenticationAttribute
which inherits from IAuthenticationFilter
(more detail about authentication filter in here).
With this attribute, you can authenticate any action: you just have to put this attribute on that action.
public class ValueController : ApiController
{
[JwtAuthentication]
public string Get()
{
return "value";
}
}
You can also use OWIN middleware or DelegateHander if you want to validate all incoming requests for your WebAPI (not specific to Controller or action)
Below is the core method from authentication filter:
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 || !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);
}
The workflow is to use the JWT library (NuGet package above) to validate the JWT token and then return back ClaimsPrincipal
. You can perform more validation, like check whether user exists on your system, and add other custom validations if you want.
The code to validate JWT token and get principal back:
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;
}
}
If the JWT token is validated and the principal is returned, you should build a new local identity and put more information into it to check role authorization.
Remember to add config.Filters.Add(new AuthorizeAttribute());
(default authorization) at global scope in order to prevent any anonymous request to your resources.
You can use Postman to test the demo:
Request token (naive as I mentioned above, just for demo):
GET http://localhost:{port}/api/token?username=cuong&password=1
Put JWT token in the header for authorized request, example:
GET http://localhost:{port}/api/value
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s
The demo can be found here: https://github.com/cuongle/WebApi.Jwt
这篇关于ASP.NET Web API 的 JWT 身份验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!