首页 文章

如何使用资源所有者流与javascript客户端从IdentityServer3添加对访问令牌的声明

提问于
浏览
8

我使用IdentityServer3的资源所有者流,并使用javascript中的用户名和密码将get token请求发送到身份服务器令牌 endpoints ,如下所示:

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);
    }

访问令牌从身份服务器返回,用户通过身份验证 . 然后我使用此令牌向我的Web Api发送请求 .

问题是,当我检查用户是否被分配了角色时,我发现该声明不存在 .

[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 回答

  • 15

    我花了一段时间自己解决这个问题 . @leastprivilege 's comment on Yang'的答案有了线索,这个答案只是在扩展它 .
    它's all down to how the oAuth and OIDC specs evolved, it'不是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 ,这显然是愚蠢的,但只是想向您展示一些选项 .

  • 2

    我发现我可以通过替换IdentityServerServiceFactory的默认IClaimsProvider来实现这一点 .

    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));
    

    结果是,当访问令牌的请求被发送到令牌 endpoints 时,声明被添加到access_token .

  • 0

    不仅我尝试了其他方法,我尝试了所有可能的范围组合等 . 我在访问令牌中读到的只是“范围”,“范围名称”,对于资源流,没有声明我添加了句点 .

    我不得不做这一切

    • 添加自定义UserServiceBase并覆盖AuthenticateLocalAsync,因为我在那里有用户名/密码,我需要两者从数据库中获取东西

    • 在同一个函数中添加我需要的声明(这本身不会添加对Access Token的声明,但是你可以在各种ClaimsPrincipal参数中读取它们)

    • 添加自定义DefaultClaimsProvider并覆盖GetAccessTokenClaimsAsync,其中ClaimsPrincipal主题包含我之前设置的声明,我只是将它们取出并再次放入ølist的结果声明中 .

    我想最后一步可能会在自定义UserServiceBase中覆盖GetProfileDataAsync,但上面的工作正常,所以我不想打扰 .

    一般问题不是如何设置声明,而是填充它们的位置 . 你必须在某处覆盖某些东西 .

    这对我有用,因为我需要数据库中的数据,其他人应该填写其他地方的声明 . 但它们不会神奇地出现只是因为你很好地设置了Scopes和Claims Identity Server配置 .

    大多数答案都没有说明如何正确设置索赔值 . 在您完成的每个特定覆盖中,传递的参数,当它们具有声明时,在函数中附加到身份 or 访问令牌 .

    只需照顾好一切,一切都会好起来的 .

相关问题