ASP.NET核心JWT和声明

时间:2017-11-24 12:34:16

标签: jwt asp.net-core-2.0 asp.net-core-webapi claims

我对ASP.NET Core和Claims中的JWT身份验证有疑问,因为我不知道我是否正确地获取了所有内容。

当我在ASP.NET中创建JWT令牌时,我添加了一些声明,其中一些可以是自定义的。将带有JWT令牌的请求从客户端发送到API时会发生什么。 User.Claims如何填写?它是否使用从JWT读取的声明?

我想创建一个自定义身份提供程序(不想使用ASP.NET提供的这个),我自己的表用于用户数据,角色等。我不想存储完成所需的所有重要数据JWT令牌中的策略(存储在令牌事务中的信息量以及安全事项)。是否可以在JWT令牌中仅存储基本声明(如用户ID,名称等),然后重新获取其他所需的数据DB / Cache?与此同时,我想使用[授权]和政策机制的标准机制。

如何使这一切全部工作:自定义用户身份+ JWT +标准基于ASP.NET策略的授权+声明在每次请求时从DB / Cache获取?怎么做到这一点?

2 个答案:

答案 0 :(得分:3)

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

现在在 Startup.cs ConfigureServices()方法中,我们可以调用 ConfigureJwtAuthService()方法来配置Jwt身份验证。

这是完整的 Startup.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。对于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()方法将令牌附加到标头中。

我希望这篇文章可以帮助你。

答案 1 :(得分:0)

是的,它使用存储在jwt令牌中的claism 查看httpcontext对象,了解创建令牌时存储在令牌中的声明

此链接也可以有用https://joonasw.net/view/adding-custom-claims-aspnet-core-2