.NET Core 2.0 上的智威汤逊

JWT on .NET Core 2.0

为了让 JWT 在 DotNet 核心 2.0 上工作(现在已经到了今天的最终版本),我一直在冒险。有 ton 的文档,但所有示例代码似乎都使用了已弃用的 API,并且对 Core 是全新的,弄清楚它应该如何实现确实令人眼花缭乱。我尝试使用何塞,但应用程序。 UseJwtBearerAuthentication 已被弃用,并且没有关于下一步做什么的文档。

有没有人有使用 dotnet core 2.0 的开源项目,它可以简单地从授权中解析 JWT header 并允许我授权对 HS256 编码的 JWT 令牌的请求?

下面的 class 没有抛出任何异常,但是没有请求被授权,我也没有得到任何指示 为什么 它们是未经授权的。响应是空的 401,所以对我来说这表明没有异常,但秘密不匹配。

一件奇怪的事情是我的令牌是用 HS256 算法加密的,但我看不到任何指示器告诉它强制它在任何地方使用该算法。

这是我目前的 class:

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

我的 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();

in Startup.cs -> 配置方法 before app.UseMvc();

这里有一个解决方案。

在您的 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 作为前端的源代码的完整详细信息,请参阅 here

这是我对 .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 网络应用程序)在 Azure AD 中单独注册,并使用其客户端 ID,我们在 API

    中将其注册为受众
  • ClientId在Azure Portal中被称为Application ID(为什么??),app注册的Application ID为API.

  • TenantId 在 Azure 门户中被称为 Directory ID(为什么??),位于 Azure Active Directory >属性

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

    例如。 AzureAD:Audience / xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

这是一个带有控制器的完整工作最小示例。我希望你可以使用 Postman 或 JavaScript 调用来检查它。

  1. appsettings.json,appsettings.Development.json。添加一个部分。注意,Key 应该比较长,Issuer 是服务的地址:

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

    !!!在实际项目中,不要将 Key 保存在 appsettings.json 文件中。它应该保存在环境变量中并像这样获取:

    Environment.GetEnvironmentVariable("JWT_KEY");
    

UPDATE:了解 .net 核心设置的工作原理,您不需要完全从环境中获取它。您可以使用设置。但是,我们可能会在生产中将此变量写入环境变量,那么我们的代码将更喜欢环境变量而不是配置。

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

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

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

    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. 在Startup.cs中ConfigureServices()添加

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

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

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

Asp.net Core 2.0 JWT Bearer Token Authentication with Web Api Demo

添加包“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 配置()

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

User.cs // 这是一个模型 class 只是为了举例。它可以是任何东西。

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 // 这只是上下文 class。它可以是任何东西。

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 上测试:

在其他网络服务的Header 中传递TokenType 和AccessToken。

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

只是为了更新@alerya 的出色回答,我不得不修改助手 class 使其看起来像这样;

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

然后我可以在我的服务层获取userId。我知道这在控制器中很容易,但进一步向下是一个挑战。