我使用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);
}
访问令牌从身份服务器返回,并对用户进行身份验证。然后我用这个令牌向我的WebApi发送请求。
问题是,当我检查用户是否被分配了角色时,我发现该声明不存在。
[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添加声明?非常感谢!
我自己刚刚花了一段时间来解决这个问题@leastprivilege对杨的回答的评论已经有了线索,这个回答只是对它的扩展。
这一切都取决于oAuth和OIDC规范是如何演变的,它不是IdentityServer的人工制品(这太棒了)。首先,这里对身份令牌和访问令牌之间的差异进行了相当不错的讨论:https://github.com/IdentityServer/IdentityServer3/issues/2015值得一读。
有了资源所有者流,就像你正在做的那样,你总是会得到一个访问令牌。默认情况下,根据规范,您不应该在该令牌中包含声明(请参阅上面的链接了解原因)。但是,在实践中,当你可以的时候,这是非常好的;它为您节省了在客户端和服务器上的额外工作。
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
,这显然很愚蠢,但只是想向您展示一些选项。
我发现我可以通过替换IdentityServerServiceFactory的默认IClaimsProvider来实现这一点。
客户化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中。
我不仅尝试了其他方法,还尝试了所有可能的作用域组合等。我在访问令牌中只能读取"scope"、"scope name",对于资源流,我没有添加句点的声明。
我不得不做所有这些
- 添加自定义UserServiceBase并覆盖AuthenticateLocalAsync,因为我有用户名/密码,并且我需要两者都从数据库中获取信息
- 在同一个函数中添加我需要的声明(这本身不会将声明添加到Access Token中,但您可以在各种ClaimsPrincipal参数中读取它们)
- 添加自定义DefaultClaimsProvider并覆盖GetAccessTokenClaimsSync,其中ClaimsPrincipal主题包含我之前设置的声明,我只需将它们取出并再次放入结果的声明列表中
我想最后一步可能是在自定义UserServiceBase中重写GetProfileDataAsync,但上面的操作刚刚成功,所以我不想麻烦。
一般的问题不在于如何设置索赔,而在于在哪里填充索赔。你必须在某个地方覆盖某些内容。
这对我来说很有效,因为我需要数据库中的数据,其他人应该在其他地方填写索赔。但是,它们不会因为您很好地设置了Scopes和Claims Identity Server配置而神奇地出现。
大多数答案对只字未提,其中用于正确设置索赔值。在您完成的每个特定覆盖中,当函数中传递的参数具有声明时,这些参数将附加到标识或访问令牌。
只要小心,一切都会好起来的。