【问题标题】:ASP.NET Core JWT and ClaimsASP.NET Core JWT 和声明
【发布时间】:2018-05-08 11:29:02
【问题描述】:

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

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

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

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

【问题讨论】:

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


    【解决方案1】:

    Asp 网络核心

    第一步是编写配置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.csConfigureServices()方法中,我们可以调用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() 方法将令牌附加到标头中。

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

    【讨论】:

      【解决方案2】:

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

      这个链接也有帮助https://joonasw.net/view/adding-custom-claims-aspnet-core-2

      【讨论】:

      • 请在此处写下解决方案,而不是包含将来可能会损坏的链接。谢谢!
      猜你喜欢
      • 2019-04-14
      • 2019-04-15
      • 2017-06-21
      • 2020-10-09
      • 2019-10-01
      • 1970-01-01
      • 2023-03-23
      • 2018-06-16
      • 2019-09-30
      相关资源
      最近更新 更多