ServiceStack OAuth场景下JWT刷新令牌用会话ID,如何映射至用户认证ID?
解决方案
1. 自定义JwtAuthProvider替换刷新令牌的sub声明
直接继承JwtAuthProvider并重写CreateJwtRefreshToken方法,将刷新令牌的主题(sub)设置为用户认证ID而非会话ID:
public class CustomJwtAuthProvider : JwtAuthProvider { protected override string CreateJwtRefreshToken(IRequest req, string subject, TimeSpan expiresIn) { // 从当前会话中获取用户认证ID,替换原有的会话ID作为sub var userAuthId = req.GetSession().UserAuthId; return base.CreateJwtRefreshToken(req, userAuthId, expiresIn); } }
之后在AppHost的配置中注册这个自定义Provider:
Plugins.Add(new AuthFeature(() => new CustomUserSession(), new IAuthProvider[] { new CustomJwtAuthProvider { // 配置你的JWT参数,比如AuthKey、ExpireTokensIn等 }, new YourOAuthProvider(...), // 注册你的OAuth提供程序 }));
2. 存储会话ID与用户认证ID的映射关系
如果无法修改刷新令牌的sub,可以在会话创建时保存两者的映射,后续通过会话ID反向查询用户认证ID:
- 在OAuth Provider的
OnAuthenticatedAsync事件中保存映射:
public class CustomOAuthProvider : OAuthProvider { public override async Task OnAuthenticatedAsync(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo) { await base.OnAuthenticatedAsync(authService, session, tokens, authInfo); // 用缓存存储会话ID到UserAuthId的映射,过期时间与会话一致 var cache = authService.TryResolve<ICacheClient>(); await cache.SetAsync($"SessionToUser:{session.Id}", session.UserAuthId, session.ExpiresAt); } }
- 在
IUserSessionSourceAsync实现中处理映射查询:
public class CustomUserSessionSource : IUserSessionSourceAsync { private readonly ICacheClient _cache; public CustomUserSessionSource(ICacheClient cache) => _cache = cache; public async Task<IUserSession> GetUserSessionAsync(string userAuthIdOrSessionId, IRequest req) { // 先尝试直接用传入ID作为UserAuthId查找会话 var session = await FetchSessionByUserAuthId(userAuthIdOrSessionId); if (session != null) return session; // 若找不到,尝试将传入ID作为会话ID,查询映射的UserAuthId var mappedUserAuthId = await _cache.GetAsync<string>($"SessionToUser:{userAuthIdOrSessionId}"); if (mappedUserAuthId != null) return await FetchSessionByUserAuthId(mappedUserAuthId); return null; } // 实现根据UserAuthId获取会话的业务逻辑 private async Task<IUserSession> FetchSessionByUserAuthId(string userAuthId) { // 示例:从数据库或缓存中加载用户会话 // var session = await Db.SingleByIdAsync<CustomUserSession>(userAuthId); // return session; ... } }
3. 直接通过会话ID查询会话实例
如果使用ServiceStack内置的会话存储(如Redis、内存缓存),可以直接通过会话ID获取完整会话,再从中提取用户认证ID:
public class CustomUserSessionSource : IUserSessionSourceAsync { private readonly ISessionFactory _sessionFactory; public CustomUserSessionSource(ISessionFactory sessionFactory) => _sessionFactory = sessionFactory; public async Task<IUserSession> GetUserSessionAsync(string userAuthIdOrSessionId, IRequest req) { // 优先尝试用UserAuthId查找 var session = await FetchSessionByUserAuthId(userAuthIdOrSessionId); if (session != null) return session; // 尝试用会话ID获取会话实例,再提取UserAuthId查询 var sessionFromId = await _sessionFactory.GetSessionAsync(req, userAuthIdOrSessionId); if (sessionFromId != null) return await FetchSessionByUserAuthId(sessionFromId.UserAuthId); return null; } private async Task<IUserSession> FetchSessionByUserAuthId(string userAuthId) { // 实现你的会话获取逻辑 ... } }
内容的提问来源于stack exchange,提问作者Joe Glover
相关产品推荐
相关产品推荐

