#asp.net-mvc #authorization #asp.net-identity #identity #claims-based-identity
Вопрос:
Я указал Политику авторизации , для которой требуется область my_custom_value
действия, например
services.AddAuthorization(AuthConfig.GetAuthorizationOptions); // ... public static void GetAuthorizationOptions(AuthorizationOptions options) { options.AddPolicy("MyPolicy", policy =gt; { policy.RequireScope("my_custom_value"); });
Запросы на конечные точки, которые защищены MyPolicy
, завершаются неудачно, потому что принципал не содержит никаких областей
Я вижу, что мой токен аутентификации имеет следующие области действия:
"scope": [ "openid", "profile", "my_custom_value", "offline_access" ],
Похоже, что они не сопоставляются с требованиями Принципала. Когда я проверяю утверждения позже, когда пользователь пытается получить доступ к защищенной конечной точке, областей нет.
policy.RequireAssertion(context =gt; { if (context.User.HasClaim(c =gt; c.Type == "scope")) // lt;-- always false { if (context.User.HasClaim(c =gt; c.Value == "my_custom_value")) { return true; } }
Почему области не отображаются на карте? Что мне нужно сделать, чтобы нанести их на карту?
Для справки, я пробовал это с
options.ClaimActions.MapUniqueJsonKey(JwtClaimTypes.Scope, "scope"); options.Scope.Add("my_custom_value");
Должен ли я реализовать пользовательский IProfileService для включения областей в OnUserInformationReceived
событие?
Ответ №1:
При выполнении проверки подлинности oidc с использованием MVC только утверждения IdentityToken сопоставляются с ClaimsPrincipal. Я не мог придумать, как сопоставить или включить утверждения о маркерах доступа в ClaimsPrincipal.
В итоге я написал обработчик авторизации, который проверяет маркер доступа и выполняет необходимые проверки утверждений. Я предполагаю, что вы читали о политике авторизации в asp.net 5.0.
public class AccessTokenAuthorizationHandler : AuthorizationHandlerlt;AccessTokenRequirementgt; { readonly IOptionsMonitorlt;OpenIdConnectOptionsgt; _openIdConnectOptions; readonly ILoggerlt;AccessTokenAuthorizationHandlergt; _logger; readonly IOptionslt;OpenIdOptionsgt; _openIdOptions; public AccessTokenAuthorizationHandler( ILoggerlt;AccessTokenAuthorizationHandlergt; logger, IOptionsMonitorlt;OpenIdConnectOptionsgt; openIdConnectOptions, IOptionslt;OpenIdOptionsgt; openIdOptions) { _logger = logger; _openIdConnectOptions = openIdConnectOptions; _openIdOptions = openIdOptions; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AccessTokenRequirement requirement) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (requirement == null) { throw new ArgumentNullException(nameof(requirement)); } if (context.Resource is Microsoft.AspNetCore.Mvc.ActionContext actionContext) { ClaimsPrincipal principal = await GetAccessTokenPrincipal(actionContext.HttpContext).ConfigureAwait(false); // verify your requirement if (condition met) { context.Succeed(requirement); } } } private async Tasklt;ClaimsPrincipalgt; GetAccessTokenPrincipal(HttpContext httpContext) { if (httpContext == null) { return null; } String accessToken = await httpContext.GetUserAccessTokenAsync().ConfigureAwait(false); if (!String.IsNullOrWhiteSpace(accessToken)) { try { TokenValidationParameters validationParameters = await BuildValidationParameters(); return new JwtSecurityTokenHandler().ValidateToken(accessToken, validationParameters, out var rawValidatedToken); } catch (SecurityTokenValidationException validationException) { _logger.LogWarning(validationException, "Access token not valid."); } catch (Exception ex) { _logger.LogError(ex, "Access token could not be validated."); } } return null; } private async Tasklt;TokenValidationParametersgt; BuildValidationParameters() { var options = _openIdConnectOptions.Get(OpenIdConnectDefaults.AuthenticationScheme); var discoveryDocument = await options.ConfigurationManager.GetConfigurationAsync(CancellationToken.None); var signingKeys = discoveryDocument.SigningKeys; var validationParameters = new TokenValidationParameters { RequireExpirationTime = true, RequireSignedTokens = true, ValidateIssuer = true, ValidIssuer = options.Authority, ValidateIssuerSigningKey = true, IssuerSigningKeys = signingKeys, ValidateLifetime = true, ValidateAudience = true, ValidAudience = "your audience", ValidateActor = false, ValidTypes = new String[] { "at jwt" }, ClockSkew = TimeSpan.FromMinutes(2), }; return validationParameters; }
}
Я не рад, что мне пришлось сделать это таким образом, хотя я думаю, что это сделано правильно. Чтобы получить маркер доступа, который я использую nuget package IdentityModel.AspNetCore, Version=3.0.0.0
Я не понимаю, почему у большего числа людей нет этой проблемы. Конечно, если ваше приложение использует данные из api, вы передаете маркер доступа, и там маркер доступа становится участником утверждений. Но если ваше приложение mvc выполняет прямой доступ к базе данных (и может быть позже извлечено в api), вам необходимо каким-то образом проверить утверждения маркера доступа. Может быть, у нас какое-то концептуальное недопонимание…
Что касается профильной службы. Я думаю, что попытка включить утверждения о маркере доступа в маркер идентификации была бы неправильным подходом. Я думаю, что это было бы даже невозможно, потому что у вас нет информации о запрашиваемых областях, когда служба вызывается для маркера идентификации.
Комментарии:
1. Это жизнеспособное решение проблемы, поэтому я приму его как правильный ответ. Спасибо, что уделили мне время. К сожалению, это не объясняет, почему области не добавляются в утверждения, и я нигде не могу найти этот ответ, только чтобы попробовать то или это, но ничего не работает. Что еще более странно, так это то, что когда запрос перенаправляется на серверную часть, api действительно добавляет области к утверждению. Оба они являются .Net Core 3.1, поэтому я подумал, что поведение по умолчанию здесь будет аналогичным. Даже отлаживая оба, я не вижу, в чем разница. Похоже, следующий шаг-это войти в код .Net Core и посмотреть.