ASP.NET Core JWT and Claims



我在ASP.NET核心和主张中有一个有关JWT身份验证的问题,因为我不知道我是否正确获得了所有内容。

当我在ASP.NET中创建JWT令牌时,我会添加一些主张,其中一些可以定制。当使用JWT令牌的请求从客户端发送到API时会发生什么。用户如何填写?它使用JWT阅读的主张吗?

我想创建一个自定义身份提供商(不想使用ASP.NET提供的此功能),并使用我自己的表作为用户数据,角色等。我不想存储满足满足所需的所有重要数据JWT令牌中的策略(存储在令牌事项中的信息数量以及安全事项)。是否可以在JWT令牌中仅存储基本索赔(例如用户ID,名称等),然后重新获取其他必需的数据DB/CACE?除此之外,我想使用标准机制来[授权]和政策机制。

如何使这一切都起作用:自定义用户身份 JWT 标准ASP.NET基于策略的授权 根据每个请求从db/cache提取的索赔?如何实现这一目标?

asp net core

第一步是编写配置JWT身份验证的方法:

// Configure authentication with JWT (Json Web Token).
public void ConfigureJwtAuthService(IServiceCollection services)
{
  // Enable the use of an [Authorize(AuthenticationSchemes = 
  // JwtBearerDefaults.AuthenticationScheme)]
  // attribute on methods and classes to protect.
  services.AddAuthentication().AddJwtBearer(cfg =>
  {
    cfg.RequireHttpsMetadata = false;
    cfg.SaveToken = true;
    cfg.TokenValidationParameters = new TokenValidationParameters()
    {
      IssuerSigningKey = JwtController.SecurityKey,
      ValidAudience = JwtController.Audience,
      ValidIssuer = JwtController.Issuer,
      // When receiving a token, check that we've signed it.
      ValidateIssuerSigningKey = true,
      // When receiving a token, check that it is still valid.
      ValidateLifetime = true,
      // This defines the maximum allowable clock skew when validating 
      // the lifetime. As we're creating the tokens locally and validating
      // them on the same machines which should have synchronised time,
      // this can be set to zero.
      ClockSkew = TimeSpan.FromMinutes(0)
    };
  });
}

现在在 configureservices() startup.cs 的方法中,我们可以调用 configurejwtauthservice()方法来配置JWT autheTication。

这是完整的 starmup.cs

using System;
using Autofac;
using ExpertCodeBlogWebApp.Controllers;
using ExpertCodeBlogWebApp.Domain;
using ExpertCodeBlogWebApp.Domain.Interfaces;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SpaServices.Webpack;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace ExpertCodeBlogWebApp
{
  public class Startup
  {
    public Startup(IConfiguration configuration)
    {
      Configuration = configuration;
    }
  public IConfiguration Configuration { get; }
  // This method gets called by the runtime. Use this method to add 
  // services to the container.
  public IServiceProvider ConfigureServices(IServiceCollection services)
  {
    services.AddMvc();
    // Configure jwt autenticazione 
    ConfigureJwtAuthService(services);
    // Repositories
    services.AddScoped<IUserRepository, UserRepository>();
    // Create the Autofac container builder for dependency injection
    var builder = new ContainerBuilder();
    // Add any Autofac modules or registrations. 
    builder.RegisterModule(new AutofacModule());
    // Return ServiceProvider
    var serviceProvider = services.BuildServiceProvider();
    return serviceProvider;
  }
  // Configure authentication with JWT (Json Web Token).
  public void ConfigureJwtAuthService(IServiceCollection services)
  {
    // Enable the use of an [Authorize(AuthenticationSchemes = 
    // JwtBearerDefaults.AuthenticationScheme)]
    // attribute on methods and classes to protect.
    services.AddAuthentication().AddJwtBearer(cfg =>
    {
      cfg.RequireHttpsMetadata = false;
      cfg.SaveToken = true;
      cfg.TokenValidationParameters = new TokenValidationParameters()
      {
        IssuerSigningKey = JwtController.SecurityKey,
        ValidAudience = JwtController.Audience,
        ValidIssuer = JwtController.Issuer,
        // When receiving a token, check that we've signed it.
        ValidateIssuerSigningKey = true,
        // When receiving a token, check that it is still valid.
        ValidateLifetime = true,
        // This defines the maximum allowable clock skew when validating 
        // the lifetime.
        // As we're creating the tokens locally and validating them on the 
        // same machines which should have synchronised time, this can be 
        // set to zero.
        ClockSkew = TimeSpan.FromMinutes(0)
      };
    });
  }
  // This method gets called by the runtime. Use this method to configure 
  // the HTTP request pipeline.
  public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  {
    if (env.IsDevelopment())
    {
      app.UseDeveloperExceptionPage();
      app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
      {
        HotModuleReplacement = true
      });
    }
    else
    {
      app.UseExceptionHandler("/Home/Error");
    }
    app.UseStaticFiles();
    app.UseMvc(routes =>
    {
      routes.MapRoute(
      name: "default",
      template: "{controller=Home}/{action=Index}/{id?}");
      routes.MapSpaFallbackRoute(
        name: "spa-fallback",
        defaults: new { controller = "Home", action = "Index" });
      });
    }
  }
  // For dependency injection.
  public class AutofacModule : Module
  {
    // Dependency Injection with Autofact
    protected override void Load(ContainerBuilder builder)
    {
      builder.RegisterType<UserRepository>().As<IUserRepository>()
        .SingleInstance();
    }
  }
}

jwtcontroller.cs

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using ExpertCodeBlogWebApp.Domain;
using ExpertCodeBlogWebApp.Domain.Interfaces;
using ExpertCodeBlogWebApp.Domain.Models;
using ExpertCodeBlogWebApp.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
namespace ExpertCodeBlogWebApp.Controllers
{
[Route("api/[controller]")]
public class JwtController : Controller
{
  #region Private Members
  // JWT-related members
  private TimeSpan TokenExpiration;
  private SigningCredentials SigningCredentials;
  // EF and Identity members, available through DI
  private MyDbContext DbContext;
  private IUserRepository _userRepository;
  private readonly ILogger _logger;
  #endregion Private Members
  #region Static Members
  private static readonly string PrivateKey = "my_PrivateKey";
  public static readonly SymmetricSecurityKey SecurityKey = 
    new SymmetricSecurityKey(Encoding.ASCII.GetBytes(PrivateKey));
  public static readonly string Issuer = "my_Issuer";
  public static readonly string Audience = "my_Audience";
  #endregion Static Members
  #region Constructor
  // I have used Autofac in the Startup.cs for dependency injection)
  public JwtController(
    MyDbContext dbContext,
    IUserRepository userRepository,
    ILogger<JwtController> logger)
  {
    _logger = logger;
    _userRepository = userRepository;
    // Instantiate JWT-related members
    TokenExpiration = TimeSpan.FromMinutes(10);
    SigningCredentials = new SigningCredentials(SecurityKey, 
      SecurityAlgorithms.HmacSha256);
    // Instantiate through Dependency Injection with Autofact
    DbContext = dbContext;
  }
  #endregion Constructor
  #region Public Methods 
  // Manages the request for a new authentication or the refresh of an 
  // already established one
  [HttpPost("token")]
  public async Task<IActionResult> 
    Authentication([FromBody]JwtRequestViewModel jwt)
  {
    if (ModelState.IsValid)
    {
      string grantType = jwt.GrantType; 
      if (grantType == "password")
      {
        string userName = jwt.UserName;
        string password = jwt.Password;
        // Password check required
        var user = await 
          _userRepository.GetUserInfoWithCheckPwd(userName, password);
        // Check if user is expired (check the ExpireDate property)
        if (UserExpired(user))
          return BadRequest($"Account of {user.Name} expired!");
        if (UserEnabled(user))
          return await GenerateToken(user);
        else
          return BadRequest("User name or password invalid.");
      }
    }
    else if (grantType == "refresh_token")
    {
      string userName = jwt.UserName;
      // Refresh token (no password check required)
      var user = await _userRepository.GetUserInfoByName(userName);
      // Check if user is expired (check the ExpireDate property)
      if (UserExpired(user))
        return BadRequest($"Account of {user.Name} expired!");
      string token = jwt.Token;
      if (token == user.Token)
      {
        // Generate token and send it via a json-formatted string
        return await GenerateToken(user);
      }
      else
      {
        return BadRequest("User token invalid.");
      }
    }
    else
      return BadRequest("Authentication type invalid.");
  }
  else
    return BadRequest("Request invalid.");
  }
  #endregion Public Methods
  #region Private Methods
  private bool UserExpired(Users utente)
  {
    if (utente != null)
      return utente.ExpireDate.CompareTo(DateTime.Now) < 0;
    return true;
  }
  private bool UserEnabled(Users utente)
  {
    if (utente != null)
      return utente.Enabled == true;
    return false;
  }
  private JsonSerializerSettings DefaultJsonSettings
  {
    get
    {
      return new JsonSerializerSettings()
      {
        Formatting = Formatting.Indented
      };
    }
  }
  private async Task<IActionResult> GenerateToken(Users user)
  {
    try
    {
      if (user != null)
      {
        var handler = new JwtSecurityTokenHandler();
        DateTime newTokenExpiration = DateTime.Now.Add(TokenExpiration);
        ClaimsIdentity identity = new ClaimsIdentity(
          new GenericIdentity(user.Name, "TokenAuth"),
          new[] { new Claim("ID", user.Id.ToString())}
        );
        var securityToken = handler.CreateToken(new SecurityTokenDescriptor
        {
          Issuer = JwtController.Issuer,
          Audience = JwtController.Audience,
          SigningCredentials = SigningCredentials,
          Subject = identity,
          Expires = newTokenExpiration
        });
        string encodedToken = handler.WriteToken(securityToken);
        // Update token data on database
        await _userRepository.UpdateTokenData(user.Name, encodedToken, 
          newTokenExpiration);
        // Build the json response 
        // (I use Automapper to maps an object into another object)
        var jwtResponse = Mapper.Map<JwtResponseViewModel>(user);
        jwtResponse.AccessToken = encodedToken;
        jwtResponse.Expiration = (int)TokenExpiration.TotalSeconds;
        return Ok(jwtResponse);
      }
      return NotFound();
      }
      catch(Exception e)
      {
        return BadRequest(e.Message);
      }
    }
    #endregion
  }
}

在我的项目上,我使用了角。为了通过Angular致电JWTController方法:

login(userName: string, password: string)
{
  return this.getLoginEndpoint(userName, password)
    .map((response: Response) => this.processLoginResponse(response));
}
getLoginEndpoint(userName: string, password: string): Observable<Response> 
{
  // Body
  // JwtRequest is a model class that I use to send info to the controller
  let jwt = new JwtRequest(); 
  jwt.GrantType = "password";
  jwt.UserName = userName;
  jwt.Password = password;
  jwt.ClientId = "my_Issuer";
  // Post requiest (I use getAuthHeader that attach to the header the
  // authentication token, but it can also be omitted because it is ignored
  // by the JwtController
  return this.http.post(this.loginUrl, JSON.stringify(jwt), 
    this.getAuthHeader(true))
}
protected getAuthHeader(includeJsonContentType?: boolean): RequestOptions
{
  // Hera I use this.authService.accessToken  that is a my service where
  // I have store the token received from the server
  let headers = new Headers({
    'Authorization': 'Bearer ' + this.authService.accessToken });
  if (includeJsonContentType)
    headers.append("Content-Type", "application/json");
  headers.append("Accept", `application/vnd.iman.v01+json, 
    application/json, text/plain, */*`);
  headers.append("App-Version", "01");
  return new RequestOptions({ headers: headers });
}
private processLoginResponse(response: Response)
{
  // process the response..
}

在控制器类(或方法)上,您只想通过身份验证的用户才能访问(不在JWTController上,因为所有用户必须访问其方法),您可以设置:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

要从Angular调用需要身份验证的控制器方法,您需要使用 getAuthheader()方法将令牌连接到标题中。

我希望这篇文章能为您提供帮助。

是的,它使用了存储在JWT令牌中的索赔查看创建令牌

时存储在令牌中的httpcontext对象

此链接也可以有用。

最新更新