建议黑名单有一个最大的弊端是每次请求都需要对服务器进行访问,会对服务器端造成很大的请求压力,而实际请求数据中99%都是正常访问,对于可疑的请求我们才需要进行服务器端验证,所以我们要在客户端校验出可疑的请求再提交到服务器校验,可以在Claim里增加客户端IP信息,当请求的客户端IP和Token里的客户端IP不一致时,我们标记为可疑Token,这时候再发起Token校验请求,校验Token是否过期,后续流程和简易黑名单模式完成一致。
实现方式
此种方式需要增加Token生成的Claim,增加自定义的ip的Claim字段,然后再重写验证方式。
优点
可以有效的减少服务器端压力
缺点
不好维护黑名单列表
3、强化白名单模式通常不管使用客户端、密码、混合模式等方式登录,都可以获取到有效的Token,这样会造成签发的不同Token可以重复使用,且很难把这些历史的Token手工加入黑名单里,防止被其他人利用。那如何保证一个客户端同一时间点只有一个有效Token呢?我们只需要把最新的Token加入白名单,然后验证时直接验证白名单,未命中白名单校验失败。校验时使用策略黑名单模式,满足条件再请求验证,为了减轻认证服务器的压力,可以根据需求在本地缓存一定时间(比如10分钟)。
实现方式
此种方式需要重写Token生成方式,重写自定义验证方式。
优点
服务器端请求不频繁,验证块,自动管理黑名单。
缺点
实现起来比较改造的东西较多
综上分析后,为了网关的功能全面和性能,建议采用强化白名单模式来实现强制过期策略。
四、强制过期的实现1.增加白名单功能
为了增加强制过期功能,我们需要在配置文件里标记是否开启此功能,默认设置为不开启。
/// <summary> /// 金焰的世界 /// 2018-12-03 /// 配置存储信息 /// </summary> public class DapperStoreOptions { /// <summary> /// 是否启用自定清理Token /// </summary> public bool EnableTokenCleanup { get; set; } = false; /// <summary> /// 清理token周期(单位秒),默认1小时 /// </summary> public int TokenCleanupInterval { get; set; } = 3600; /// <summary> /// 连接字符串 /// </summary> public string DbConnectionStrings { get; set; } /// <summary> /// 是否启用强制过期策略,默认不开启 /// </summary> public bool EnableForceExpire { get; set; } = false; /// <summary> /// Redis缓存连接 /// </summary> public List<string> RedisConnectionStrings { get; set; } }然后重写Token生成策略,增加白名单功能,并使用Redis存储白名单。白名单的存储的Key格式为clientId+sub+amr,详细实现代码如下。
using Czar.IdentityServer4.Options; using IdentityModel; using IdentityServer4.ResponseHandling; using IdentityServer4.Services; using IdentityServer4.Stores; using IdentityServer4.Validation; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; namespace Czar.IdentityServer4.ResponseHandling { public class CzarTokenResponseGenerator : TokenResponseGenerator { private readonly DapperStoreOptions _config; private readonly ICache<CzarToken> _cache; public CzarTokenResponseGenerator(ISystemClock clock, ITokenService tokenService, IRefreshTokenService refreshTokenService, IResourceStore resources, IClientStore clients, ILogger<TokenResponseGenerator> logger, DapperStoreOptions config, ICache<CzarToken> cache) : base(clock, tokenService, refreshTokenService, resources, clients, logger) { _config = config; _cache = cache; } /// <summary> /// Processes the response. /// </summary> /// <param>The request.</param> /// <returns></returns> public override async Task<TokenResponse> ProcessAsync(TokenRequestValidationResult request) { var result = new TokenResponse(); switch (request.ValidatedRequest.GrantType) { case OidcConstants.GrantTypes.ClientCredentials: result = await ProcessClientCredentialsRequestAsync(request); break; case OidcConstants.GrantTypes.Password: result = await ProcessPasswordRequestAsync(request); break; case OidcConstants.GrantTypes.AuthorizationCode: result = await ProcessAuthorizationCodeRequestAsync(request); break; case OidcConstants.GrantTypes.RefreshToken: result = await ProcessRefreshTokenRequestAsync(request); break; default: result = await ProcessExtensionGrantRequestAsync(request); break; } if (_config.EnableForceExpire) {//增加白名单 var token = new CzarToken(); string key = request.ValidatedRequest.Client.ClientId; var _claim = request.ValidatedRequest.Subject?.FindFirst(e => e.Type == "sub"); if (_claim != null) { //提取amr var amrval = request.ValidatedRequest.Subject.FindFirst(p => p.Type == "amr"); if (amrval != null) { key += amrval.Value; } key += _claim.Value; } //加入缓存 if (!String.IsNullOrEmpty(result.AccessToken)) { token.Token = result.AccessToken; await _cache.SetAsync(key, token, TimeSpan.FromSeconds(result.AccessTokenLifetime)); } } return result; } } }