ASP.NET Core JWT 和声明 [英] ASP.NET Core JWT and Claims

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

问题描述

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

I have a question regarding JWT authentication in ASP.NET Core and Claims, because I don't know if I get everything correctly.

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

When I create a JWT token in ASP.NET I add some Claims, some of which can be custom. What happens when the request with JWT token is sent from the client to API. How is User.Claims filled ? Does it use the claims that are read from JWT?

我想创建一个自定义身份提供程序(不想使用由 ASP.NET 提供的),并使用我自己的用户数据、角色等表.我不想存储完成所需的所有重要数据JWT 令牌中的策略(令牌中存储的信息量很重要,也很安全).是否可以在 JWT 令牌中仅存储基本声明(如用户 ID、名称等),然后重新获取其他所需的数据 DB/缓存?除此之外,我想使用 [Authorize] 的标准机制和 Policy 机制.

I would like to create a custom Identity provider ( don't want to use this provided by ASP.NET), with my own tables for user data, roles etc. I don't want store all important data required to fulfill the policy in JWT token (the amount of information stored in token matters, as well as security matters). Is it possible to store only basic claims (like user id, name etc) in JWT token, and then re-fetch other required data DB/ Cache? Along with that, I would like to use the standard mechanism for [Authorize] and the Policy mechanism.

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

How to make this all work: Custom User Identity + JWT + Standard ASP.NET policy-based authorization + claims fetched from DB/Cache on every request? How to achieve this?

推荐答案

Asp Net Core

第一步是编写配置Jwt认证的方法:

First step is write the method that configure Jwt authentication:

// 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.csConfigureServices() 方法中,我们可以调用 ConfigureJwtAuthService() 方法来配置 Jwt 认证.

Now inside the ConfigureServices() method of the Startup.cs, we can call ConfigureJwtAuthService() method to configure the Jwt authentication.

这是完整的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 方法:

On my project I use Angular. For call JwtController method by Angular:

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 上,因为它的方法必须可供所有用户访问)您可以设置:

On the controllers classes (or methods) that you want to be accessible only by authenticated users (not on your JwtController because its method must be accessible by all users) you can set:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

要从 Angular 调用需要身份验证的控制器方法,您需要使用 getAuthHeader() 方法将令牌附加到标头中.

To call from Angular the controller method that require authentication, you need to attach the token into the header with the getAuthHeader() method.

希望这篇文章对你有帮助.

I hope this post can help you.

这篇关于ASP.NET Core JWT 和声明的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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