Проверяйте только jwt, если присутствует заголовок канала - PullRequest
2 голосов
/ 21 июня 2019

У меня есть несколько конечных точек API (.net core mvc), которые будут использовать зарегистрированного пользователя (JWT), если пользователь вошел в систему, но любой может вызывать метод.Используя [AllowAnonymous] вместе с [Authorize], я получаю нужную функциональность, но если срок действия отправленного JWT истек, я не получаю 401, а вместо этого он обрабатывается как анонимный запрос.

Мне нужна логика авторизации для обработки конечной точки, как это было [Authorize], только если есть заголовок Authorization: Bearer, означающий, что если токен истек, он должен вернуть 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 Не авторизовано при использовании токенов с истекшим сроком вместо вызова метода как анонимного.Можно ли это настроить?

Обновление Я создал атрибут следующим образом:

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 этовозвращает истину.Не имеет значения, отправляю ли я неверный заголовок JWT или нет.Разве это не должен быть правильный способ выполнения политики?

Ответы [ 3 ]

1 голос
/ 22 июня 2019

Спасибо за ваши предложения. Во время реализации решения, опубликованного 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:

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();

Используется только как фиктивный "разрешить все"

0 голосов
/ 21 июня 2019

Вы можете создать свою собственную логику аутентификации, реализовав интерфейс 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
}

Дополнительная информация и примеры приведены здесь.

0 голосов
/ 21 июня 2019

Вы можете написать собственное промежуточное программное обеспечение для этого, например:

        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;
        }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...