web-dev-qa-db-ja.com

.NET Web API:ユーザーごとに異なる更新トークンの有効期限を設定する

Identity Server 3を使用して、angularクライアントのアクセス/更新トークンを認証および生成しています。

現在Angularクライアントの48時間で期限切れになるように更新トークンを設定しています。

My Angularアプリケーションを使用する一部のユーザーは、資格情報を再入力せずに100日間サインオンする必要があります。特定のユーザーの更新トークンの有効期限を設定することは可能ですか?クライアント全体ではなく?

私のデータベースには100人のユーザーがいますが、残りのユーザーは48時間ごとに認証を行う必要がありますが、特定の1人のユーザーだけが100日以内に再認証する必要がないようにしたいと考えています。

以下に沿ったもの:

if (user == "Super Man") {
    AbsoluteRefreshTokenLifetime = TimeSpan.FromDays(100.0).Seconds,
}

これは可能ですか?または、クライアント全体の更新トークンの有効期限の設定のみに制限されていますか?

ありがとうございました

15
Mike D

私はIdentityServer3で作業したことがなく、以下のコードもテストしていませんが、コンセプトはうまくいくと思います。

IdentityServer3のコードを見ると、 DefaultRefreshTokenService.CreateRefreshTokenAsync でライフタイムが設定されていることがわかります。

int lifetime;
if (client.RefreshTokenExpiration == TokenExpiration.Absolute)
{
    Logger.Debug("Setting an absolute lifetime: " + client.AbsoluteRefreshTokenLifetime);
    lifetime = client.AbsoluteRefreshTokenLifetime;
}
else
{
    Logger.Debug("Setting a sliding lifetime: " + client.SlidingRefreshTokenLifetime);
    lifetime = client.SlidingRefreshTokenLifetime;
}

コアコードを変更する必要はありませんが、独自の実装でIRefreshTokenServiceをオーバーライドできるはずです。

例として CustomUserService sample からコードを取得すると:

internal class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.Map("/core", coreApp =>
        {
            var factory = new IdentityServerServiceFactory()
                .UseInMemoryClients(Clients.Get())
                .UseInMemoryScopes(Scopes.Get());

            var refreshTokenService = new MyDefaultRefreshTokenService();

            // note: for the sample this registration is a singletone (not what you want in production probably)
            factory.RefreshTokenService = new Registration<IrefreshTokenService>(resolver => refreshTokenService);

MyDefaultRefreshTokenServiceは、DefaultRefreshTokenServiceのコピーです。

コンパイルするには、IdentityModel(v1.13.1)のNuGetパッケージを追加し、次のクラスを追加します。

using System;

namespace IdentityServer3.Core.Extensions
{
    internal static class DateTimeOffsetHelper
    {
        internal static Func<DateTimeOffset> UtcNowFunc = () => DateTimeOffset.UtcNow;

        internal static DateTimeOffset UtcNow
        {
            get
            {
                return UtcNowFunc();
            }
        }

        internal static int GetLifetimeInSeconds(this DateTimeOffset creationTime)
        {
            return (int)(UtcNow - creationTime).TotalSeconds;
        }
    }
}

現在、イベントに関するいくつかのコンパイルエラーがあります。コードをテストするためにイベントを削除できます。機能する場合は、いつでも追加することを選択できます。

そして今度は、ユーザーごとにRefreshTokenLifetimeを実装します。お使いのバージョンのRefreshTokenServiceでは、クライアントコードを削除し、独自のロジックを使用してユーザーごとのライフタイムを決定できます。

件名は入手可能ですが、すでに十分な情報が含まれているかどうかはわかりません。ただし、その場合は、userManagerにアクセスしてストアから存続期間を読み取ることができます。または、代替情報を使用して有効期間情報を渡します(おそらく、有効期間値を含むクレームを使用できます)。

繰り返しますが、これはテストしませんでしたが、コンセプトは機能するはずです。

5

考慮事項

たとえば、スライドセッションを検討してください。スライディングセッションでは、ユーザーが行ったすべての認証済みアクションを使用して、有効期間が短い新しいトークンを送信します。 ユーザーがアクティブである限り、ユーザーは認証されたままです(たとえば、トークン管理の実装が必要ですが、有効期限が切れる前にユーザーの操作が必要です)。ユーザーが期限切れのトークンを送信する場合、それは彼がしばらく非アクティブであることを意味します。

JWTの仕組みを見てみましょう

JWT snapshot

JWTは主に次の場合に適しています。

  • server-to-serverまたはclient-to-server(モバイルアプリなど)をサポートする必要があるAPIサービスを構築する場合またはシングルページアプリ(SPA))通信、APIトークンとしてJWTを使用することは非常に賢いアイデアです(クライアントは限られた範囲で頻繁にリクエストを行い、通常、認証データはユーザーにあまり依存せずにステートレスな方法で永続化できます)データ)。
  • リクエストに関与する3人以上の関係者が必要なタイプのサービスを構築している場合、JWTも役立ちます。
  • ユーザーフェデレーション(シングルサインオンやOpenID Connectなど)を使用している場合、サードパーティを介してユーザーのIDを検証する方法が必要になるため、JWTが重要になります。

jwtsをセッショントークンとして使用することをやめる

したがって、セッションでのJWTの使用を停止 、ほとんどの場合、セッショントークンとしてJWTを使用することはお勧めできません

可能な解決策

JWTを更新する場合、 JWT更新トークンと.NET Core は、独自のコードと JWT(JSON Web Token)内の説明を実装するのに役立つ場合があります。 は、作業シナリオを設計するためのガイドです。操作を更新する前に、目的のユーザーを検査する必要があります。

Handle Refresh Token Using ASP.NET Core 2.0 And JSON Web Token の別の実装が見つかりました。

私はMicrosoftのIdentity Serverに精通していません(以下のコードで参照している「Identity Service」はカスタム実装です)が、HTTPヘッダーのトークンをインターセプトするための認証ハンドラーの作成を検討し、トークンのプレフィックスを調べてから、正常に処理するか、寿命を延ばすかを決定します。

私の場合、JWTがトークンを処理する前にトークンをインターセプトします。 (私はSharePointワークフローの制限を回避するためにこれを行わなければなりませんでした。ああ、SharePointです。)これがAuthenticationHandlerクラスです:

using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;


namespace CompanyName.Core2.Application.Middleware
{
    [UsedImplicitly]
    public class AuthenticationHandler : AuthenticationHandler<AuthenticationOptions>
    {
        public const string AuthenticationScheme = "CompanyName Token";
        [UsedImplicitly] public const string HttpHeaderName = "Authorization";
        [UsedImplicitly] public const string TokenPrefix = "CompanyName ";


        public AuthenticationHandler(IOptionsMonitor<AuthenticationOptions> Options, ILoggerFactory Logger, UrlEncoder Encoder, ISystemClock Clock)
            : base(Options, Logger, Encoder, Clock)
        {
        }


        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.TryGetValue(HttpHeaderName, out StringValues authorizationValues))
            {
                // Indicate failure.
                return await Task.FromResult(AuthenticateResult.Fail($"{HttpHeaderName} header not found."));
            }
            string token = authorizationValues.ToString();
            foreach (AuthenticationIdentity authenticationIdentity in Options.Identities)
            {
                if (token == $"{TokenPrefix}{authenticationIdentity.Token}")
                {
                    // Authorization token is valid.
                    // Create claims identity, add roles, and add claims.
                    ClaimsIdentity claimsIdentity = new ClaimsIdentity(AuthenticationScheme);
                    claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, authenticationIdentity.Username));
                    foreach (string role in authenticationIdentity.Roles)
                    {
                        claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role));
                    }
                    foreach (string claimType in authenticationIdentity.Claims.Keys)
                    {
                        string claimValue = authenticationIdentity.Claims[claimType];
                        claimsIdentity.AddClaim(new Claim(claimType, claimValue));
                    }
                    // Create authentication ticket and indicate success.
                    AuthenticationTicket authenticationTicket = new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), Scheme.Name);
                    return await Task.FromResult(AuthenticateResult.Success(authenticationTicket));
                }
            }
            // Indicate failure.
            return await Task.FromResult(AuthenticateResult.Fail($"Invalid {HttpHeaderName} header."));
        }
    }
}

次に、サービスのStartupクラスに、使用する認証ハンドラーを決定するコードを追加します。ここでの重要な機能はForwardDefaultSelectorです。

public void ConfigureServices(IServiceCollection Services)
{
    // Require authentication token.
    // Enable CompanyName token for SharePoint workflow client, which cannot pass HTTP headers > 255 characters (JWT tokens are > 255 characters).
    // Enable JWT token for all other clients.  The JWT token specifies the security algorithm used when it was signed (by Identity service).
    Services.AddAuthentication(AuthenticationHandler.AuthenticationScheme).AddCompanyNameAuthentication(Options =>
    {
        Options.Identities = Program.AppSettings.AuthenticationIdentities;
        Options.ForwardDefaultSelector = HttpContext =>
        {
            // Forward to JWT authentication if CompanyName token is not present.
            string token = string.Empty;
            if (HttpContext.Request.Headers.TryGetValue(AuthenticationHandler.HttpHeaderName, out StringValues authorizationValues))
            {
                token = authorizationValues.ToString();
            }
            return token.StartsWith(AuthenticationHandler.TokenPrefix)
                ? AuthenticationHandler.AuthenticationScheme
                : JwtBearerDefaults.AuthenticationScheme;
        };
    })
    .AddJwtBearer(Options =>
    {
        Options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Program.AppSettings.ServiceOptions.TokenSecret)),
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = true,
            ClockSkew = TimeSpan.FromMinutes(_clockSkewMinutes)
        };
    });

AuthenticationBuilderクラスに拡張メソッドを追加します。

public static AuthenticationBuilder AddCompanyNameAuthentication(this AuthenticationBuilder AuthenticationBuilder, Action<AuthenticationOptions> ConfigureOptions = null)
{
    return AuthenticationBuilder.AddScheme<AuthenticationOptions, AuthenticationHandler>(AuthenticationHandler.AuthenticationScheme, ConfigureOptions);
}

必要に応じて、認証オプション。

using JetBrains.Annotations;
using Microsoft.AspNetCore.Authentication;


namespace CompanyName.Core2.Application.Middleware
{
    public class AuthenticationOptions : AuthenticationSchemeOptions
    {
        [UsedImplicitly]
        public AuthenticationIdentities Identities { get; [UsedImplicitly] set; }


        public AuthenticationOptions()
        {
            Identities = new AuthenticationIdentities();
        }
    }
}

AuthenticationIdentitiesは、トークンをユーザー名、ロール、およびクレーム(SharePointワークフローエンジンのトークン)に関連付けるために定義したクラスです。 appsettings.jsonから読み込まれます。ほとんどの場合、オプションクラスには、有効期間の延長が許可されているユーザーのリストが含まれます。

using System.Collections.Generic;
using JetBrains.Annotations;


namespace CompanyName.Core2.Application.Middleware
{
    public class AuthenticationIdentity
    {
        public string Token { get; [UsedImplicitly] set; }
        public string Username { get; [UsedImplicitly] set; }
        [UsedImplicitly] public List<string> Roles { get; [UsedImplicitly] set; }
        [UsedImplicitly] public Dictionary<string, string> Claims { get; [UsedImplicitly] set; }


        public AuthenticationIdentity()
        {
            Roles = new List<string>();
            Claims = new Dictionary<string, string>();
        }
    }
}
0
Erik Madsen