然后定一个通用缓存方法,默认使用Redis实现。
using Czar.IdentityServer4.Options; using IdentityServer4.Services; using System; using System.Threading.Tasks; namespace Czar.IdentityServer4.Caches { /// <summary> /// 金焰的世界 /// 2019-01-11 /// 使用Redis存储缓存 /// </summary> public class CzarRedisCache<T> : ICache<T> where T : class { private const string KeySeparator = ":"; public CzarRedisCache(DapperStoreOptions configurationStoreOptions) { CSRedis.CSRedisClient csredis; if (configurationStoreOptions.RedisConnectionStrings.Count == 1) { //普通模式 csredis = new CSRedis.CSRedisClient(configurationStoreOptions.RedisConnectionStrings[0]); } else { csredis = new CSRedis.CSRedisClient(null, configurationStoreOptions.RedisConnectionStrings.ToArray()); } //初始化 RedisHelper RedisHelper.Initialization(csredis); } private string GetKey(string key) { return typeof(T).FullName + KeySeparator + key; } public async Task<T> GetAsync(string key) { key = GetKey(key); var result = await RedisHelper.GetAsync<T>(key); return result; } public async Task SetAsync(string key, T item, TimeSpan expiration) { key = GetKey(key); await RedisHelper.SetAsync(key, item, (int)expiration.TotalSeconds); } } }然后重新注入下ITokenResponseGenerator实现。
builder.Services.AddSingleton<ITokenResponseGenerator, CzarTokenResponseGenerator>(); builder.Services.AddTransient(typeof(ICache<>), typeof(CzarRedisCache<>));现在我们来测试下生成Token,查看Redis里是否生成了白名单?
Reference Token生成
客户端模式生成
密码模式生成
从结果中可以看出来,无论那种认证方式,都可以生成白名单,且只保留最新的报名单记录。
2.改造校验接口来适配白名单校验
前面介绍了认证原理后,实现校验非常简单,只需要重写下IIntrospectionRequestValidator接口即可,增加白名单校验策略,详细实现代码如下。
using Czar.IdentityServer4.Options; using Czar.IdentityServer4.ResponseHandling; using IdentityServer4.Models; using IdentityServer4.Services; using IdentityServer4.Validation; using Microsoft.Extensions.Logging; using System.Collections.Specialized; using System.Linq; using System.Threading.Tasks; namespace Czar.IdentityServer4.Validation { /// <summary> /// 金焰的世界 /// 2019-01-14 /// Token请求校验增加白名单校验 /// </summary> public class CzarIntrospectionRequestValidator : IIntrospectionRequestValidator { private readonly ILogger _logger; private readonly ITokenValidator _tokenValidator; private readonly DapperStoreOptions _config; private readonly ICache<CzarToken> _cache; public CzarIntrospectionRequestValidator(ITokenValidator tokenValidator, DapperStoreOptions config, ICache<CzarToken> cache, ILogger<CzarIntrospectionRequestValidator> logger) { _tokenValidator = tokenValidator; _config = config; _cache = cache; _logger = logger; } public async Task<IntrospectionRequestValidationResult> ValidateAsync(NameValueCollection parameters, ApiResource api) { _logger.LogDebug("Introspection request validation started."); // retrieve required token var token = parameters.Get("token"); if (token == null) { _logger.LogError("Token is missing"); return new IntrospectionRequestValidationResult { IsError = true, Api = api, Error = "missing_token", Parameters = parameters }; } // validate token var tokenValidationResult = await _tokenValidator.ValidateAccessTokenAsync(token); // invalid or unknown token if (tokenValidationResult.IsError) { _logger.LogDebug("Token is invalid."); return new IntrospectionRequestValidationResult { IsActive = false, IsError = false, Token = token, Api = api, Parameters = parameters }; } _logger.LogDebug("Introspection request validation successful."); if (_config.EnableForceExpire) {//增加白名单校验判断 var _key = tokenValidationResult.Claims.FirstOrDefault(t => t.Type == "client_id").Value; var _amr = tokenValidationResult.Claims.FirstOrDefault(t => t.Type == "amr"); if (_amr != null) { _key += _amr.Value; } var _sub = tokenValidationResult.Claims.FirstOrDefault(t => t.Type == "sub"); if (_sub != null) { _key += _sub.Value; } var _token = await _cache.GetAsync(_key); if (_token == null || _token.Token != token) {//已加入黑名单 _logger.LogDebug("Token已经强制失效"); return new IntrospectionRequestValidationResult { IsActive = false, IsError = false, Token = token, Api = api, Parameters = parameters }; } } // valid token return new IntrospectionRequestValidationResult { IsActive = true, IsError = false, Token = token, Claims = tokenValidationResult.Claims, Api = api, Parameters = parameters }; } } }然后把接口重新注入,即可实现白名单的校验功能。
builder.Services.AddTransient<IIntrospectionRequestValidator, CzarIntrospectionRequestValidator>();