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

时间:2018-10-17 00:44:00

标签: c# .net jwt token identityserver3

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

我当前将Angular Client的刷新令牌设置为在48小时内过期。

使用我的Angular应用程序的某些用户将需要连续100天登录,而不必重新输入其凭据,是否可以仅为特定用户而不是整个客户端设置我的刷新令牌的有效期?

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

类似的东西:

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

这有可能实现吗?还是只为整个客户端设置“刷新令牌过期”?

谢谢

3 个答案:

答案 0 :(得分:5)

我从没使用过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来从存储中读取生存期。或使用其他方法传递寿命信息(也许您可以使用包含寿命值的声明)。

再次,我没有对此进行测试,但是我认为这个概念应该可行。

答案 1 :(得分:3)

注意事项

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

让我们看看 JWT的工作方式

JWT snapshot

JWT主要适用于以下情况:

  • 在构建需要支持的API服务的情况下 使用JWT作为API令牌的服务器到服务器客户端到服务器通信(例如移动应用或单页应用(SPA))是一种 非常聪明的主意(客户会经常发出请求, 范围有限,通常可以将身份验证数据保留在 无需过多依赖用户数据的无状态方式。
  • 如果您要构建需要三种或三种以上服务的任何类型的服务 参与请求的各方,JWT也会很有用。
  • 如果您使用的是用户联盟(单点登录和 OpenID Connect),JWT变得很重要,因为您需要一种方法 通过第三方验证用户的身份。

stop using jwts as session tokens

有更多说明

因此, Stop using JWT for sessions,在大多数情况下,使用JWT作为会话令牌是一个坏主意。

可能的解决方案

对于刷新JWT,JWT refresh tokens and .NET Core可能对实现自己的代码很有用。JWT (JSON Web Token) automatic prolongation of expiration内的描述可指导您设计可行的方案。您需要在刷新操作之前检查所需的用户。

我在Handle Refresh Token Using ASP.NET Core 2.0 And JSON Web Token上为您找到了另一个实现,也许有用。

答案 2 :(得分:0)

我不熟悉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>();
        }
    }
}