ASP.NET Web API的JWT身份验证 [英] JWT authentication for ASP.NET Web API

查看:1030
本文介绍了ASP.NET Web API的JWT身份验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在我的Web API应用程序中支持JWT承载令牌(JSON Web令牌),但我迷路了.

I'm trying to support JWT bearer token (JSON Web Token) in my web API application and I'm getting lost.

我看到了对.NET Core和OWIN应用程序的支持.
我目前正在将应用程序托管在IIS中.

I see support for .NET Core and for OWIN applications.
I'm currently hosting my application in IIS.

如何在我的应用程序中实现此身份验证模块?有什么方法可以使用<authentication>配置,类似于使用表单/Windows身份验证的方法?

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?

推荐答案

我回答了这个问题:现在,安全方面发生了很多变化,尤其是JWT变得越来越流行.在这里,我将尝试解释如何以最简单,最基本的方式使用JWT,这样我们就不会迷失于OWIN,Oauth2,ASP.NET Identity ... :)的丛林中.

Now, lots of things changed in security, especially JWT is getting popular. In here, 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... :).

如果您不知道JWT令牌,则需要看一下:

If you don't know JWT token, you need to take a look a little bit at:

https://tools.ietf.org/html/rfc7519

基本上,JWT令牌看起来像:

Basically, a JWT token looks like:

<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>

示例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjE0Nzc1NjY5MjQsImlhdCI6MTQ3NzU2NTcyNH0.6MzD1VwA5AcOcajkFyKhLYybr3h13iZjDyHm9zysDFQ

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjE0Nzc1NjY5MjQsImlhdCI6MTQ3NzU2NTcyNH0.6MzD1VwA5AcOcajkFyKhLYybr3h13iZjDyHm9zysDFQ

JWT令牌包含三个部分:

A JWT token has three sections:

  1. 标题:以Base64编码的JSON格式
  2. 声明:以Base64编码的JSON格式.
  3. 签名:基于在Base64中编码的标头和声明创建和签名.

如果您将网站 jwt.io 与上面的令牌一起使用,则可以对令牌进行解码,如下所示:

If you use the website jwt.io with the token above, you can decode the token and see it like below:

从技术上讲,JWT使用从标头签名的声明,并使用标头中指定的安全算法声明所有权(例如:HMACSHA256).因此,如果您在声明中存储任何敏感信息,则必须通过HTTP传输JWT.

Technically, JWT uses signature which is signed from headers and claims with security algorithm specified in the headers (example: HMACSHA256). Therefore, JWT is required to be transferred over HTTPs if you store any sensitive information in claims.

现在,要使用JWT身份验证,如果您有旧版Web Api系统,则实际上不需要OWIN中间件.简单的概念是如何提供JWT令牌以及如何在请求到来时验证令牌.就是这样.

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.

回到演示,为了保持JWT令牌的轻量,我只在JWT中存储了usernameexpiration time.但是通过这种方式,如果您想进行角色授权,则必须重新构建新的本地身份(主要身份)以添加更多信息,例如:role...但是,如果您想在JWT中添加更多信息,则取决于您:这非常灵活.

Back to the demo, to keep JWT token lightweight, I only store username and expiration time in JWT. 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. But, if you want to add more information into JWT, it's up to you: it's very flexible.

代替使用OWIN中间件,您只需使用控制器的操作即可提供JWT令牌终结点:

Instead of using OWIN middleware, you can simply provide a JWT token endpoint by using action from the controller:

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;
    }
}

这是一个幼稚的行为;在生产中,您应该使用POST请求或基本身份验证终结点来提供JWT令牌.

This is a naive action; in production you should use a POST request or a Basic Authentication endpoint to provide the JWT token.

如何基于username生成令牌?

您可以使用Microsoft的称为System.IdentityModel.Tokens.Jwt的NuGet程序包生成令牌,或者根据需要使用其他程序包.在演示中,我将HMACSHA256SymmetricKey一起使用:

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;
}

提供JWT令牌的端点已完成.现在,如何在请求到来时验证JWT?在演示中,我建立了 从IAuthenticationFilter继承的JwtAuthenticationAttribute(有关此处的验证过滤器的更多详细信息).

The endpoint to provide the JWT token is done. Now, 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";
    }
}

如果要验证WebAPI的所有传入请求(不特定于Controller或操作),也可以使用OWIN中间件或DelegateHander

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)
        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);
}

工作流使用JWT库(上面的NuGet包)来验证JWT令牌,然后返回ClaimsPrincipal.您可以执行更多验证,例如检查系统上是否存在用户,并根据需要添加其他自定义验证.验证JWT令牌并获取本金的代码:

The workflow is, using JWT library (NuGet package above) to validate 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;
    }
}

如果JWT令牌已经过验证并且主体返回了,则您应该构建一个新的本地身份,并在其中添加更多信息以检查角色授权.

If the JWT token is validated and the principal is return, you should build a new local identity and put more information into it to check role authorization.

请记住在全局范围内添加config.Filters.Add(new AuthorizeAttribute());(默认授权),以防止对您的资源进行任何匿名请求.

Remember to add config.Filters.Add(new AuthorizeAttribute()); (default authorization) at global scope in order to prevent any anonymous request to your resources.

您可以使用Postman进行演示:

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

在授权请求的标头中放置JWT令牌,例如:

Put JWT token in the header for authorized request, example:

GET http://localhost:{port}/api/value

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s

该演示位于此处: https://github.com/cuongle/WebApi.Jwt

这篇关于ASP.NET Web API的JWT身份验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆