繁体   English   中英

.NET Web API:为不同的用户设置不同的刷新令牌到期时间

[英].NET Web API: Set a different Refresh Token Expiration time for different users

我正在使用Identity Server 3对我的角客户端进行身份验证并生成访问/刷新令牌。

我目前正在为我的Angular客户端设置刷新令牌在48小时后到期。

使用我的Angular应用程序的一些用户需要连续100天签名,而不必重新输入他们的凭据,是否可以仅为特定用户而不是整个客户端设置刷新令牌的到期时间?

我的数据库中有100个用户,我只希望一个特定用户在100天内不需要重新进行身份验证,而其他用户应该每48小时进行一次身份验证。

有点像:

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

这有可能实现吗? 或者我是否仅限于为整个客户端设置刷新令牌过期?

谢谢

我从未使用过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示例中获取代码作为示例时:

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以从商店中读取生命周期。 或使用替代方法传递生命周期信息(也许您可以使用包含生命周期值的声明)。

同样,我没有对此进行测试,但我认为这个概念应该有效。

注意事项

例如,考虑滑动会话。 使用滑动会话,您将发送一个新的短期令牌,其中包含用户进行的每个经过身份验证的操作。 只要用户处于活动状态,他就会保持身份验证 (例如,它需要在到期间隔之前进行用户交互,尽管它需要令牌管理实现)。 如果用户发送过期令牌,则表示他已暂停一段时间。

让我们看看JWT的工作原理

JWT快照

JWT主要适用于以下情况:

  • 如果构建需要支持服务器到服务器客户端到服务器 (如移动应用程序或单页面应用程序(SPA))通信的API服务,使用JWT作为API令牌是一个非常聪明的想法(客户将在范围有限的情况下频繁地发出请求,并且通常认证数据可以以无状态方式持久存在而不过度依赖用户数据。
  • 如果您正在构建任何类型的服务,您需要三个或更多方参与请求,那么JWT也很有用。
  • 如果您正在使用用户联合(例如单点登录和OpenID Connect),JWT变得很重要,因为您需要一种方法来通过第三方验证用户的身份。

停止使用jwts作为会话令牌时的更多说明

所以停止使用JWT进行会话 ,在大多数情况下使用JWT作为会话令牌是个坏主意

可能解决方案

对于刷新JWT, JWT刷新令牌和.NET Core可能对实现您自己的代码很有用.JWT内部的描述(JSON Web令牌)自动延长到期时间指导您设计工作场景。 您需要在刷新操作之前检查所需的用户。

我找到了另一个使用ASP.NET Core 2.0和JSON Web Token的Handle Refresh Token实现,可能很有用。

我不熟悉Microsoft的Identity Server(我在下面的代码中引用的“身份服务”是一个自定义实现),但您可以考虑编写一个身份验证处理程序来拦截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>();
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM