web-dev-qa-db-ja.com

javascriptクライアントでリソース所有者フローを使用してIdentityServer3から取得するトークンにアクセスするためのクレームを追加する方法

IdentityServer3でリソース所有者フローを使用し、以下のようにjavascriptのユーザー名とパスワードを使用してgetトークン要求をIDサーバートークンエンドポイントに送信します。

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

アクセストークンがIDサーバーから返され、ユーザーが認証されます。次に、このトークンを使用して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" };
    }

IDサーバーでクライアントを定義する方法は次のとおりです。

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にクレームを追加するにはどうすればよいですか?どうもありがとう!

9
Yang Zhang

私はこれを自分で理解するのに少し時間を費やしました。ヤンの答えに対する@leastprivilegeのコメントには手がかりがあり、この答えはそれを拡張しているだけです。
それはすべてoAuthとOIDC仕様がどのように進化したかによるものであり、IdentityServerのアーティファクトではありません(これは素晴らしいです)。まず、ここでの違いについてかなりまともな議論があります。 IDトークンとアクセストークン: 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の両方を含めましたが、これは明らかにばかげていますが、いくつかのオプションを示したかっただけです。

15
Frans

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に追加されます。

2
Yang Zhang

他の方法を試しただけでなく、スコープなどの可能なすべての組み合わせを試しました。アクセストークンで読み取れるのは「スコープ」、「スコープ名」だけでした。リソースフローの場合、期間を追加したクレームはありませんでした。

私はこれをすべてしなければなりませんでした

  1. カスタムUserServiceBaseを追加し、AuthenticateLocalAsyncをオーバーライドします。これは、ユーザー名とパスワードがあり、データベースから取得するために両方が必要なためです。
  2. 同じ関数で必要なクレームを追加します(これ自体はアクセストークンにクレームを追加しませんが、さまざまなClaimsPrincipalパラメーターでそれらを読み取ることができます)
  3. カスタムDefaultClaimsProviderを追加し、GetAccessTokenClaimsAsyncをオーバーライドします。ここで、ClaimsPrincipalサブジェクトには以前に設定したクレームが含まれています。それらを取り出して、結果のクレームのリストに再度入れます。

この最後のステップは、カスタムUserServiceBaseのGetProfileDataAsyncをオーバーライドして実行される可能性があると思いますが、上記はうまく機能したので、気にしませんでした。

一般的な問題は、クレームをどのように設定するかではなく、どこに入力するかです。どこかで何かをオーバーライドする必要があります。

データベースからのデータが必要だったので、これは私にとってはうまくいきました。他の誰かがクレームを他の場所に入力する必要があります。ただし、スコープとクレームIDサーバーの構成を適切に設定したからといって、魔法のように表示されることはありません。

ほとんどの回答は、クレーム値を適切に設定するためのwhereについての言葉ではないと言っています。実行した特定のオーバーライドごとに、関数で渡されたパラメーターは、クレームがある場合、ID またはアクセストークンにアタッチされます。

気をつければ大丈夫です。

0
alex.peter