ServiceStack:如何让TenantIdFilter优先于[Authenticate]属性执行?
你的问题核心在于ServiceStack的过滤器执行顺序:默认情况下,属性过滤器(比如[Authenticate])会先于全局请求过滤器运行,导致你的TenantId还没设置就触发了认证逻辑,进而使用了错误的数据库连接。
要让TenantIdFilter在所有认证属性之前执行,最直接的方案是使用ServiceStack提供的PreRequestFiltersAsync钩子——这是请求处理流程中最早执行的异步过滤器环节,会在所有属性过滤器、全局请求过滤器之前运行。
具体修改步骤:
- 替换原来的全局过滤器注册方式
把你当前注册全局过滤器的代码:
appHost.GlobalAsyncRequestFilters.Add(new TenantIdFilter().Invoke);
改成注册到PreRequestFiltersAsync:
appHost.PreRequestFiltersAsync.Add(new TenantIdFilter().Invoke);
- 修正TenantIdFilter中的小问题(可选但建议)
你的代码里有几处笔误,比如变量名不一致、缺少分号,修正后的完整过滤器代码如下:
public class TenantIdFilter { public async Task Invoke(IRequest req, IResponse res, object dto) { var hasTenant = dto as IHaveTenant; if (hasTenant == null) return; if (hasTenant.TenantId == Guid.Empty) { var error = DtoUtils.CreateErrorResponse(req, HttpError.BadRequest("Missing tenant id")); await res.WriteToResponse(req, error); return; } if (!req.GetSession().IsAuthenticated) { var error = DtoUtils.CreateErrorResponse(req, HttpError.Unauthorized("Unauthorized")); await res.WriteToResponse(req, error); req.Items.Remove("TenantId"); return; } req.Items.Add("TenantId", hasTenant.TenantId); } }
为什么这个方案有效?
ServiceStack的请求执行顺序是:PreRequestFilters/PreRequestFiltersAsync → 属性过滤器(如[Authenticate]) → GlobalRequestFilters/GlobalAsyncRequestFilters → 服务方法执行
通过将TenantIdFilter注册到PreRequestFiltersAsync,你就能确保TenantId在认证逻辑触发前就已经被设置到IRequest.Items中,此时GetDbConnection(IRequest request)获取的连接会包含正确的租户Search Path,认证时访问OrmLiteCacheClient的cache_entry表就不会出现找不到表的错误了。
备选方案:重写AppHost的OnPreExecuteServiceAsync方法
如果你需要更底层的控制,也可以重写AppHost的OnPreExecuteServiceAsync方法,在这里执行TenantId的设置逻辑——这个方法同样会在属性过滤器之前运行:
public override async Task OnPreExecuteServiceAsync(IRequest req, object requestDto) { await new TenantIdFilter().Invoke(req, req.Response, requestDto); await base.OnPreExecuteServiceAsync(req, requestDto); }
不过相比之下,使用PreRequestFiltersAsync更符合ServiceStack的过滤器设计模式,代码也更简洁。
内容的提问来源于stack exchange,提问作者Ludovic C

