Несколько методов аутентификации в одном контроллере ASP.NET Core 2.2 - PullRequest
0 голосов
/ 07 октября 2019

У меня есть приложение ASP.NET Core 2.2, которое работает на CentOS, но все еще требует авторизации на основе свойств Active Directory. Поэтому я написал пользовательский AuthenticationHandler с использованием библиотеки LDAP Novell, которая определит требуемые требования. Все работает нормально, но медленно.

Поэтому я добавил JWT в качестве второго метода аутентификации. Это тоже работает само по себе. Конфигурация в Startup.cs выглядит следующим образом:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(cfg => {
    cfg.RequireHttpsMetadata = true;
    cfg.SaveToken = true;

    cfg.TokenValidationParameters = new TokenValidationParameters() {
        // Omitted for brevity
    };
})
.AddScheme<AuthenticationSchemeOptions, MyAuthenticationHandler>(Constants.MyAuthenticationScheme, null);

services.AddAuthorization(o => {
    o.AddPolicy(Constants.AuthorisationPolicy, p => p.RequireClaim(ClaimTypes.GroupSid, "group"));
});

Проблема, на определение которой у меня ушло довольно много времени, возникает в следующей ситуации: во-первых, я хочу использовать токен-носитель только для запросов на чтение,не для изменений. Во-вторых, для маркера XSRF требуются операции с атрибутом [ValidateAntiForgeryToken] для использования метода аутентификации на странице, поэтому я должен использовать здесь свой собственный метод. Поэтому я придумал следующую конструкцию:

[Authorize(Policy = Constants.AuthorisationPolicy)]
[Produces("application/json")]
[Route("api/structure")]
[ApiController]
public sealed class DirectoryStructureController : ControllerBase {
    //...

    [HttpPost]
    [Route("create")]
    [Authorize(AuthenticationSchemes = Constants.MyAuthenticationScheme,
        Policy = Constants.AuthorisationPolicy)]
    [ValidateAntiForgeryToken]
    public ActionResult<Result> Create([FromForm] string folder) {
        //...
    }
}

Эта конструкция работает - она ​​использует мою собственную аутентификацию для Create и JWT для других операций (потому что это по умолчанию), , нотолько , если Create - первая операция, которая вызывается на контроллере. Если я вызову любую другую операцию (используя JWT) раньше, Create завершится неудачно с 401 с заголовком www-authenticate: Bearer. Использование JWT один раз как-то «сжигает» контроллер, поэтому он больше не может использовать мой пользовательский обработчик.

Есть ли способ исправить это? Я думаю, я мог бы добавить еще один контроллер только для создания и обновления, но я не хотел бы разрывать логику приложения. Так есть ли исправление для каждого метода?

Заранее спасибо, Кристоф

Редактировать: Пользовательский обработчик аутентификации довольно сложен, но следующая минимальная версия воспроизводит поведение(он аутентифицирует всех и выдает требуемое групповое утверждение):

public sealed class MyAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions> {

    public MyAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
         : base(options, logger, encoder, clock) { }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync() {
        var claims = new[] {
            new Claim(ClaimTypes.Name, "user"),
            new Claim(ClaimTypes.GroupSid, "group")
        };
        var identity = new ClaimsIdentity(claims, this.Scheme.Name);
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, this.Scheme.Name);
        return Task.FromResult(AuthenticateResult.Success(ticket));
    }

Соответствующий контроллер, выдающий токен-носитель JWT, будет:

[Authorize(AuthenticationSchemes = Constants.MyAuthenticationScheme)]
[Route("api/[controller]")]
[ApiController]
public class TokenController : ControllerBase {

    [HttpGet]
    public IActionResult Get() {
        var claims = new[] {
            new Claim(ClaimTypes.Name, "user"),
            new Claim(ClaimTypes.GroupSid, "group")
        };

        var creds = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes("key")), SecurityAlgorithms.HmacSha256);
        var expires = DateTime.UtcNow + TimeSpan.FromMinutes(5);

        var token = new JwtSecurityToken("issuer", "audience", claims,
            expires: expires,
            signingCredentials: creds);

        var handler = new JwtSecurityTokenHandler();

        var retval = handler.WriteToken(token);

        return this.Ok(new {
            Token = token,
            LifeTime = TimeSpan.FromMinutes(5)
        });
    }
}

В JavaScript можно получить токен, подобный

$.ajax({
    url: '/api/token'
}).done(function (data, statusText, xhdr) {
    // Save the token
}.bind(this)).fail(function (xhr, status, error) {
    // Handle error
}.bind(this));

и используйте его как

$.ajax({
    url: '/api/structure/somemethod'
    beforeSend: function (xhr) {
        xhr.setRequestHeader('Authorization', 'Bearer ' + token);
    }
}).done(function (data, statusText, xhdr) {
    // Use the result of somemethod
}.bind(this)).fail(function (xhr, status, error) {
    // Handle error
}.bind(this));

После этого вызова любой последующий вызов завершится неудачно, как описано выше:

$.ajax({
    type: 'POST',
    url: '/api/structure/create',
    dataType: 'json',
    data: '=' + folder,
    headers: {
        'RequestVerificationToken': xsrftok // From @Xsrf.GetAndStoreTokens(this.HttpContext).RequestToken
    }
}).done(function (data, statusText, xhdr) {
    // Show success message
}.bind(this)).fail(function (xhr, status, error) {
    // Handle error
}.bind(this));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...