【问题标题】:Only validate JWT if bearer header is present仅在存在承载标头时才验证 JWT
【发布时间】:2019-11-04 01:29:05
【问题描述】:

如果用户已登录,我有一些 API 端点(.net core mvc)将使用登录用户(JWT),但任何人都可以调用该方法。 通过将 [AllowAnonymous][Authorize] 一起使用,我得到了我正在寻找的功能,但如果发送的 JWT 已过期,我不会收到 401,而是将其视为匿名请求。

只有当存在 Authorization: Bearer 标头时,我才需要授权逻辑将端点视为 [Authorize],这意味着如果令牌过期,它应该返回 401

此功能仅在少数端点上需要,而不是在完整的控制器上

我试过[AllowAnonymous] + [Authorize]的组合。

在创建策略时也尝试过RequireAssertion,但似乎不是为此

我用于测试的方法:

[HttpPost]
[Route("testToken")]
[AllowAnonymous]
[Authorize(Policy = AuthFilterConvension.POLICY_AUTHORIZE_WHEN_HAS_BEARER)]
public async Task<IActionResult> testToken()
{
    var user = await _signInManager.UserManager.GetUserAsync(HttpContext.User);
        return Ok(new {result = user});
    }

设置身份验证以支持 cookie + JWT:

services.AddAuthorization(o =>
            {
                o.AddPolicy(AuthFilterConvension.POLICY_AUTHORIZE_WHEN_HAS_BEARER, b =>
                {
                    b.RequireRole("Admin");
                    b.RequireAuthenticatedUser();
                    b.AuthenticationSchemes = new List<string> {JwtBearerDefaults.AuthenticationScheme};
                    
                });
            });
            services.AddAuthentication()
                .AddCookie()
                .AddJwtBearer(cfg =>
                    {
                        var issuer = Environment.GetEnvironmentVariable("JWT_ISSUER");
                        var tokenKey = Environment.GetEnvironmentVariable("JWT_TOKEN_KEY");

                        cfg.RequireHttpsMetadata = false;
                        cfg.SaveToken = true;

                        cfg.TokenValidationParameters = new TokenValidationParameters
                        {
                            RequireExpirationTime = true,
                            RequireSignedTokens = true,
                            ValidateAudience = true,
                            ValidateIssuer = true,
                            ValidateLifetime = true,

                            ValidIssuer = issuer,
                            ValidAudience = issuer,
                            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenKey))
                        };
                    }
                );

我希望[Authorize] 标头在使用过期令牌而不是以匿名方式调用该方法时返回 401 Unauthorized。这个可以设置吗?

更新 我创建了这样的属性:

public class AuthorizeBearerWhenPresent : ActionFilterAttribute, IAsyncAuthorizationFilter
    {
        public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
        {
            var headers = context.HttpContext.Request;
            
            //TODO: Test header
            var httpContext = context.HttpContext;
            var authService = httpContext.RequestServices.GetRequiredService<IAuthorizationService>();

            var authResult = await authService.AuthorizeAsync(httpContext.User, context,
                AuthFilterConvension.POLICY_AUTHORIZE_WHEN_HAS_BEARER);
            
            if (!authResult.Succeeded)
            {
                context.Result = new UnauthorizedResult();
            }
        }
    }

但是,无论我向authService 发送什么,它都会返回true。我发送无效的 JWT 标头或没有标头都没有关系。这不应该是执行政策的正确方法吗?

【问题讨论】:

    标签: c# asp.net-core jwt asp.net-core-mvc authorization


    【解决方案1】:

    您可以编写一个自定义中间件来执行此操作,例如:

            private readonly RequestDelegate _next;
    
            public MyMiddleware(RequestDelegate next)
            {
    
                _next = next;
            }
    
            public async Task InvokeAsync(HttpContext httpContext, IConfiguration configuration)
            {
                if (httpContext.Request.Headers.ContainsKey("Authorization"))
                {
    
                    var authorizationToken = httpContext.Request.Headers["Authorization"].ToString();
    
                    if (!authorizationToken.StartsWith("bearer", StringComparison.OrdinalIgnoreCase))
                    {
                        await UnauthorizedResponseAsync(httpContext);
                    }
                    else
                    {
                        var token  =authorizationToken.Substring("Bearer".Length).Trim())
    
                       if (httpContext.Request.Path == "Some of your path")
                       {
                       // DO your stuff
                       await _next.Invoke(httpContext);
                       }
                    }
                }
                else
                {
                    await UnauthorizedResponseAsync(httpContext);
                }
            }
    
            private static async Task UnauthorizedResponseAsync(HttpContext httpContext)
            {
                httpContext.Response.StatusCode = 401;
                await httpContext.Response.WriteAsync("Unauthorized");
                return;
            }
    

    【讨论】:

    • 谢谢,在这种情况下应该是:``` if (httpContext.Request.Headers.ContainsKey("Authorization")){ //Call JWT auth } else { //Allow Anonymous _next. Invoke(...) } ``` 如何调用我的 JWT 策略 (POLICY_AUTHORIZE_WHEN_HAS_BEARER)?
    • 您需要编写自定义警察广告解码 jwt 令牌并自己检查所有内容。
    【解决方案2】:

    您可以通过实现IAuthenticationFilter 接口并从ActionFilterAttribute 继承来创建自己的身份验证逻辑:

    public class MyCustomAuthentication : ActionFilterAttribute, System.Web.Http.Filters.IAuthenticationFilter
    {
        public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
        {
            HttpRequestMessage request = context.Request;
            AuthenticationHeaderValue authorization = request.Headers.Authorization;
             // Handle the authorization header
        }
    }
    

    然后在您的控制器中,您可以将属性添加到类或特定方法中。

    [MyCustomAuthentication]
    public async Task<IHttpActionResult> DoSomethingAsync()
    {
        // ...
    }
    

    如果令牌存在,您还必须手动验证令牌。 下面的代码基于 .Net Framework,所以不确定这是否也适用于 Core。

    // Build URL based on your AAD-TenantId
    var stsDiscoveryEndpoint = String.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}/.well-known/openid-configuration", "<Your_tenant_ID>");
    
    // Get tenant information that's used to validate incoming jwt tokens
    var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
    
    // Get Config from AAD:
    var config = await configManager.GetConfigurationAsync();
    
    // Validate token:
    var tokenHandler = new JwtSecurityTokenHandler();
    
    var validationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
    {
        ValidAudience = "<Client_ID>",
        ValidIssuer = "<Issuer>",
        IssuerSigningTokens = config.SigningTokens,
        CertificateValidator = X509CertificateValidator.ChainTrust
    };
    
    var parsedToken = (System.IdentityModel.Tokens.SecurityToken)new JwtSecurityToken();
    
    try
    {
        tokenHandler.ValidateToken(token, validationParameters, out parsedToken);
        result.ValidatedToken = (JwtSecurityToken)parsedToken;
    }
    catch (System.IdentityModel.Tokens.SecurityTokenValidationException stve)
    {
        // Handle error using stve.Message
    }
    

    Some more info and examples here.

    【讨论】:

    • 谢谢,如果标头存在,我如何调用我的 JWT 策略?
    • 谢谢,在我看到你之前更新了我的信息。我去看看
    【解决方案3】:

    感谢您的建议。在实施 Bart van der Drift 发布的解决方案时,我偶然发现了自定义标头与使用 IAuthorizationPolicyProvider

    的组合

    这样我就使用了一个自定义策略名称,然后我会覆盖它:

    AuthFilterConvension中的常量:

    public const string POLICY_JWT = "jwtPolicy";
    public const string POLICY_AUTHORIZE_WHEN_HAS_BEARER = "authorizeWhenHasBearer";
    
    

    设置策略:

    services.AddAuthorization(o =>
                {
                    o.AddPolicy(AuthFilterConvension.POLICY_JWT, b =>
                    {
                        b.RequireRole("Admin");
                        b.RequireAuthenticatedUser();
                        b.AuthenticationSchemes = new List<string> {JwtBearerDefaults.AuthenticationScheme};
    
                    });
                }); 
    

    添加自定义属性:

        public class AuthorizeBearerWhenPresent : AuthorizeAttribute
        {
            public AuthorizeBearerWhenPresent()
            {
                Policy = AuthFilterConvension.POLICY_AUTHORIZE_WHEN_HAS_BEARER;
            }
        }
    

    名称 POLICY_AUTHORIZE_WHEN_HAS_BEARER 没有配置,只是在我的CustomPolicyProvicer中用作key:

    public class CustomPolicyProvider : IAuthorizationPolicyProvider
        {
            private readonly IHttpContextAccessor _httpContextAccessor;
            private readonly DefaultAuthorizationPolicyProvider _fallbackPolicyProvider;
    
            public CustomPolicyProvider(IHttpContextAccessor httpContextAccessor, IOptions<AuthorizationOptions> options)
            {
                _httpContextAccessor = httpContextAccessor;
                _fallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
            }
    
            public async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
            {
                if (AuthFilterConvension.POLICY_AUTHORIZE_WHEN_HAS_BEARER.Equals(policyName))
                {
                    if (_httpContextAccessor.HttpContext.Request.Headers.ContainsKey("Authorization"))
                    {
                        return await _fallbackPolicyProvider.GetPolicyAsync(AuthFilterConvension.POLICY_JWT);    
                    }
    
                    return new AuthorizationPolicyBuilder()
                        .RequireAssertion(x=>true)
                        .Build();
                }
    
    
                return await _fallbackPolicyProvider.GetPolicyAsync(policyName);
            }
    
            public async Task<AuthorizationPolicy> GetDefaultPolicyAsync()
            {
                return await _fallbackPolicyProvider.GetDefaultPolicyAsync();
            }
        }
    

    这样我可以避免对 JWT 令牌进行自定义处理

    以下内容:

    return new AuthorizationPolicyBuilder()
      .RequireAssertion(x=>true)
      .Build();
    

    仅用作虚拟“全部允许”

    【讨论】:

      猜你喜欢
      • 2018-05-04
      • 2018-11-25
      • 2015-09-13
      • 2015-05-09
      • 2018-07-10
      • 2021-10-07
      • 1970-01-01
      • 1970-01-01
      • 2023-03-25
      相关资源
      最近更新 更多