Как добавить заявки на токен доступа, полученный от IdentityServer3, используя поток владельца ресурса с клиентом javascript
Я использую поток владельца ресурса с IdentityServer3 и отправляю запрос на получение токена конечной точке токена идентификационного сервера с именем пользователя и паролем в javascript, как показано ниже:
function getToken() {
var uid = document.getElementById("username").value;
var pwd = document.getElementById("password").value;
var xhr = new XMLHttpRequest();
xhr.onload = function (e) {
console.log(xhr.status);
console.log(xhr.response);
var response_data = JSON.parse(xhr.response);
if (xhr.status === 200 && response_data.access_token) {
getUserInfo(response_data.access_token);
getValue(response_data.access_token);
}
}
xhr.open("POST", tokenUrl);
var data = {
username: uid,
password: pwd,
grant_type: "password",
scope: "openid profile roles",
client_id: 'client_id'
};
var body = "";
for (var key in data) {
if (body.length) {
body += "&";
}
body += key + "=";
body += encodeURIComponent(data[key]);
}
xhr.setRequestHeader("Authorization", "Basic " + btoa(client_id + ":" + client_secret));
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(body);
}
Токен доступа возвращается с сервера идентификации, и пользователь проходит аутентификацию. Затем я использую этот токен для отправки запроса на мой веб-интерфейс.
Проблема в том, что когда я проверяю, назначена ли пользователю роль, я обнаруживаю, что претензии не существует.
[Authorize]
// GET api/values
public IEnumerable<string> Get()
{
var id = RequestContext.Principal as ClaimsPrincipal;
bool geek = id.HasClaim("role", "Geek"); // false here
bool asset_mgr = id.HasClaim("role", "asset_manager"); // false here
return new string[] { "value1", "value2" };
}
Вот как определяется клиент на сервере идентификации.
new Client
{
ClientName = "Client",
ClientId = "client_id",
Flow = Flows.ResourceOwner,
RequireConsent = false,
AllowRememberConsent = false,
AllowedScopes = new List<string>
{
"openid",
"profile",
"roles",
"sampleApi"
},
AbsoluteRefreshTokenLifetime = 86400,
SlidingRefreshTokenLifetime = 43200,
RefreshTokenUsage = TokenUsage.OneTimeOnly,
RefreshTokenExpiration = TokenExpiration.Sliding,
ClientSecrets = new List<Secret>
{
new Secret("4C701024-0770-4794-B93D-52B5EB6487A0".Sha256())
},
},
и вот как определяется пользователь:
new InMemoryUser
{
Username = "bob",
Password = "secret",
Subject = "1",
Claims = new[]
{
new Claim(Constants.ClaimTypes.GivenName, "Bob"),
new Claim(Constants.ClaimTypes.FamilyName, "Smith"),
new Claim(Constants.ClaimTypes.Role, "Geek"),
new Claim(Constants.ClaimTypes.Role, "Foo")
}
}
Как я могу добавить претензии к access_token в этом случае? Большое спасибо!
3 ответа
Я просто потратил некоторое время, чтобы понять это сам. Комментарий @lessprivilege к ответу Янга имел ключ, этот ответ только расширяется.
Все зависит от того, как эволюционировали спецификации oAuth и OIDC, это не артефакт IdentityServer (что потрясающе). Во-первых, довольно приличное обсуждение различий между токенами идентификации и токенами доступа: https://github.com/IdentityServer/IdentityServer3/issues/2015 которое стоит прочитать.
С потоком Resource Owner, как и вы, вы всегда получите токен доступа. По умолчанию и согласно спецификации, вы не должны включать претензии в этот токен (см. Ссылку выше, чтобы узнать почему). Но на практике очень приятно, когда ты можешь; это экономит ваши дополнительные усилия как на клиенте, так и на сервере.
Leastprivilege имеет в виду, что вам нужно создать область видимости, что-то вроде этого:
new Scope
{
Name = "member",
DisplayName = "member",
Type = ScopeType.Resource,
Claims = new List<ScopeClaim>
{
new ScopeClaim("role"),
new ScopeClaim(Constants.ClaimTypes.Name),
new ScopeClaim(Constants.ClaimTypes.Email)
},
IncludeAllClaimsForUser = true
}
И тогда вам нужно запросить эту область, когда вы запрашиваете токен. Т.е. твоя линияscope: "openid profile roles",
должен измениться на scope: "member",
(ну, я говорю, что - насколько я вижу, области играют здесь двойную роль - они также являются формой контроля, то есть клиент запрашивает определенные области и может быть отклонен, если это не разрешено, но это другая тема).
Обратите внимание на важную строку, которая ускользнула от меня на некоторое время, Type = ScopeType.Resource
(потому что токены доступа предназначены для управления доступом к ресурсам). Это означает, что он будет применяться к токенам доступа, и указанные требования будут включены в токен (я думаю, возможно, против спецификации, но замечательно).
Наконец, в моем примере я включил как некоторые конкретные утверждения, так и IncludeAllClaimsForUser
что, очевидно, глупо, но просто хотел показать вам несколько вариантов.
Я нахожу, что могу этого добиться, заменив IClaimsProvider по умолчанию на IdentityServerServiceFactory.
Cusomized IClaimsProvider, как показано ниже:
public class MyClaimsProvider : DefaultClaimsProvider
{
public MaccapClaimsProvider(IUserService users) : base(users)
{
}
public override Task<IEnumerable<Claim>> GetAccessTokenClaimsAsync(ClaimsPrincipal subject, Client client, IEnumerable<Scope> scopes, ValidatedRequest request)
{
var baseclaims = base.GetAccessTokenClaimsAsync(subject, client, scopes, request);
var claims = new List<Claim>();
if (subject.Identity.Name == "bob")
{
claims.Add(new Claim("role", "super_user"));
claims.Add(new Claim("role", "asset_manager"));
}
claims.AddRange(baseclaims.Result);
return Task.FromResult(claims.AsEnumerable());
}
public override Task<IEnumerable<Claim>> GetIdentityTokenClaimsAsync(ClaimsPrincipal subject, Client client, IEnumerable<Scope> scopes, bool includeAllIdentityClaims, ValidatedRequest request)
{
var rst = base.GetIdentityTokenClaimsAsync(subject, client, scopes, includeAllIdentityClaims, request);
return rst;
}
}
Затем замените IClaimsProvider следующим образом:
// custom claims provider
factory.ClaimsProvider = new Registration<IClaimsProvider>(typeof(MyClaimsProvider));
В результате, когда запрос на токен доступа отправляется на конечную точку токена, заявки добавляются в access_token.
Мало того, что я пробовал другие методы, я пробовал все возможные комбинации областей и т. Д. Все, что я мог прочитать в маркере доступа, это "область действия", "имя области действия", для потока ресурсов не было никаких претензий, которые я добавил.
Я должен был сделать все это
- Добавьте пользовательскую UserServiceBase и переопределите AuthenticateLocalAsync, поскольку у меня есть имя пользователя и пароль, и мне нужно и то, и другое для извлечения данных из базы данных.
- Добавьте утверждения, которые мне нужны, в той же функции (это само по себе не добавит утверждения в Access Token, однако вы сможете прочитать их в различных параметрах ClaimsPrincipal)
- Добавьте пользовательский DefaultClaimsProvider и переопределите GetAccessTokenClaimsAsync, где тема ClaimsPrincipal содержит ранее установленные утверждения, я просто извлекаю их и снова помещаю в список утверждений для результата.
Я предполагаю, что этот последний шаг может быть сделан с переопределением GetProfileDataAsync в пользовательской UserServiceBase, но вышеприведенное просто сработало, поэтому я не хотел беспокоиться.
Общая проблема не в том, как устанавливать претензии, а в том, где вы их заполняете. Вы должны где-то переопределить.
Это сработало для меня, так как мне нужны были данные из базы данных, кто-то другой должен заполнять заявки в другом месте. Но они не появятся волшебным образом только потому, что вы правильно настроили конфигурации Scopes и Claims Identity Server.
В большинстве ответов не сказано ни слова о том, где правильно установить значения претензий. В каждом конкретном переопределении, которое вы сделали, переданные параметры, когда они имеют утверждения, в функции присоединяются к токену идентификации или доступа.
Просто позаботься об этом, и все будет хорошо.