Как проверить access_denied на клиенте при использовании IdentityServer4?
У меня есть страница входа в систему, и мне нужно, когда пользователь нажимает кнопку Отмена, чтобы перенаправить его на доступ к запрещенной странице в клиентском приложении.
Действие внутри логина:
if (button != "login")
{
// the user clicked the "cancel" button
var context = await interaction.GetAuthorizationContextAsync(model.ReturnUrl);
if (context != null)
{
// if the user cancels, send a result back into IdentityServer as if they
// denied the consent (even if this client does not require consent).
// this will send back an access denied OIDC error response to the client.
await interaction.GrantConsentAsync(context, ConsentResponse.Denied);
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
return Redirect(model.ReturnUrl);
}
}
и на стороне клиента (MVC) я настроил следующее событие:
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = context =>
{
// here it's returned as 200 ok in case I denied
// consent should'nt be 401 access denined??
var statusCode=context.Response.StatusCode;
context.Response.Redirect("/");
context.HandleResponse();
return Task.FromResult(0);
}
};
Но мой вопрос: как мне узнать, что IdentityServer4 не удалось, потому что пользователь нажал кнопку Отмена (access_denied), или если есть другая проблема, вызвавшая эту ошибку?
1 ответ
На стороне IdentityServer:
Базовая форма имеет 2 кнопки: login
а также cancel
, Если login
не нажимается; это cancel
,
В противном случае это ошибка проверки, и вы можете показать ее. На cancel
Вы должны перенаправить обратно на страницу, которая имеет смысл.
На стороне MVC:
Вы можете перенаправить с дополнительными параметрами. Их можно получить и использовать для отображения ошибки. Имейте в виду, что большая часть обработки ошибок, например, неверное имя пользователя / пароль, остается на стороне IdentityServer.
Как проверить access_denied на клиенте при использовании IdentityServer4?
После некоторого осмотра Событие context.Failure.Data
это тип ListDictionaryInternal
, который содержит записи, которые вы, вероятно, ищете:
error
error_description
error_uri
.
Итак, используя context.Failure.Data["error"]
вы можете получить искомое значение "access_denied".
Другой вариант
Используя демонстрационный код Identity Server 4 в качестве примера, если мы перейдем к действию Login Post Account Controller, вас заинтересует раздел, который обрабатывает нажатие кнопки отмены:
// check if we are in the context of an authorization request
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
// the user clicked the "cancel" button
if (button != "login")
{
if (context != null)
{
// RATHER THAN USING THE ORIGINAL STRATEGY:
//await _interaction.GrantConsentAsync(context, ConsentResponse.Denied);
// ...MANUALLY BUILD THE RETURN URL YOURSELF
// THEN REDIRECT TO THAT:
model.ReturnUrl = BuildCancelReturnUrl(context);
if (await _clientStore.IsPkceClientAsync(context.ClientId))
{
return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
}
return Redirect(model.ReturnUrl);
}
else
{
// since we don't have a valid context, then we just go back to the home page
return Redirect("~/");
}
}
Как видите, вместо вызова GrantConsentAsync
ответить access_denied
код (который не включает error_description
или error_uri
values), вы можете просто заменить URL-адрес возврата на redirect_uri, добавив параметр ошибки самостоятельно. Хитрость здесь в том, что, поскольку параметры error_description и error_uri являются полностью необязательными, вы можете извлечь из этого выгоду и передать что-нибудь значимое, чтобы каждый клиент знал, что в доступе было отказано, потому что пользователь отменил форму. Затем используйте этот URL-адрес возврата в перенаправлении.
Вот пример того, как "воссоздать" возвращаемый URL. У вас может быть такая функция, как:
private string BuildCancelReturnUrl(AuthorizationRequest context)
{
var RedirectUri = new UriBuilder(context.RedirectUri);
var Query = HttpUtility.ParseQueryString(string.Empty);
Query.Add("error", "access_denied");
Query.Add("error_description", "some_meaningful_code_here");
// The state IS MEGA IMPORTANT:
Query.Add("state", context.Parameters["state"]);
RedirectUri.Query = Query.ToString();
return RedirectUri.ToString();
}
// Use like: model.ReturnUrl = BuildCancelReturnUrl(context);
// See above extract from Identity Server's demo code
Итак, на стороне клиента вы можете создать такое расширение:
public static class OpenIdConnectOptionsExtensions {
public static OpenIdConnectOptions UseRedirectionOnLoginCancel(
this OpenIdConnectOptions options
, string RedirectTo = "/")
{
options.Events.OnAccessDenied = context =>
{
NameValueCollection RequestQuery = HttpUtility.ParseQueryString(context.Request.QueryString.Value);
string descriptionField = "error_description";
string cancelledCode = "your_meaningful_description_code_here";
bool descriptionIncluded = RequestQuery.AllKeys.Contains(descriptionField);
if (descriptionIncluded && RequestQuery[errorDescriptionField].Equals(cancelledCode))
{
context.Response.Redirect(RedirectTo);
context.HandleResponse();
}
return Task.CompletedTask;
};
return options;
}
}
Наконец, каждый клиент может быть настроен для перенаправления туда, куда вы укажете в Startup.cs
:
services.AddOpenIdConnect(<challenge_name>, config =>
{
// ...
config.UseRedirectionOnLoginCancel(<final path to redirect to>);
});