Как проверить 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_urivalues), вы можете просто заменить 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>);
});
Другие вопросы по тегам