web-dev-qa-db-fra.com

API Web .NET: définir un délai d'expiration du jeton d'actualisation différent pour différents utilisateurs

J'utilise Identity Server 3 pour authentifier et générer des jetons d'accès/de rafraîchissement pour mon client angulaire.

Je suis en train de configurer le jeton d'actualisation pour qu'il expire dans 48 heures pour mon client angulaire.

Certains utilisateurs qui utilisent mon application Angular devront être connectés pendant 100 jours sans avoir à ressaisir leurs informations d'identification. Est-il possible de définir l'expiration de mon jeton d'actualisation pour un utilisateur spécifique uniquement au lieu du client entier?

J'ai 100 utilisateurs dans ma base de données, je veux juste qu'un utilisateur spécifique n'ait pas besoin de se réauthentifier dans 100 jours, tandis que le reste devrait s'authentifier toutes les 48 heures.

Quelque chose dans le genre de:

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

Est-ce possible à réaliser? ou suis-je limité à la définition de l'expiration du jeton d'actualisation pour l'ensemble du client?

Je vous remercie

15
Mike D

Je n'ai jamais travaillé avec IdentityServer3 et je n'ai pas testé le code ci-dessous, mais je pense que le concept peut fonctionner.

Lorsque je regarde le code d'IdentityServer3, je constate que dans DefaultRefreshTokenService.CreateRefreshTokenAsync la durée de vie est définie:

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

Vous ne voudriez pas changer le code principal, mais vous devriez pouvoir écraser IRefreshTokenService avec votre propre implémentation.

Quand je prends le code de exemple de CustomUserService comme exemple:

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

Où MyDefaultRefreshTokenService est une copie de DefaultRefreshTokenService.

Afin de le compiler, ajoutez un paquet NuGet d'IdentityModel (v1.13.1) et ajoutez la classe suivante:

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

Maintenant, il y a quelques erreurs de compilation concernant les événements. Vous pouvez supprimer les événements afin de tester le code. Si cela fonctionne, vous pouvez toujours choisir de les ajouter.

Et maintenant, implémentons RefreshTokenLifetime par utilisateur. Dans votre version de RefreshTokenService, vous pouvez supprimer le code client et utiliser votre propre logique pour déterminer la durée de vie par utilisateur.

Le sujet est disponible, mais je ne sais pas s'il contient déjà suffisamment d'informations. Mais si c'est le cas, vous pouvez accéder à userManager pour lire la durée de vie à partir du magasin. Ou utilisez une alternative pour transmettre les informations de durée de vie (vous pouvez éventuellement utiliser une revendication contenant la valeur de durée de vie).

Encore une fois, je n’ai pas testé cela, mais je pense que le concept devrait fonctionner.

5
Ruard van Elburg

Je ne connais pas bien Microsoft Identity Server (le "Identity Service" auquel je fais référence dans le code ci-dessous est une implémentation personnalisée), mais vous pouvez envisager d'écrire un gestionnaire d'authentification pour intercepter le jeton dans les en-têtes HTTP, examiner un préfixe de jeton, puis décider de traiter normalement ou de permettre une durée de vie prolongée.

Dans mon cas, j'intercepte le jeton avant que JWT le traite. (Je devais le faire pour contourner une limitation du flux de travail SharePoint. Oh, SharePoint.) Voici la classe 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."));
        }
    }
}

Ensuite, dans la classe de démarrage de votre service, ajoutez du code pour décider du gestionnaire d'authentification à utiliser. La caractéristique clé ici est le 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)
        };
    });

Ajoutez une méthode d'extension à la classe AuthenticationBuilder:

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

Et des options d'authentification si vous en avez besoin.

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 est juste une classe que je définis pour associer un jeton à un nom d'utilisateur, des rôles et des revendications (le jeton du moteur de flux de travail SharePoint). Il est rempli à partir de appsettings.json. Votre classe d'options contient probablement une liste d'utilisateurs autorisés à une durée de vie étendue.

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