#c# #asp.net-core #oauth-2.0 #azure-active-directory #microsoft-identity-platform
#c# #asp.net-core #oauth-2.0 #azure-active-directory #microsoft-identity-platform
Вопрос:
У меня есть ASP.NET Проект Core 3.1, подобный этому примеру: войдите в систему пользователя с помощью платформы Microsoft Identity Platform в настольном приложении WPF и вызовите ASP.NET Основной веб-API.
Я использую Identity web
версию 1.0 и Azure AD, однопользовательское приложение.
Я отредактировал добавление манифеста appRoles
, поскольку запрашиваю только токен приложения, а не токен пользователя:
[... more json ...]
"appId": "<guid>",
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"description": "Accesses the application.",
"displayName": "access_as_application",
"id": "<unique guid>",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "access_as_application"
}
],
"oauth2AllowUrlPathMatching": false,
[... more json ...]
Я также включил idtyp
требование маркера доступа, чтобы указать, что это токен приложения.:
[... more json ...]
"optionalClaims": {
"idToken": [],
"accessToken": [
{
"name": "idtyp",
"source": null,
"essential": false,
"additionalProperties": []
}
],
"saml2Token": []
[... more json ...]
Следующий запрос выполняется с помощью Postman . Пожалуйста, обратите внимание на использование /.default
с областью видимости, которое упоминается в документации в отношении потока предоставления учетных данных клиента.
POST /{tenant_id}/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
scope=api:/%2{client_id}/.default
amp;client_id={client_id}
amp;grant_type=client_credentials
amp;client_secret={secret_key}
Запрос возвращает access_token
значение, которое можно просмотреть с помощью jwt.ms и выглядит примерно так, где фактические данные были заменены заполнителями по соображениям безопасности.:
{
"typ": "JWT",
"alg": "RS256",
"x5t": "[...]",
"kid": "[...]"
}.{
"aud": "api://<client_id>",
"iss": "https://sts.windows.net/<tenant_id>/",
"iat": 1601803439,
"nbf": 1601803439,
"exp": 1601807339,
"aio": "[...]==",
"appid": "<app id>",
"appidacr": "1",
"idp": "https://sts.windows.net/<tenant_id>/",
"idtyp": "app",
"oid": "<guid>",
"rh": "[..].",
"roles": [
"access_as_application"
],
"sub": "<guid>",
"tid": "<guid>",
"uti": "[...]",
"ver": "1.0"
}
I notice that the token above does not include scp
. This seem correct as this is an application token and not a user token. Instead it includes `”roles”´ as appropiate for an application token.
The access_token
can now be used as bearer in a Postman Get:
GET /api/myapi
Host: https://localhost:5001
Authorization: Bearer {access_token}
The reponse to this request is 500 internal error
. I.e. something is wrong. The access_token
looks like a corrent application token, so the error seems to be on the ASP.NET Core 3.1 controller side.
The ASP.NET Core 3.1. project hosting the custom API, has a startup.cs
which includes the following code:
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
// This is added for the sole purpose to highlight the origin of the exception.
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
if (context.Principal.Claims.All(x => x.Type != ClaimConstants.Scope)
amp;amp; context.Principal.Claims.All(y => y.Type != ClaimConstants.Scp)
amp;amp; context.Principal.Claims.All(y => y.Type != ClaimConstants.Roles))
{
// This where the exception originates from:
throw new UnauthorizedAccessException("Neither scope or roles claim was found in the bearer token.");
}
};
});
appsettings.json
Для проекта включает:
"AzureAD": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "mydomain.onmicrosoft.com",
"ClientId": "<client_id>",
"TenantId": "<tenant_id>",
"Audience": "api://<client_id>"
},
… и контроллер выглядит так:
[Authorize]
[Route("api/[controller]")]
public class MyApiController : Controller
{
[HttpGet]
public async Task<string> Get()
{
return "Hello world!";
}
}
Основная причина 500 internal error
заключается в том, что это исключение генерируется: IDW10201: Neither scope or roles claim was found in the bearer token.
exception .
Обновить:
(Пожалуйста, смотрите Ответ ниже для получения более подробной информации).
Это видео на тему «Внедрение авторизации в ваших приложениях с помощью платформы Microsoft identity platform — июнь 2020» предполагает, что недостающим элементом является этот флаг JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
, который необходимо установить, startup.cs
например:
public void ConfigureServices(IServiceCollection services)
{
// By default, the claims mapping will map clain names in the old format to accommodate older SAML applications.
//'http://schemas.microsodt.com/ws/2008/06/identity/clains/role' instead of 'roles'
// This flag ensures that the ClaimsIdentity claims collection will be build from the claims in the token
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
[...more code...]
Комментарии:
1. Используйте сниффер, такой как wireshark или fiddler, и сравните работающий Postman с нерабочим c #. Сначала проверьте версию используемого TLS. Если они совпадают, сравните заголовки в первом запросе. Сделайте c # похожим на результаты Postman.
2. Не уверен, какую помощь это окажет? Postman не работает. Я использую только Postman. Проблема не в проводе. Проблема, похоже, в том, что в access_token отсутствует некоторая информация.
3. проверьте версию TLS. Вероятно, вы получаете сообщение об ошибке, потому что используете TLS 1.0 / 1.1. Отрасль 5 лет назад решила исключить TLS 1.0 / 1.1 из-за проблем с безопасностью. В июне этого года Microsoft выпустила обновление для системы безопасности, чтобы отключить TLS 1.//1.1 на серверах. Поэтому теперь клиенту необходимо запросить TLS 1.2 / 1.3. Поскольку вы не указываете версию TLS, по умолчанию используется версия Net, которую вы используете, и версия Windows, которую вы используете. Также убедитесь, что вы используете последнюю версию API. Старые API могут использовать более старую версию TLS.
4. Я могу подтвердить, что связь использует TLS 1.2, но я все еще не понимаю, почему это имеет значение, поскольку связь между Postman и моим ASP.NET Core 3.1 api, оба из которых работают на моем локальном сервере Windows 10 bld 19042.541, работает просто отлично. Проблема не в проводе.
Ответ №1:
Это может помочь, если вы планируете не использовать встроенную область видимости или роли. Вы можете включить аутентификацию «список контроля доступа», используя мой пример для Azure B2C ниже. Вот несколько ссылок на официальную документацию.
Добавьте следующее в свою конфигурацию AD: "AllowWebApiToBeAuthorizedByACL": true
Пример:
"AzureAdB2C": {
"Instance": "https://xxx.b2clogin.com/",
"ClientId": "xxxx",
"Domain": "xxx.onmicrosoft.com",
"SignUpSignInPolicyId": "xxx",
"AllowWebApiToBeAuthorizedByACL": true
},
Что означает список ACL / Access-control:
ACL: https://en.wikipedia.org/wiki/Access-control_list
Комментарии:
1. Избегайте использования ссылок на другие ответы, вместо этого вы можете обобщить и добавить шаги, информацию или все, что необходимо для ответа на вопрос.
2. @juagicre Я не думаю, что ссылка на официальную документацию — это то же самое, что «ссылка на другие ответы». Кроме того, я думаю, что важно прочитать официальную документацию, особенно при изменении поведения безопасности ваших приложений по умолчанию. Документация имеет гораздо больше шансов быть обновленной до этого ответа SO. Наконец, я написал, как включить то, что я предложил в сообщении.
3. Это решает нашу проблему. Спасибо!
4. Это решило мою проблему!
5. Было бы неплохо, если бы в сообщении об ошибке упоминалось об этом!
Ответ №2:
В видео «Внедрение авторизации в ваших приложениях с помощью платформы Microsoft identity platform — июнь 2020 года» указывается, что недостающим элементом является этот флаг JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
, который необходимо установить, startup.cs
например:
public void ConfigureServices(IServiceCollection services)
{
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
// By default, the claims mapping will map claim names in the old format to accommodate older SAML applications.
//'http://schemas.microsodt.com/ws/2008/06/identity/clains/role' instead of 'roles'
// This flag ensures that the ClaimsIdentity claims collection will be build from the claims in the token
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
// Notice that this part is different in the video,
// however in this context the following seems to be
// the correct way of setting the RoleClaimType:
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
// The claim in the Jwt token where App roles are available.
options.TokenValidationParameters.RoleClaimType = "roles";
});
[... more code ...]
}
Альтернатива 1
Также можно установить авторизацию для всего приложения следующим образом в startup.cs
:
services.AddControllers(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireClaim("roles", "access_as_application")
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
Альтернатива 2
Также можно использовать политику, подобную этой:
services.AddAuthorization(config =>
{
config.AddPolicy("Role", policy =>
policy.RequireClaim("roles", "access_as_application"));
});
Теперь эта политика может использоваться для запроса контроллера, подобного этому:
[HttpGet]
[Authorize(Policy = "Role")]
public async Task<string> Get()
{
return "Hello world!";
}
Подробнее в документации: проверки ролей на основе политики.
Комментарии:
1. вероятно, это связано с тем, что ваши роли на самом деле не являются ролями, но утверждения пытаются добавить политику, подобную политике. RequireClaim(«роли», «access_as_application»)), а затем используйте эту политику в своем контроллере
2. Не сработало. Сделал ли это:
services.AddControllers(options => { var policy = new AuthorizationPolicyBuilder() .RequireClaim("roles", "access_as_application") .Build(); options.Filters.Add(new AuthorizeFilter(policy)); })
3. Ну, это вроде как сработало, в том смысле, что оно устанавливало требования
access_as_application
глобально для всего приложения. Однако все равно произойдет сбой, если я добавлю[Authorize(Roles = "access_as_application")]
атрибут к самому контроллеру.4. хм, странно, для меня это работает
services.AddAuthorization(config => { config.AddPolicy("Role", policy => policy.RequireClaim("roles", "access_as_application")); });
, но ваше решение тоже хорошее5. в контроллере вы должны указать не роли, а созданную вами политику
[Authorize(Policy = "Role")]
Ответ №3:
Просто добавьте DefaultMapInboundClaims в конфигурацию вашей службы API
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
}
Комментарии:
1. Если вы хотите быть полезным, объясните, почему это не работает
[Authorize(Roles = "access_as_application")]
. Вы можете увидеть подробности в моем ответе на вопрос.2. ах, извините, я изучал это поведение в проекте и не обновлял страницу, поэтому я пропустил ваши обновления
3. Sry об этом, сделал предположение, которое я не должен был делать.
Ответ №4:
Когда я получил эту ошибку «IDW10202», это было из-за этой строки кода в контроллере.
HttpContext.ValidateAppRole("MyAppRole");
(Это был единственный результат, возвращенный Google, поэтому разместите этот комментарий здесь для чьей-либо пользы. Извиняюсь, если немного не по теме.)
Ответ №5:
Причина в том, что вы делаете запрос с областью по умолчанию scope=api:/%2{client_id}/.default
, которая не включает утверждение области видимости в access_token. вы должны использовать определенную область, в которой вы зарегистрированы для своего ASP.NET Проект API Core 3.1, когда вы предоставляете этот API на портале Azure.
Комментарии:
1. Вы уверены в этом. Если я изменяю область видимости при запросе токена, вместо этого я получаю эту ошибку:
"AADSTS70011: The provided request must include a 'scope' input parameter. The provided value for the input parameter 'scope' is not valid. The scope api://{client_id}/access_as_application is not valid.rnTrace ID"
2. Вот что говорится в документации: поток предоставления учетных данных клиента и /.default (обновлено с правильной ссылкой)
3. правильно ли вы настроили свое приложение Azure для своего клиента, включая добавление разрешения api для вашей
api://{client_id}/access_as_application
области? Поскольку это странная ситуация, в которой вы access_token должны содержать утверждения области видимости или роли, а azure не выдает утверждение области видимости из-за области .default, и кажется, что у вашего приложения web api нет разрешений / ролей в azure, и поэтому утверждения ролей тоже не выдаются,4. ДА. Я обновил манифест, и теперь я действительно получаю роли, но я все еще получаю то же исключение. Пожалуйста, ознакомьтесь с обновлениями в исходном сообщении.