web-dev-qa-db-ja.com

oauth2でアクセストークンのキャッシュが悪いと見なされるのはなぜですか?

ユーザーアクセスを取り消すために、この記事をフォローしています。

http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/

ここで、ユーザーを検証した後、上記の記事に示されているように30分のライフスパンと更新トークンを1日としてaccesstokenを発行したことを検討してください。ただし、管理者が10分間でユーザーを削除し、20分間残っている場合は、この場合、そのユーザーのアクセスを取り消します。

これを行うには、更新トークンテーブルからそのユーザーエントリを削除して、それ以上のアクセストークンリクエストを禁止する必要がありますが、accesstokenの有効期限がまだ20分あるので、ユーザーは完全に間違っている保護されたリソースにアクセスできます。

したがって、私はサーバーにアクセストークンをキャッシュし、データベースに保存するためのキャッシュメカニズムを実装することを考えていました。したがって、そのユーザーが取り消されたら、キャッシュとデータベースからそのユーザーエントリを削除するだけで、保護されたリソースへのユーザーアクセスを停止できます。

しかし、以下の2つの回答は、これはoauth2の設計方法ではないことを示しています。

OAuthBearerAuthenticationのアクセストークンを取り消す

OAuth2-更新トークンによる不必要な複雑さ

だから私の質問は:

1)アクセストークンのキャッシングがリフレッシュトークンメカニズムよりも優れているとは言えず、悪いアプローチでもありますか?

私の2番目の質問は、@ Hans Z。によって与えられた以下の回答に基づいています:

これには、リソースサーバー(RS)が承認サーバー(AS)に問い合わせる必要があるため、オーバーヘッドが非常に大きくなります。

2)ユーザーのアクセスを取り消す場合、ASはユーザーを認証し、これに従ってアクセストークンを生成するためだけなので、なぜRSはASに問い合わせますか 記事

3)記事には2つのプロジェクトしかありません。

  • Authentication.api-ユーザーの認証とアクセストークンの生成
  • Resource server-[Authorize]属性を使用したaccesstokenの検証

    上記の場合、承認サーバーはどれですか?

Update:ユーザーが削除された場合、およびユーザーがログアウトしたときに更新トークンテーブルからトークンを更新する場合に、更新トークンを使用してユーザーアクセスを取り消すことにしました。ユーザーがログアウトをクリックするとすぐにログアウトするという要件の要件。

しかし、ここでの問題は私がユーザーに関連付けられている250のロールがあるため、accesstokenにロールを配置すると、accesstokenのサイズが非常に大きくなり、ヘッダーからこのような巨大なaccesstokenを渡すことができないしかし、エンドポイントが呼び出されるたびにエンドポイントのユーザーアクセスを検証するためのロールをクエリすることはできません。

これが私が直面しているもう1つの問題です。

ここには2つの異なる質問があるようです。アクセストークンについてと、役割の大きなリストについてです。

アクセストークン

OAuth2は高負荷を処理できるように設計されており、これにはいくつかのトレードオフが必要です。特に、OAuth2が一方で「リソースサーバー」と「承認サーバー」の役割を明示的に分離し、他方で「アクセストークン」と「更新トークン」を明示的に分離するのはこのためです。リクエストごとにユーザー認証を確認する必要がある場合、それは認証サーバーがシステム内のすべてのリクエストを処理できる必要があることを意味します。高負荷システムの場合、これは現実的ではありません。

OAuth2を使用すると、パフォーマンスとセキュリティの間で次のトレードオフを行うことができます:承認サーバーがアクセスを生成しますAuthorization ServerにアクセスせずにResource Serverによって検証できるトークン(Authorization Serverの存続期間中、まったくまたは少なくとも1回のみ)。これは事実上、許可情報のキャッシングです。このようにして、認証サーバーの負荷を大幅に削減できます。欠点は、キャッシングの場合と常に同じです。認証情報が停止する可能性があります。アクセストークンの有効期間を変えることで、パフォーマンスとセキュリティのバランスを調整できます。

このアプローチは、各サービスが独自のストレージを持ち、相互にアクセスしないマイクロサービスアーキテクチャを実行する場合にも役立ちます。

それでも、負荷が少なく、さまざまなテクノロジーを使用して実装されたさまざまなサービスではなく単一のリソースサーバーしかない場合でも、実際にすべてのリクエストで本格的な検証を行うことを妨げるものはありません。つまりはい、あなたはDBにアクセストークンを保存し、リソースサーバーへのすべてのアクセスでそれを確認し、ユーザーが削除されたときにすべてのアクセストークンを削除することができます。 。

ロールの大きなリスト

AFAIU OAuth2は、ユーザーロールに明示的な機能を提供していません。ロールにも使用できる「スコープ」機能があり、その典型的な実装では、250のロールには長すぎる文字列が生成されます。それでもOAuth2はアクセストークンの特定の形式を明示的に指定していないため、ロール情報をビットマスクとして保持するカスタムトークンを作成できます。 base-64エンコーディングを使用すると、6つの役割を1つの文字に取得できます(64 = 2 ^ 6)。したがって、250〜300の役割は40〜50文字で管理できます。

[〜#〜] jwt [〜#〜]

とにかくカスタムトークンが必要になる可能性があるため、 JSON Web Tokens 別名JWTに興味があるかもしれません。つまり、JWTを使用すると、カスタムの追加ペイロード(プライベートクレーム)を指定して、そこにロールのビットマスクを配置できます。

OAuth2の高度な機能(スコープなど)が本当に必要ない場合は、実際にOAuth2を一切使用せずにJWTを単独で使用できます。 JWTトークンはisisのコンテンツのみで検証されることになっていますが、ローカルDBに保存して、DBに対して追加の検証を行うこともできます(アクセスリフレッシュトークンで行う場合と同様)。


2017年12月1日更新

OWIN OAuthインフラストラクチャを使用する場合、_ AccessTokenFormat および-でOAuthBearerAuthenticationOptionsを介してカスタムフォーマッタを提供するトークンフォーマットをカスタマイズできます OAuthAuthorizationServerOptionsRefreshTokenFormatをオーバーライドすることもできます。

以下は、ロールクレームを単一のビットマスクに「圧縮」する方法を示すスケッチです。

  1. 持っているすべてのロールをリストするCustomRoles列挙を定義します
[Flags]
public enum CustomRoles
{
    Role1,
    Role2,
    Role3,

    MaxRole // fake, for convenience
}
  1. EncodeRolesメソッドとDecodeRolesメソッドを作成して、ロールのIEnumerable<string>形式と、上記で定義したCustomRolesに基づくbase64エンコードビットマスクとの間で変換します。
    public static string EncodeRoles(IEnumerable<string> roles)
    {
        byte[] bitMask = new byte[(int)CustomRoles.MaxRole];
        foreach (var role in roles)
        {
            CustomRoles roleIndex = (CustomRoles)Enum.Parse(typeof(CustomRoles), role);
            var byteIndex = ((int)roleIndex) / 8;
            var bitIndex = ((int)roleIndex) % 8;
            bitMask[byteIndex] |= (byte)(1 << bitIndex);
        }
        return Convert.ToBase64String(bitMask);
    }

    public static IEnumerable<string> DecodeRoles(string encoded)
    {
        byte[] bitMask = Convert.FromBase64String(encoded);

        var values = Enum.GetValues(typeof(CustomRoles)).Cast<CustomRoles>().Where(r => r != CustomRoles.MaxRole);

        var roles = new List<string>();
        foreach (var roleIndex in values)
        {
            var byteIndex = ((int)roleIndex) / 8;
            var bitIndex = ((int)roleIndex) % 8;
            if ((byteIndex < bitMask.Length) && (0 != (bitMask[byteIndex] & (1 << bitIndex))))
            {
                roles.Add(Enum.GetName(typeof(CustomRoles), roleIndex));
            }
        }

        return roles;
    }
  1. SecureDataFormat<AuthenticationTicket>のカスタム実装でこれらのメソッドを使用します。このスケッチを簡単にするために、ほとんどの作業を標準のOWINコンポーネントに委任し、CustomTicketSerializerを実装するだけで、別のAuthenticationTicketを作成し、標準のDataSerializers.Ticketを使用します。これは明らかに最も効率的な方法ではありませんが、何ができるかを示しています。
public class CustomTicketSerializer : IDataSerializer<AuthenticationTicket>
{

    public const string RoleBitMaskType = "RoleBitMask";
    private readonly IDataSerializer<AuthenticationTicket> _standardSerializers = DataSerializers.Ticket;

    public static SecureDataFormat<AuthenticationTicket> CreateCustomTicketFormat(IAppBuilder app)
    {
        var tokenProtector = app.CreateDataProtector(typeof(OAuthAuthorizationServerMiddleware).Namespace, "Access_Token", "v1");
        var customTokenFormat = new SecureDataFormat<AuthenticationTicket>(new CustomTicketSerializer(), tokenProtector, TextEncodings.Base64Url);
        return customTokenFormat;
    }

    public byte[] Serialize(AuthenticationTicket ticket)
    {
        var identity = ticket.Identity;
        var otherClaims = identity.Claims.Where(c => c.Type != identity.RoleClaimType);
        var roleClaims = identity.Claims.Where(c => c.Type == identity.RoleClaimType);
        var encodedRoleClaim = new Claim(RoleBitMaskType, EncodeRoles(roleClaims.Select(rc => rc.Value)));
        var modifiedClaims = otherClaims.Concat(new Claim[] { encodedRoleClaim });
        ClaimsIdentity modifiedIdentity = new ClaimsIdentity(modifiedClaims, identity.AuthenticationType, identity.NameClaimType, identity.RoleClaimType);
        var modifiedTicket = new AuthenticationTicket(modifiedIdentity, ticket.Properties);
        return _standardSerializers.Serialize(modifiedTicket);
    }

    public AuthenticationTicket Deserialize(byte[] data)
    {
        var ticket = _standardSerializers.Deserialize(data);
        var identity = ticket.Identity;
        var otherClaims = identity.Claims.Where(c => c.Type != RoleBitMaskType);
        var encodedRoleClaim = identity.Claims.SingleOrDefault(c => c.Type == RoleBitMaskType);
        if (encodedRoleClaim == null)
            return ticket;

        var roleClaims = DecodeRoles(encodedRoleClaim.Value).Select(r => new Claim(identity.RoleClaimType, r));
        var modifiedClaims = otherClaims.Concat(roleClaims);
        var modifiedIdentity = new ClaimsIdentity(modifiedClaims, identity.AuthenticationType, identity.NameClaimType, identity.RoleClaimType);
        return new AuthenticationTicket(modifiedIdentity, ticket.Properties);
    }
}
  1. Startup.csで、次のようなカスタム形式を使用するようにOWINを構成します。
var customTicketFormat = CustomTicketSerializer.CreateCustomTicketFormat(app);
OAuthBearerOptions.AccessTokenFormat = customTicketFormat;
OAuthServerOptions.AccessTokenFormat = customTicketFormat;
  1. OAuthAuthorizationServerProviderで、ユーザーに割り当てられた各ロールのClaimsIdentityClaimTypes.Roleを追加します。

  2. コントローラでは、次のような標準のAuthorizeAttributeを使用します

    [Authorize(Roles = "Role1")]
    [Route("")]
    public IHttpActionResult Get()
    

便宜と安全のために、AuthorizeAttributeクラスをサブクラス化して、ロール構成として文字列ではなくCustomRoles enumを受け入れることができます。

10
SergGr

「承認サーバーは、リソースサーバーと同じサーバーでも、別のエンティティでもかまいません。」 [RFC 6749、p。 6]

そうは言っても、そうであればキャッシュすることはできますが、トークンはリソースサーバーが理解でき、キャッシュする必要はありません。それが実装の詳細であれば、キャッシュは可能ですが、必須ではありません。

0
Igor Kroeber

更新トークンアプローチの主な利点は、データベースクエリの数を減らすことです。アクセストークンにはクレームがあり、データベースにクエリを行わなくてもトークンを信頼できるように署名されています。

アクセストークンのキャッシュは機能しますが、リクエストごとにキャッシュをクエリする必要があります。

これは、アクセストークンの有効性を確認するためのクエリの数とアクセス許可の変更のn分の遅延の間で選択する必要があるトレードオフです

複雑さが増すと、サーバーRAMにキャッシュを保存する必要があり、リストを小さく保つために失効したトークンのみを保存する必要がある場合、両方をほぼ達成できます。サーバーのインスタンスが複数ある場合は、RSとASの間で取り消されたトークンのキャッシュを同期させる必要があるため、複雑になります。

基本的に、アクセストークンが取り消されると、ASはすべてのRSにそのアクセストークンを取り消されたトークンキャッシュに追加するよう通知する必要があります。

リソース要求があるときはいつでも、RSはトークンが取り消されているかどうかをチェックし、取り消されていない場合は、リソースをサーバーにサーバーが送ります。このように、オーバーヘッドは各リクエストに存在しますが、キャッシュがメモリ内にあり、取り消されたトークンの数は有効なトークンの数と比較して非常に少ないため、大幅に削減されます。

0
Bhoju

私はあなたの質問を正しく理解し、いくつかの答えを提供できることを願っています:

1)ユーザーがログインするたびに検証を要求するようにASを開発した場合は、現金化できます。

2)@Hans Z.は、ASによるユーザーの取り消しを意味すると思います。 RSがユーザーを取り消すとき、それらがまだASによって識別されるものであるという事実は変わりません。ただし、ASがユーザーを取り消すと、IDの使用ができなくなります。

3)この記事ではおそらく、承認はRSによって行われ、ASはユーザーが誰であるかを伝えるだけの責任があり、RSはそれに基づいて承認を決定することを想定しています。

0
Majid ALSarra