IdentityServer4 PersistedGrantStore:刷新令牌存储异常排查
看起来你遇到的核心问题是:Refresh Token虽然被返回给客户端,但没有正确存入你的自定义IPersistedGrantStore,导致刷新时系统找不到对应的记录。结合日志信息,我帮你梳理下关键排查点和修复方案:
1. 先明确IdentityServer4存储Refresh Token的逻辑
IdentityServer4中,Refresh Token是作为PersistedGrant对象存储的,它的Type字段值为"refresh_token"。特别要注意的是:
- DefaultRefreshTokenStore会对原始的Refresh Token值做SHA-256哈希处理,将哈希结果作为PersistedGrant的
Key - 原始的Refresh Token字符串会存在
PersistedGrant的Data字段中 - 查询Refresh Token时,系统会把客户端传入的Refresh Token值哈希后,用这个哈希值去匹配PersistedGrant的
Key
你的日志里显示:
Grant retrieved: /jjI7S51px7ApIt+zVxiNDOUe5Y16kYfNTSp2f37WnU=
DefaultRefreshTokenStore -> refresh_token grant with value: 4cadcc4c0d5b7e77e78e353a773c838d7f66e48507ea5303f9349c86555ed896 not found in store.
这说明系统用哈希后的Refresh Token值(4cadcc4c0d5b7e77e78e353a773c838d7f66e48507ea5303f9349c86555ed896)去查询,但你的数据库里没有这个Key的记录。
2. 排查你的IPersistedGrantStore实现
检查StoreAsync方法
你需要确保这个方法没有过滤掉Type = "refresh_token"的PersistedGrant对象。很多人会不小心只处理access_token类型,导致Refresh Token被遗漏。
错误示例(过滤了refresh_token):
public async Task StoreAsync(PersistedGrant grant) { // 错误:只存储access token,忽略了refresh token if (grant.Type != "access_token") return; // 存入数据库的逻辑... }
正确实现应该完整存储所有类型的PersistedGrant:
public async Task StoreAsync(PersistedGrant grant) { // 直接将传入的grant对象完整存入数据库,不要做类型过滤 var existing = await _context.PersistedGrants.FirstOrDefaultAsync(g => g.Key == grant.Key); if (existing != null) { _context.PersistedGrants.Remove(existing); } _context.PersistedGrants.Add(grant); await _context.SaveChangesAsync(); }
检查GetAsync方法
确保这个方法是根据传入的key参数(也就是哈希后的Refresh Token值)去精确匹配PersistedGrant的Key字段,而不是去匹配Data字段(原始Refresh Token)。
正确示例:
public async Task<PersistedGrant> GetAsync(string key) { // 根据key(哈希值)查询对应的PersistedGrant return await _context.PersistedGrants.FirstOrDefaultAsync(g => g.Key == key); }
3. 验证数据库记录
直接查询你的PersistedGrants表,看看是否存在Type = "refresh_token"的记录:
- 如果没有:说明
StoreAsync方法根本没存储Refresh Token,回到第一步检查是否有类型过滤逻辑 - 如果有:对比记录的
Key是否和日志中找不到的那个哈希值一致,如果不一致,说明你在存储时篡改了Key值,要确保完全保留IdentityServer传入的PersistedGrant对象的所有字段
4. 额外确认客户端配置
虽然你已经拿到了Refresh Token,但还是可以确认下客户端的AllowOfflineAccess是否设置为true,这是返回Refresh Token的前提条件:
new Client { ClientId = "your-client-id", // 其他配置... AllowOfflineAccess = true, AllowedScopes = { "openid", "profile", "api1", IdentityServerConstants.StandardScopes.OfflineAccess } }
按照以上步骤排查,应该能解决Refresh Token未存储导致的刷新失败问题。
内容的提问来源于stack exchange,提问作者Bob W

