【问题标题】:Authentication & Authorization - Token in HTTP request body身份验证和授权 - HTTP 请求正文中的令牌
【发布时间】:2020-02-12 01:23:10
【问题描述】:

我正在尝试创建一个自定义身份验证处理程序,该处理程序需要在 HTTP 请求的正文中使用 Bearer JWT,但我不希望创建一个全新的自定义授权。不幸的是,我唯一能做的就是读取 HTTP 请求正文,从那里获取令牌并将其放入请求的 Authorization 标头中。

有没有其他更有效的方法来做到这一点?我所做的只是在GitHub 上找到默认的JwtBearerHandler 实现,但是当我进行一些修改时,它无法正确读取主体。

Startup.cs:

services.AddAuthentication(auth =>
{
    auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.RequireHttpsMetadata = true;
    options.SaveToken = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        ValidateIssuer = false,
        ValidateAudience = false,
        ValidateLifetime = true,
        IssuerSigningKey = new SymmetricSecurityKey(key),
        RequireExpirationTime = true,
        ClockSkew = TimeSpan.FromSeconds(30)
    };

    options.Events = new JwtBearerEvents
    {
        OnAuthenticationFailed = ctx =>
        {
            if (ctx.Exception.GetType() == typeof(SecurityTokenExpiredException))
            {
                ctx.Response.Headers.Add("Token-Expired", "true");
            }
            return Task.CompletedTask;
        }
    };
});
public class AuthHandler : JwtBearerHandler
{
    private readonly IRepositoryEvonaUser _repositoryUser;
    private OpenIdConnectConfiguration _configuration;

    public AuthHandler(IOptionsMonitor<JwtBearerOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder,
        IDataProtectionProvider dataProtection,
        ISystemClock clock,
        IRepositoryUser repositoryUser,
        OpenIdConnectConfiguration configuration
        )
        : base(options, logger, encoder, dataProtection, clock)
    {
        _repositoryUser = repositoryUser;
    }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        string token = null;
        try
        {
            var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);

            await Events.MessageReceived(messageReceivedContext);
            if (messageReceivedContext.Result != null)
            {
                return messageReceivedContext.Result;
            }
            token = messageReceivedContext.Token;

            if (string.IsNullOrEmpty(token))
            {
                Request.EnableBuffering();
                using (var reader = new StreamReader(Request.Body, Encoding.UTF8, true, 10, true))
                {
                    var jsonBody = reader.ReadToEnd();
                    var body = JsonConvert.DeserializeObject<BaseRequest>(jsonBody);
                    if (body != null)
                    {
                        token = body.Token;
                    }
                    Request.Body.Position = 0;
                }

                if (string.IsNullOrEmpty(token))
                {
                    return AuthenticateResult.NoResult();
                }
            }


            if (_configuration == null && Options.ConfigurationManager != null)
            {
                _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
            }

            var validationParameters = Options.TokenValidationParameters.Clone();
            if (_configuration != null)
            {
                var issuers = new[] { _configuration.Issuer };
                validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;
            }

            List<Exception> validationFailures = null;
            SecurityToken validatedToken;

            foreach (var validator in Options.SecurityTokenValidators)
            {
                if (validator.CanReadToken(token))
                {
                    ClaimsPrincipal principal; // it can't find this
                    try
                    {
                        principal = validator.ValidateToken(token, validationParameters, out validatedToken);
                    }
                    catch (Exception ex)
                    {

                        if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
                            && ex is SecurityTokenSignatureKeyNotFoundException)
                        {
                            Options.ConfigurationManager.RequestRefresh();
                        }

                        if (validationFailures == null)
                        {
                            validationFailures = new List<Exception>(1);
                        }
                        validationFailures.Add(ex);
                        continue;
                    }

                    var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
                    {
                        Principal = principal,
                        SecurityToken = validatedToken
                    };

                    await Events.TokenValidated(tokenValidatedContext);
                    if (tokenValidatedContext.Result != null)
                    {
                        return tokenValidatedContext.Result;
                    }

                    if (Options.SaveToken)
                    {
                        tokenValidatedContext.Properties.StoreTokens(new[]
                        {
                            new AuthenticationToken { Name = "access_token", Value = token }
                        });
                    }

                    tokenValidatedContext.Success();
                    return tokenValidatedContext.Result;
                }
            }

            if (validationFailures != null)
            {
                var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
                {
                    Exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures)
                };

                await Events.AuthenticationFailed(authenticationFailedContext);
                if (authenticationFailedContext.Result != null)
                {
                    return authenticationFailedContext.Result;
                }

                return AuthenticateResult.Fail(authenticationFailedContext.Exception);
            }

            return AuthenticateResult.Fail("No SecurityTokenValidator available for token: " + token ?? "[null]");
        }
        catch (Exception ex)
        {

            var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
            {
                Exception = ex
            };

            await Events.AuthenticationFailed(authenticationFailedContext);
            if (authenticationFailedContext.Result != null)
            {
                return authenticationFailedContext.Result;
            }

            throw;
        }
    }
}

或者,有没有办法告诉应用程序在 HTTP 请求正文中期待 JWT?我很清楚令牌应该在请求标头而不是正文中发送,但我很想看看是否(如果是,如何)这可以实现。

我也试过这个:

OnMessageReceived = ctx =>
{
       ctx.Request.EnableBuffering();
       using (var reader = new StreamReader(ctx.Request.Body, Encoding.UTF8, true, 10, true))
       {
              var jsonBody = reader.ReadToEnd();
              var body = JsonConvert.DeserializeObject<BaseRequest>(jsonBody);
              if (body != null)
              {
                     ctx.Token = body.Token;
                     ctx.Request.Body.Position = 0;
              }
       }
       return Task.CompletedTask;
}

【问题讨论】:

    标签: authentication .net-core jwt asp.net-core-webapi bearer-token


    【解决方案1】:

    我会将@Nan Yu 的答案标记为正确答案,但我还是会发布我的最终代码。我所做的基本上是恢复到默认的JwtBearerHandler 并使用JwtBearerOptionsJwtBearerEventsOnMessageReceived 事件从HTTP 请求的正文中获取令牌值。

    它们都位于Microsoft.AspNetCore.Authentication.JwtBearer 命名空间中。

    services
        .AddAuthentication(auth =>
        {
            auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(options =>
        {
            options.RequireHttpsMetadata = true;
            options.SaveToken = true;
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateLifetime = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                RequireExpirationTime = true,
                ClockSkew = TimeSpan.Zero
            };
    
            options.Events = new JwtBearerEvents
            {
                OnAuthenticationFailed = ctx =>
                {
                    if (ctx.Exception.GetType() == typeof(SecurityTokenExpiredException))
                    {
                        ctx.Response.Headers.Add("Token-Expired", "true");
                    }
                    return Task.CompletedTask;
                },
                OnMessageReceived = ctx =>
                {
                    ctx.Request.EnableBuffering();
                    using (var reader = new StreamReader(ctx.Request.Body, Encoding.UTF8, true, 1024, true))
                    {
                        var jsonBody = reader.ReadToEnd();
                        var body = JsonConvert.DeserializeObject<BaseRequest>(jsonBody);
                        ctx.Request.Body.Position = 0;
                        if (body != null)
                        {
                            ctx.Token = body.Token;
                        }
                    }
                    return Task.CompletedTask;
                }
            };
        });
    

    【讨论】:

      【解决方案2】:

      默认情况下,AddJwtBearer 将从请求标头中获取令牌,您应该编写逻辑以从请求正文中读取令牌并验证令牌。这意味着没有这样的配置来“告诉”中间件读取令牌表单请求正文。

      如果在请求体中发送token,则需要在jwt中间件到达之前读取中间件中的请求体并将token放入header中。或者在 jwt 承载中间件的一个事件中读取请求正文,例如 OnMessageReceived event ,读取请求正文中的令牌,最后设置令牌,例如:context.Token = token;Here 是在中间件中读取请求体的代码示例。

      【讨论】:

      • 这实际上很疯狂,因为我最初尝试在我的OnMessageReceived 事件中设置context.Token = token;,但它没有做任何事情。但是,在您的建议下,我今天尝试了它,它工作得很好。
      • 我用不起作用的代码更新了原始的 bode。它与有效的几乎相同。任何想法为什么会这样?
      • 不确定,您可以从原始代码中调试到OnMessageReceived 事件以确认问题所在。但有时,是的,你会发现这个项目“突然”起作用了......:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-10
      • 2018-07-01
      • 2018-06-22
      • 2021-10-27
      相关资源
      最近更新 更多