.NET Core 2.0上的JWT


83

我一直在冒险,让JWT在DotNet core 2.0上工作(现在到达今天的最终版本)。有大量的文档,但是所有的示例代码似乎都在使用不推荐使用的API,并重新引入了Core。弄清楚应该如何实现它确实令人头晕。我尝试使用Jose,但使用了app。UseJwtBearerAuthentication已被弃用,并且没有关于下一步操作的文档。

是否有人有使用dotnet core 2.0的开源项目,该项目可以简单地从授权标头解析J​​WT,并允许我授权对HS256编码的JWT令牌的请求?

下面的类没有引发任何异常,但是没有任何请求被授权,并且我也没有指出为什么它们是未经授权的。响应为空401,因此对我来说,没有任何异常,但机密不匹配。

奇怪的是,我的令牌是用HS256算法加密的,但是我看不到任何指示符可以迫使它在任何地方使用该算法。

这是我到目前为止的课程:

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json.Linq;
using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace Site.Authorization
{
    public static class SiteAuthorizationExtensions
    {
        public static IServiceCollection AddSiteAuthorization(this IServiceCollection services)
        {
            var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("SECRET_KEY"));

            var tokenValidationParameters = new TokenValidationParameters
            {
                // The signing key must match!
                ValidateIssuerSigningKey = true,
                ValidateAudience = false,
                ValidateIssuer = false,
                IssuerSigningKeys = new List<SecurityKey>{ signingKey },


                // Validate the token expiry
                ValidateLifetime = true,
            };

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;


            })

            .AddJwtBearer(o =>
            {
                o.IncludeErrorDetails = true;
                o.TokenValidationParameters  = tokenValidationParameters;
                o.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = c =>
                    {
                        c.NoResult();

                        c.Response.StatusCode = 401;
                        c.Response.ContentType = "text/plain";

                        return c.Response.WriteAsync(c.Exception.ToString());
                    }

                };
            });

            return services;
        }
    }
}

我关闭了签名验证,没有任何更改,所有请求都使用[Authorize] 401
Michael Draper

2
您可以发布完整的代码吗?或演示项目,我很想看看您是如何做到的……
Piotr Stulinski


还有一个可以帮助您的帖子。stackoverflow.com/a/48295906/8417618
Marco Barbero,

Answers:


87

这是带有控制器的完整的最小工作样本。希望您可以使用Postman或JavaScript调用进行检查。

  1. appsettings.json,appsettings.Development.json。添加一个部分。注意,密钥应相当长,并且Issuer是服务的地址:

    ...
    ,"Tokens": {
        "Key": "Rather_very_long_key",
        "Issuer": "http://localhost:56268/"
    }
    ...
    

    !!! 在实际项目中,请勿将Key保留在appsettings.json文件中。应该将其保存在Environment变量中,并采取以下方式:

    Environment.GetEnvironmentVariable("JWT_KEY");
    

更新:了解.net核心设置的工作原理,您无需完全从Environment中获取它。您可以使用设置。但是,相反,我们可以将此变量写到生产环境变量中,然后我们的代码将更喜欢环境变量而不是配置。

  1. AuthRequest.cs:Dto保留用于传递登录名和密码的值:

    public class AuthRequest
    {
        public string UserName { get; set; }
        public string Password { get; set; }
    }
    
  2. 在app.UseMvc()之前的Configure()方法中的Startup.cs:

    app.UseAuthentication();
    
  3. ConfigureServices()中的Startup.cs:

    services.AddAuthentication()
        .AddJwtBearer(cfg =>
        {
            cfg.RequireHttpsMetadata = false;
            cfg.SaveToken = true;
    
            cfg.TokenValidationParameters = new TokenValidationParameters()
            {
                ValidIssuer = Configuration["Tokens:Issuer"],
                ValidAudience = Configuration["Tokens:Issuer"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
            };
    
        });
    
  4. 添加控制器:

        [Route("api/[controller]")]
        public class TokenController : Controller
        {
            private readonly IConfiguration _config;
            private readonly IUserManager _userManager;
    
            public TokenController(IConfiguration configuration, IUserManager userManager)
            {
                _config = configuration;
                _userManager = userManager;
            }
    
            [HttpPost("")]
            [AllowAnonymous]
            public IActionResult Login([FromBody] AuthRequest authUserRequest)
            {
                var user = _userManager.FindByEmail(model.UserName);
    
                if (user != null)
                {
                    var checkPwd = _signInManager.CheckPasswordSignIn(user, model.authUserRequest);
                    if (checkPwd)
                    {
                        var claims = new[]
                        {
                            new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
                            new Claim(JwtRegisteredClaimNames.Jti, user.Id.ToString()),
                        };
    
                        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
                        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    
                        var token = new JwtSecurityToken(_config["Tokens:Issuer"],
                        _config["Tokens:Issuer"],
                        claims,
                        expires: DateTime.Now.AddMinutes(30),
                        signingCredentials: creds);
    
                        return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
                    }
                }
    
                return BadRequest("Could not create token");
            }}
    

那就是所有人!干杯!

更新:人们问如何获得当前用户。去做:

  1. 在ConfigureServices()的Startup.cs中添加

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    
  2. 在控制器中添加到构造函数中:

    private readonly int _currentUser;
    public MyController(IHttpContextAccessor httpContextAccessor)
    {
       _currentUser = httpContextAccessor.CurrentUser();
    }
    
  3. 在某处添加扩展名并在您的Controller中使用它(使用....)

    public static class IHttpContextAccessorExtension
    {
        public static int CurrentUser(this IHttpContextAccessor httpContextAccessor)
        {
            var stringId = httpContextAccessor?.HttpContext?.User?.FindFirst(JwtRegisteredClaimNames.Jti)?.Value;
            int.TryParse(stringId ?? "0", out int userId);
    
            return userId;
        }
    }
    

1
这对我很有帮助。我唯一仍不清楚的是如何检查令牌以进行后续调用,或者如何确定当前登录的用户是谁。
Travesty3,2017年

真的很简单。在控制器的方法调用中:var currentUser = HttpContext.User.Identity.Name; 干杯!
alerya

1
杜德(Dude)这是一个巨大的帮助,非常感谢您的详细回答!
Ryanman '17

1
感谢您提供以下信息。它从401未经授权错误中救了我。3.在app.UseMvc()之前的Configure()方法中的Startup.cs:app.UseAuthentication();
Sumia

1
这是一个矛盾,我们不应该将密钥存储在appsettings文件中,而是在此处通过Configuration [“ Tokens:Key”])进行检索。如果我们有其他服务可以安全地检索密钥,我们如何将其合并到ConfigureServices中?
战争肉汁

18

我的tokenValidationParameters作品如下所示:

 var tokenValidationParameters = new TokenValidationParameters
  {
      ValidateIssuerSigningKey = true,
      IssuerSigningKey = GetSignInKey(),
      ValidateIssuer = true,
      ValidIssuer = GetIssuer(),
      ValidateAudience = true,
      ValidAudience = GetAudience(),
      ValidateLifetime = true,
      ClockSkew = TimeSpan.Zero
   };

    static private SymmetricSecurityKey GetSignInKey()
    {
        const string secretKey = "very_long_very_secret_secret";
        var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));

        return signingKey;
    }

    static private string GetIssuer()
    {
        return "issuer";
    }

    static private string GetAudience()
    {
        return "audience";
    }

此外,添加options.RequireHttpsMetadata = false像这样:

         .AddJwtBearer(options =>
       {         
           options.TokenValidationParameters =tokenValidationParameters         
           options.RequireHttpsMetadata = false;
       });

编辑

不要忘记打电话

 app.UseAuthentication();

在Startup.cs中->app.UseMvc()之前配置方法;


我敢打赌这是应用程序。UseAuthentication(); 打电话就能解决问题,我不知道我需要那个。谢谢!
Michael Draper

我认为您也需要指定ValidAudience,ValidIssuer和IssuerSigningKey。没有我
它就行不通

是的,这就是事实。我需要添加app.UseAuthentication(),仅此而已。非常感谢你!
Michael Draper

3
如果您不这样做app.UseAuthentication();app.UseMvc();则在您之前调用+1便会获得401,即使成功授权了令牌也是如此-我花了大约2天的时间来解决这个问题!
pcdev

1
“ app.UseAuthentication();”,在将.net core从1.0升级到2.0之后,我花了一整天的时间来解决401问题,但是直到看到这篇文章之后我才找到解决方案。谢谢阿德里安。
Chan的

8

带有Web Api演示的Asp.net Core 2.0 JWT承载令牌身份验证实现

添加程序包“ Microsoft.AspNetCore.Authentication.JwtBearer

Startup.cs ConfigureServices()

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(cfg =>
            {
                cfg.RequireHttpsMetadata = false;
                cfg.SaveToken = true;

                cfg.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidIssuer = "me",
                    ValidAudience = "you",
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")) //Secret
                };

            });

Startup.cs Configure()

// ===== Use Authentication ======
        app.UseAuthentication();

User.cs //例如,这是一个模型类。可以是任何东西。

public class User
{
    public Int32 Id { get; set; }
    public string Username { get; set; }
    public string Country { get; set; }
    public string Password { get; set; }
}

UserContext.cs //这只是上下文类。可以是任何东西。

public class UserContext : DbContext
{
    public UserContext(DbContextOptions<UserContext> options) : base(options)
    {
        this.Database.EnsureCreated();
    }

    public DbSet<User> Users { get; set; }
}

AccountController.cs

[Route("[controller]")]
public class AccountController : Controller
{

    private readonly UserContext _context;

    public AccountController(UserContext context)
    {
        _context = context;
    }

    [AllowAnonymous]
    [Route("api/token")]
    [HttpPost]
    public async Task<IActionResult> Token([FromBody]User user)
    {
        if (!ModelState.IsValid) return BadRequest("Token failed to generate");
        var userIdentified = _context.Users.FirstOrDefault(u => u.Username == user.Username);
            if (userIdentified == null)
            {
                return Unauthorized();
            }
            user = userIdentified;

        //Add Claims
        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.UniqueName, "data"),
            new Claim(JwtRegisteredClaimNames.Sub, "data"),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        };

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")); //Secret
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken("me",
            "you",
            claims,
            expires: DateTime.Now.AddMinutes(30),
            signingCredentials: creds);

        return Ok(new
        {
            access_token = new JwtSecurityTokenHandler().WriteToken(token),
            expires_in = DateTime.Now.AddMinutes(30),
            token_type = "bearer"
        });
    }
}

UserController.cs

[Authorize]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    private readonly UserContext _context;

    public UserController(UserContext context)
    {
        _context = context;
        if(_context.Users.Count() == 0 )
        {
            _context.Users.Add(new User { Id = 0, Username = "Abdul Hameed Abdul Sattar", Country = "Indian", Password = "123456" });
            _context.SaveChanges();
        }
    }

    [HttpGet("[action]")]
    public IEnumerable<User> GetList()
    {
        return _context.Users.ToList();
    }

    [HttpGet("[action]/{id}", Name = "GetUser")]
    public IActionResult GetById(long id)
    {
        var user = _context.Users.FirstOrDefault(u => u.Id == id);
        if(user == null)
        {
            return NotFound();
        }
        return new ObjectResult(user);
    }


    [HttpPost("[action]")]
    public IActionResult Create([FromBody] User user)
    {
        if(user == null)
        {
            return BadRequest();
        }

        _context.Users.Add(user);
        _context.SaveChanges();

        return CreatedAtRoute("GetUser", new { id = user.Id }, user);

    }

    [HttpPut("[action]/{id}")]
    public IActionResult Update(long id, [FromBody] User user)
    {
        if (user == null)
        {
            return BadRequest();
        }

        var userIdentified = _context.Users.FirstOrDefault(u => u.Id == id);
        if (userIdentified == null)
        {
            return NotFound();
        }

        userIdentified.Country = user.Country;
        userIdentified.Username = user.Username;

        _context.Users.Update(userIdentified);
        _context.SaveChanges();
        return new NoContentResult();
    }


    [HttpDelete("[action]/{id}")]
    public IActionResult Delete(long id)
    {
        var user = _context.Users.FirstOrDefault(u => u.Id == id);
        if (user == null)
        {
            return NotFound();
        }

        _context.Users.Remove(user);
        _context.SaveChanges();

        return new NoContentResult();
    }
}

在PostMan上测试: 您将收到令牌作为回应。

在其他Web服务的Header中传递TokenType和AccessToken。 在此处输入图片说明

祝你好运!我只是初学者。我只花了一个星期就开始学习asp.net核心。


我收到InvalidOperationException:尝试激活“ AccountController”时无法解析类型为“ WebApplication8.UserContext”的服务。当我尝试将邮递员呼叫“邮寄到帐户/ api /令牌”时
Kirsten Greed

7

这是为您提供的解决方案。

首先,在您的startup.cs中,将其配置为服务:

  services.AddAuthentication().AddJwtBearer(cfg =>
        {
            cfg.RequireHttpsMetadata = false;
            cfg.SaveToken = true;
            cfg.TokenValidationParameters = new TokenValidationParameters()
            {
                IssuerSigningKey = "somethong",
                ValidAudience = "something",
                :
            };
        });

其次,在配置中调用此服务

          app.UseAuthentication();

现在您可以通过添加属性在控制器中使用它

          [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
          [HttpGet]
          public IActionResult GetUserInfo()
          {

有关使用angular作为前端的完整详细信息源代码,请参见此处


这就是拯救我的培根的答案!能够使用[Authorize](授权)会很好。想象一下,可以使用Startup.cs来处理
Simon,

1
Simon,因为在同一个asp.net核心mvc应用程序中可以有多个方案,例如services.AddAuthentication()。AddCookie()。AddJwtBearer();
长场

1
您还可以通过services.AddAuthorization启动时的功能设置默认的身份验证方案。
内维尔·纳泽拉内

为了跟进@NevilleNazerane的声明,设置默认身份验证方案的代码(将与普通的[Authorize] Decorator一起使用)是此问题的答案。这是服务。AddAuthentication(sharedOptions => {sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;})
Ryanman

如果我试图以此为榜样为IssuerSigningKey我得到一个错误,无法转换sourceType的“串”为目标类型“Microsoft.IdentityModel.Tokens.SecurityKey”
基尔斯滕贪婪

4

这是我对.Net Core 2.0 API的实现:

    public IConfigurationRoot Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services
        services.AddMvc(
        config =>
        {
            // This enables the AuthorizeFilter on all endpoints
            var policy = new AuthorizationPolicyBuilder()
                                .RequireAuthenticatedUser()
                                .Build();
            config.Filters.Add(new AuthorizeFilter(policy));
            
        }
        ).AddJsonOptions(opt =>
        {
            opt.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
        });

        services.AddLogging();

        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(options =>
        {
            options.Audience = Configuration["AzureAD:Audience"];  
            options.Authority = Configuration["AzureAD:AADInstance"] + Configuration["AzureAD:TenantId"];
        });            
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.UseAuthentication(); // THIS METHOD MUST COME BEFORE UseMvc...() !!
        app.UseMvcWithDefaultRoute();            
    }

appsettings.json:

{
  "AzureAD": {
    "AADInstance": "https://login.microsoftonline.com/",
    "Audience": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "ClientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "Domain": "mydomain.com",
    "TenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  },
  ...
}

上面的代码在所有控制器上启用身份验证。要允许匿名访问,您可以装饰整个控制器:

[Route("api/[controller]")]
[AllowAnonymous]
public class AnonymousController : Controller
{
    ...
}

或者只是装饰一个方法以允许单个端点:

    [AllowAnonymous]
    [HttpPost("anonymousmethod")]
    public async Task<IActionResult> MyAnonymousMethod()
    {
        ...
    }

笔记:

  • 这是我第一次尝试进行AD身份验证-如果有任何问题,请告诉我!

  • Audience必须与客户端请求的资源ID相匹配。在我们的案例中,我们的客户端(一个Angular Web应用程序)是在Azure AD中单独注册的,并且使用了其客户端ID,我们已在API中将其注册为受众群体

  • ClientId在Azure门户中被称为“应用程序ID ”(为什么),即API的应用程序注册的应用程序ID。

  • TenantIdAzure活动目录>属性下的Azure门户中被称为目录ID(为什么?)

  • 如果将API部署为Azure托管的Web App,请确保设置“应用程序设置”:

    例如。AzureAD:受众群体/ xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx


3

只是为了更新@alerya的出色答案,我不得不修改helper类使其看起来像这样;

public static class IHttpContextAccessorExtension
    {
        public static string CurrentUser(this IHttpContextAccessor httpContextAccessor)
        {           
            var userId = httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; 
            return userId;
        }
    }

然后,我可以在服务层中获取userId。我知道在控制器中很容易,但是挑战仍然很深。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.