【问题标题】:ASP.NET Core JWT Bearer Token Custom ValidationASP.NET Core JWT 不记名令牌自定义验证
【发布时间】:2017-10-26 01:06:25
【问题描述】:

经过大量阅读,我找到了一种实现自定义 JWT 不记名令牌验证器的方法,如下所示。

Starup.cs:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
         ILoggerFactory loggerFactory, IApplicationLifetime appLifetime)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();
        
    app.UseStaticFiles();
        
    app.UseIdentity();

    ConfigureAuth(app);
        
    app.UseMvcWithDefaultRoute();            
}

private void ConfigureAuth(IApplicationBuilder app)
{

    var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetSection("TokenAuthentication:SecretKey").Value));


    var tokenValidationParameters = new TokenValidationParameters
    {
        // The signing key must match!
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = signingKey,
        // Validate the JWT Issuer (iss) claim
        ValidateIssuer = true,
        ValidIssuer = Configuration.GetSection("TokenAuthentication:Issuer").Value,
        // Validate the JWT Audience (aud) claim
        ValidateAudience = true,
        ValidAudience = Configuration.GetSection("TokenAuthentication:Audience").Value,
        // Validate the token expiry
        ValidateLifetime = true,
        // If you want to allow a certain amount of clock drift, set that here:
        ClockSkew = TimeSpan.Zero
    };

    var jwtBearerOptions = new JwtBearerOptions();
    jwtBearerOptions.AutomaticAuthenticate = true;
    jwtBearerOptions.AutomaticChallenge = true;
    jwtBearerOptions.TokenValidationParameters = tokenValidationParameters;
    jwtBearerOptions.SecurityTokenValidators.Clear();
    //below line adds the custom validator class
    jwtBearerOptions.SecurityTokenValidators.Add(new CustomJwtSecurityTokenHandler());
    app.UseJwtBearerAuthentication(jwtBearerOptions);
    
    var tokenProviderOptions = new TokenProviderOptions
    {
        Path = Configuration.GetSection("TokenAuthentication:TokenPath").Value,
        Audience = Configuration.GetSection("TokenAuthentication:Audience").Value,
        Issuer = Configuration.GetSection("TokenAuthentication:Issuer").Value,
        SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
    };

    app.UseMiddleware<TokenProviderMiddleware>(Options.Create(tokenProviderOptions));
}

自定义验证器类:

public class CustomJwtSecurityTokenHandler : ISecurityTokenValidator
{
    private int _maxTokenSizeInBytes = TokenValidationParameters.DefaultMaximumTokenSizeInBytes;
    private JwtSecurityTokenHandler _tokenHandler;

    public CustomJwtSecurityTokenHandler()
    {
        _tokenHandler = new JwtSecurityTokenHandler();
    }
    
    public bool CanValidateToken
    {
        get
        {
            return true;
        }
    }

    public int MaximumTokenSizeInBytes
    {
        get
        {
            return _maxTokenSizeInBytes;
        }

        set
        {
            _maxTokenSizeInBytes = value;
        }
    }

    public bool CanReadToken(string securityToken)
    {
        return _tokenHandler.CanReadToken(securityToken);            
    }

    public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
    {
        //How to access HttpContext/IP address from here?

        var principal = _tokenHandler.ValidateToken(securityToken, validationParameters, out validatedToken);

        return principal;
    }
}

如果令牌被盗,我想添加一个额外的安全层来验证请求来自生成令牌的同一个客户端。

问题:

  1. 有什么方法可以访问CustomJwtSecurityTokenHandler 类中的HttpContext,以便我可以根据当前客户端/请求者添加自定义验证?
  2. 还有其他方法可以使用此类方法/中间件验证请求者的真实性吗?

【问题讨论】:

  • 由于我无法在任何地方找到答案,我将与HttpContext 相关的验证逻辑移至ActionFilter。但是,它确实使解决方案分散。
  • 你需要为 UseIdentity 和 TokenValidationParameters 添加什么包?
  • @schmidlop Microsoft.AspNetCore.IdentityMicrosoft.IdentityModel.Tokens 分别。
  • 您可能会发现我的回答在这里很有用:stackoverflow.com/questions/47138849/…

标签: c# asp.net asp.net-core jwt asp.net-core-middleware


【解决方案1】:

在 ASP.NET Core 中,HttpContext 可以使用IHttpContextAccessor 服务获取。使用 DI 将 IHttpContextAccessor 实例传递给您的处理程序并获取 IHttpContextAccessor.HttpContext 属性的值。

IHttpContextAccessor服务默认没有注册,所以你首先需要在你的Startup.ConfigureServices方法中添加以下内容:

services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

然后修改你的CustomJwtSecurityTokenHandler 类:

private readonly IHttpContextAccessor _httpContextAccessor;

public CustomJwtSecurityTokenHandler(IHttpContextAccessor httpContextAccessor)
{
    _httpContextAccessor = httpContextAccessor;
    _tokenHandler = new JwtSecurityTokenHandler();
}

... 

public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
{
    var httpContext = _httpContextAccessor.HttpContext;
}

您还应该使用 DI 技术进行 JwtSecurityTokenHandler 实例化。如果您不熟悉所有这些内容,请查看 Dependency Injection 文档。


更新:如何手动解决依赖关系(更多信息here

修改Configure方法使用IServiceProvider serviceProvider

public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
         ILoggerFactory loggerFactory, IApplicationLifetime appLifetime,
         IServiceProvider serviceProvider)
{
    ...
    var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
    // and extend ConfigureAuth
    ConfigureAuth(app, httpContextAccessor);
    ...
}

【讨论】:

  • 我明白你想说什么。但是,如果您查看我的代码示例,我需要在中间件注入期间传递CustomJwtSecurityTokenHandler 的实例。此时,没有HttpContext。处理这种情况是我面临的主要问题。
  • 问题不在于我无法访问IHttpContextAccessor。问题是当CustomJwtSecurityTokenHandler 被实例化时,httpContextAccessor.Context 将为空。我试图通过移动我的代码来实现你的建议,但到目前为止还没有成功。再次感谢您的建议。
  • @SangSuantak。也许现在我理解错了,但是:when the CustomJwtSecurityTokenHandler is instantiated, httpContextAccessor.Context would be null - 是的,此时上下文不存在,这就是为什么在初始化期间你只需要存储 HttpContextAccessor 实例,你应该在你的 ValidateToken 方法中调用httpContextAccessor.HttpContext,因为它稍后将在 HTTP 请求到来时调用,因此将创建 HttpContext。
  • 你是对的,我以错误的方式接近解决方案。我已成功实施您的建议。非常感谢:D。
  • @Set 你的例​​子不完整。 jwtBearerOptions.SecurityTokenValidators.Add(new CustomJwtSecurityTokenHandler()); 需要你在添加验证器时手动注入http上下文访问器
【解决方案2】:

对于自定义 JWT 验证器,我创建了一个继承 IOAuthBearerAuthenticationProvider 的 JWTCosumerProvider 类。并实现 ValidateIdentity() 方法检查我存储客户端 IP 地址的身份声明,然后与当前请求的 Id 地址进行比较。

public Task ValidateIdentity(OAuthValidateIdentityContext context)
    {

        var requestIPAddress = context.Ticket.Identity.FindFirst(ClaimTypes.Dns)?.Value;

        if (requestIPAddress == null)
            context.SetError("Token Invalid", "The IP Address not right");

        string clientAddress = JWTHelper.GetClientIPAddress();
        if (!requestIPAddress.Equals(clientAddress))
            context.SetError("Token Invalid", "The IP Address not right");


        return Task.FromResult<object>(null);
    }

JWTHelper.GetClientIPAddress()

internal static string GetClientIPAddress()
    {
        System.Web.HttpContext context = System.Web.HttpContext.Current;
        string ipAddress = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];

        if (!string.IsNullOrEmpty(ipAddress))
        {
            string[] addresses = ipAddress.Split(',');
            if (addresses.Length != 0)
            {
                return addresses[0];
            }
        }

        return context.Request.ServerVariables["REMOTE_ADDR"];
    }

希望对您有所帮助!

【讨论】:

  • 嗨,你能告诉我应该在我的核心 api 中哪里使用这个代码吗?我已经在 jwt toekn 中设置了 IP 地址,现在我需要使用 jwt toekn 值验证请求者的 IP 地址。提前致谢。
【解决方案3】:

只是为了补充另一个解决方案而不注入 ISecurityTokenValidator,可能就像

在您的 ISecurityTokenValidator 实现中(在本例中为 CustomJwtSecurityTokenHandler)

public class CustomJwtSecurityTokenHandler : ISecurityTokenValidator {
   ...

   //Set IHttpContextAccessor as public property to set later in Starup class
   public IHttpContextAccessor _httpContextAccessor { get; set; };

   //Remove injection of httpContextAccessor;
   public CustomJwtSecurityTokenHandler()
   {
   _tokenHandler = new JwtSecurityTokenHandler();
   }

   ...

并在 Startup 类中将属性“CustomJwtSecurityTokenHandler”配置为全局成员

public readonly CustomJwtSecurityTokenHandler customJwtSecurityTokenHandler = new()

在 Startup 类的 ConfigureServices 方法中添加全局 customJwtSecurityTokenHandler。

 public void ConfigureServices(IServiceCollection services)
 {

      ...

      services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
           .AddJwtBearer(
                o =>
                {
                    ...
                    //Add the global ISercurityTokenValidator implementation
                    o.SecurityTokenValidators.Add(this.customJwtSecurityTokenHandler );
                }
            );

      ...
} 

然后在 Startup 类的 Configure 方法中将 IHttpContextAccessor 实例传递给全局 customJwtSecurityTokenHandler (ISecurityTokenValidator) 的属性

public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
         ILoggerFactory loggerFactory, IApplicationLifetime appLifetime,
         IServiceProvider serviceProvider)
{
    ...
    var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
    //And add to property, and not by constructor
    customJwtSecurityTokenHandler.httpContextAccessor = httpContextAccessor;
    ...
}

在我的例子中,我已经在 ConfigureService 中配置了 SecurityTokenValidator,所以此时不​​存在任何 IServiceProvider 实例,那么在 Configure 方法中你可以使用 IServiceProvider 来获取 IHttpContextAccessor

【讨论】:

    猜你喜欢
    • 2017-01-03
    • 2020-08-22
    • 2016-09-25
    • 2020-09-04
    • 2021-04-29
    • 2020-04-04
    • 1970-01-01
    • 2020-07-01
    • 2022-11-26
    相关资源
    最近更新 更多