У меня есть приложение 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));