Spring Boot调用TenantAccessor.getCurrentTenant()报TenantNotAvailableException求助
看起来你遇到的问题核心是RequestContext没有在请求处理流程中被正确初始化,导致SAP Cloud SDK的TenantAccessor无法从携带Bearer令牌的请求中提取租户信息。同时,@RequestScope的尝试因为应用启动时(entityManagerFactory初始化阶段)不存在请求上下文而失败。下面是具体的分析和解决方案:
问题原因拆解
- RequestContext缺失:SAP Cloud SDK的
TenantAccessor依赖于RequestContext来读取请求中的JWT令牌(包含租户信息)。如果这个上下文没有在请求进入时被初始化,即使请求带了Authorization头,SDK也无法关联到当前请求,就会抛出TenantNotAvailableException。 - @RequestScope的冲突:
entityManagerFactory是应用启动时就初始化的单例Bean,而@RequestScope的Bean只能在请求上下文存在时创建,启动阶段没有请求,自然会初始化失败。
解决方案步骤
1. 注册RequestContextFilter确保上下文初始化
首先要确保RequestContextFilter被正确注册到Spring Boot的过滤器链中,并且执行顺序在OAuth2相关过滤器之前——它负责在请求进来时创建RequestContext,把请求的JWT等信息注入到上下文中。
创建一个配置类来注册过滤器:
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.sap.cloud.sdk.cloudplatform.servlet.RequestContextFilter; @Configuration public class CloudSdkConfig { @Bean public FilterRegistrationBean<RequestContextFilter> requestContextFilter() { final FilterRegistrationBean<RequestContextFilter> filterRegistrationBean = new FilterRegistrationBean<>(); filterRegistrationBean.setFilter(new RequestContextFilter()); filterRegistrationBean.setOrder(1); // 确保在OAuth2过滤器之前执行 return filterRegistrationBean; } }
2. 调整TenantIdentifierResolver的实现
不要给TenantIdentifierResolver加@RequestScope,而是让它在**运行时(处理请求时)**从RequestContext中获取租户信息,而不是在Bean注入阶段依赖请求上下文。
示例实现:
import com.sap.cloud.sdk.cloudplatform.tenant.Tenant; import com.sap.cloud.sdk.cloudplatform.tenant.TenantAccessor; import com.sap.cloud.sdk.cloudplatform.tenant.exception.TenantNotAvailableException; import com.sap.cloud.sdk.persistence.multitenancy.TenantIdentifierResolver; public class CustomTenantIdentifierResolver implements TenantIdentifierResolver { @Override public String resolveTenantIdentifier() { try { final Tenant currentTenant = TenantAccessor.getCurrentTenant(); return currentTenant.getTenantId(); } catch (final TenantNotAvailableException e) { throw new IllegalStateException("Failed to resolve tenant identifier from request", e); } } }
这个Resolver是单例Bean,初始化时不会依赖请求上下文;只有在实际处理请求、调用resolveTenantIdentifier方法时,才会从已经初始化好的RequestContext中读取租户信息。
3. 验证OAuth2配置正确性
确保Spring Security的OAuth2配置能正确解析Bearer令牌,并且JWT中包含SAP Cloud SDK默认识别的租户字段(zid)。示例Spring Security配置:
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .oauth2ResourceServer() .jwt() .jwtAuthenticationConverter(jwtAuthenticationConverter()); } private JwtAuthenticationConverter jwtAuthenticationConverter() { final JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); // 如需自定义JWT字段映射,可在此添加逻辑,SDK默认会读取zid字段作为租户ID return converter; } }
4. 排查验证RequestContext状态
可以在你的RestController中添加一段调试代码,确认RequestContext是否被正确初始化:
import com.sap.cloud.sdk.cloudplatform.servlet.RequestContextAccessor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TenantTestController { @GetMapping("/check-context") public String checkRequestContext() { if (RequestContextAccessor.isCurrentRequestContextAvailable()) { return "RequestContext is available - tenant should be resolvable"; } else { return "NO RequestContext found - check filter configuration"; } } }
用Postman调用这个接口,如果返回"RequestContext is available",说明上下文初始化正常,后续的租户获取逻辑应该就能正常工作了。
内容的提问来源于stack exchange,提问作者Michal Holič

