如何克服间歇性 Scope 'session' is not active for the current thread for OauthClientContext?
How to overcome intermittent Scope 'session' is not active for the current thread for OauthClientContext?
我正在尝试在 Spring Boot 1.3.0 应用程序中实现 OpenId Connect 登录,在 Spring Framework 4.2.3 上使用 Spring Security 3.2.5。实现与这个问题非常相似:,除了我已经为 RequestContextFilter
.
实现了建议的 bean
@Configuration
@EnableOAuth2Client
public class OpenIdConnectConfig {
@Value("${oidc.clientId}")
private String clientId;
@Value("${oidc.clientSecret}")
private String clientSecret;
@Value("${oidc.accessTokenUrl}")
private String accessTokenUri;
@Value("${oidc.userAuthorizationUri}")
private String userAuthorizationUri;
@Value("${oidc.redirectUri}")
private String redirectUri;
@Value("#{'${oidc.scopes}'.split(',')}")
private List<String> oidcScopes;
@Bean
public OAuth2ProtectedResourceDetails openIdResourceDetails() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setClientId(clientId);
details.setClientSecret(clientSecret);
details.setAccessTokenUri(accessTokenUri);
details.setUserAuthorizationUri(userAuthorizationUri);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
details.setScope(oidcScopes);
details.setPreEstablishedRedirectUri(redirectUri);
details.setUseCurrentUri(false);
return details;
}
@Bean(name = "my.company.ui.security.OpenIdRestTemplate")
// ToDo: fix org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'session' is not active for the current thread
public OAuth2RestTemplate OpenIdRestTemplate(OAuth2ClientContext clientContext) {
return new OAuth2RestTemplate(openIdResourceDetails(), clientContext);
}
@Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
}
异常堆栈跟踪也类似
Caused by:
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'scopedTarget.oauth2ClientContext':
Scope 'session' is not active for the current thread; consider
defining a scoped proxy for this bean if you intend to refer to it
from a singleton;
nested exception is java.lang.IllegalStateException: No thread-bound
request found: Are you referring to request attributes outside of an
actual web request, or processing a request outside of the originally
receiving thread? If you are actually operating within a web request
and still receive this message, your code is probably running outside
of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:355)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:187)
at com.sun.proxy.$Proxy78.getAccessToken(Unknown Source)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:169)
at my.company.ui.security.OpenIdConnectFilter.attemptAuthentication(OpenIdConnectFilter.java:118)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:211)
at my.company.ui.security.OpenIdConnectFilter.doFilter(OpenIdConnectFilter.java:93)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at my.company.ui.security.UserCookieFilter.doFilter(UserCookieFilter.java:29)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at my.company.server.filter.UncaughtExceptionRequestFilter.doFilter(UncaughtExceptionRequestFilter.java:45)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at com.rmn.commons.web.metrics.AbstractInstrumentedFilter.doFilter(AbstractInstrumentedFilter.java:143)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:167)
at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:80)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:499)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection.run(AbstractConnection.java:544)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at org.springframework.web.context.request.SessionScope.get(SessionScope.java:91)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340)
... 48 more
Caused by:
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at org.springframework.web.context.request.SessionScope.get(SessionScope.java:91)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:187)
at com.sun.proxy.$Proxy78.getAccessToken(Unknown Source)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:169)
at my.company.ui.security.OpenIdConnectFilter.attemptAuthentication(OpenIdConnectFilter.java:118)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:211)
at my.company.ui.security.OpenIdConnectFilter.doFilter(OpenIdConnectFilter.java:93)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at my.company.ui.security.UserCookieFilter.doFilter(UserCookieFilter.java:29)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at my.company.server.filter.UncaughtExceptionRequestFilter.doFilter(UncaughtExceptionRequestFilter.java:45)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at com.rmn.commons.web.metrics.AbstractInstrumentedFilter.doFilter(AbstractInstrumentedFilter.java:143)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:167)
at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:80)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:499)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection.run(AbstractConnection.java:544)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Thread.java:748)
Powered by Jetty://
您可以清楚地看到过滤器调用是 Spring 安全性 FilterChainProxy
的一部分,所以我不知道如何理解错误消息的建议 "your code is probably running outside of DispatcherServlet/DispatcherPortlet"。此外,我尝试为建议的替代方案添加一个 bean RequestContextFilter
并抛出相同的异常。
执行身份验证的过滤器(删除了一些异常处理和用户处理代码):
public class OpenIdConnectFilter extends AbstractAuthenticationProcessingFilter {
public OpenIdConnectFilter(
RequestMatcher requiresAuthenticationRequestMatcher,
AuthenticationService authenticationService
) {
super(requiresAuthenticationRequestMatcher);
setAuthenticationManager(new NoopAuthenticationManager());
}
@SuppressWarnings("RedundantThrows") // Matching overridden method
@Override
public Authentication attemptAuthentication(
HttpServletRequest request,
HttpServletResponse response
) throws AuthenticationException, IOException, ServletException {
// Required parameters (one-time access code, state) are retrieved from the context
OAuth2AccessToken oAuth2AccessToken = restTemplate.getAccessToken();
// Process the token, get the user details, return an Authentication object.
}
public void setRestTemplate(OAuth2RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
private static class NoopAuthenticationManager implements AuthenticationManager {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
throw new UnsupportedOperationException("No authentication should be done with this AuthenticationManager");
}
}
private static final Logger LOGGER = LoggerFactory.getLogger(OpenIdConnectFilter.class);
@Value("${oidc.clientId}")
private String clientId;
@Value("${oidc.issuer}")
private String issuer;
@Value("${oidc.jwt.jwk.url}")
private String jwkUrl;
private final AuthenticationService authenticationService;
private OAuth2RestTemplate restTemplate;
}
以及设置 Spring 安全过滤器代理链的安全配置:
@Configuration
@EnableWebSecurity
@EnableOAuth2Client
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
@SuppressWarnings("unchecked")
protected void configure(HttpSecurity http)
throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.csrf()
.disable()
.authorizeRequests()
.expressionHandler(securityExpressionHandler)
.antMatchers("/asset/**").access("permitAll")
.antMatchers("/ws/ssoEnabled").access("permitAll")
.antMatchers("/**").access("hasRole('ROLE_USER') or hasRole('ROLE_TOKEN_ACCESS')")
.and()
.httpBasic()
.authenticationEntryPoint(ajaxAwareLoginUrlAuthenticationEntryPoint)
.and()
// Handles unauthenticated requests, catching UserRedirectRequiredExceptions and redirecting to OAuth provider
.addFilterAfter(new OAuth2ClientContextFilter(), SecurityContextPersistenceFilter.class)
// Handles the oauth callback, exchanging the one-time code for a durable token
.addFilterAfter(openIdConnectFilter, OAuth2ClientContextFilter.class)
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/logincheck")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(ajaxAwareAuthenticationSuccessHandler)
.failureHandler(ajaxAwareAuthenticationFailureHandler)
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.and()
.rememberMe()
.rememberMeServices(rememberMeServices)
// Even though this key has been added directly to the rememberMeServices instance, the RememberMeConfigurer
// can instantiate a new RememberMeServices with a made-up key if the same key is not provided.
.key("the key value")
;
// We do not configure a bean for the SessionAuthenticationStrategy. We want to use the Spring default strategy,
// which is configured by the above builder chain. In order to share the correct, configured instance with our
// custom OpenIdConnectFilter, we first tell the builder to perform the configuration (normally this would be
// done long after this method returns)...
http.getConfigurer(SessionManagementConfigurer.class).init(http);
// ... then we get the shared object by interface (SessionAuthenticationStrategy) class name...
final SessionAuthenticationStrategy sessionAuthenticationStrategy = http.getSharedObject(SessionAuthenticationStrategy.class);
// ... then set it in our custom filter.
openIdConnectFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
}
}
最后一个问题:我在 运行 我的机器上遇到这个错误。然后,添加 RequestContextListener
解决了它。将其部署到我们的测试环境时,异常消息再次出现。然而,几个小时或几天后,有时在 CI/CD 管道重新部署相同版本后,问题自行解决并且 OpenID 集成按预期工作,直到我们进行下一次更改、错误修复或改进,然后它通常在类似的短间隔内重复出现。
问题:
- 在 Spring 引导中 运行 时
DispatcherServlet/DispatcherPortlet
是一个因素吗?
- 如何确定是否应该使用
RequestContextFilter
or RequestContextListener
?有实际区别吗?在我看来,(链接的)文档在这里没有多大帮助。
Servlet listener that exposes the request to the current thread, through both LocaleContextHolder and RequestContextHolder. To be registered as listener in web.xml.
Alternatively, Spring's RequestContextFilter and Spring's DispatcherServlet also expose the same request context to the current thread. In contrast to this listener, advanced options are available there (e.g. "threadContextInheritable").
This listener is mainly for use with third-party servlets, e.g. the JSF FacesServlet. Within Spring's own web support, DispatcherServlet's processing is perfectly sufficient.
- RequestContextFilter/Listener 的 Bean 应该在 Spring 启动应用程序的什么地方定义?我们没有
web.xml
。 (我的理解是 web.xml
仅适用于 Spring MVC - 如果有误请指正。)
- 什么可能导致找到上下文有时?为什么我们的应用程序在持久故障后可以运行一段时间?
进一步调查,我发现 RequestContextFilter
肯定在 Oauth2ClientContextFilter
和 OpenIdConnectFilter
之前执行,而 运行 在本地执行。我决定尝试 M. Prokhorov 从评论中提出的建议,并将 RequestContextFilter
注册到 Spring 安全过滤器中;它扩展了 OncePerRequestFilter
以防止它执行多次。
总而言之,我进行了以下修改:
从 OpenIdConnectConfig 中删除了 RequestContextLister
bean 的声明。这是在应用程序的其他地方注册的,在我验证的父模块中进行了配置扫描。
@Bean
public ServletListenerRegistrationBean<RequestContextListener> requestContextListener() {
return new ServletListenerRegistrationBean<>(new RequestContextListener());
}
将 Oauth2ClientContextFilter
移动到 bean 声明中。似乎此过滤器中没有任何依赖于作为 Spring bean 填充的内容 - 在测试中,客户端配置附加到此过滤器处理 之前的 UserRedirectRequiredException
OAuth2RestTemplate
(确实有 Spring 注入的客户端配置 OAuth2ProtectedResourceDetails
)抛出它。但是,如果 Spring 的未来版本改变此过滤器、它处理的异常和 OAuth 客户端详细信息之间的相互作用,这感觉就像是防御性编程。
将过滤器添加到 SecurityConfig.java
中 WebSecurityConfigurerAdapter.configure()
调用中的 HttpSecurity
对象。 class 的最终代码如下所示:
@Configuration
@EnableWebSecurity
@EnableOAuth2Client
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
@SuppressWarnings("unchecked")
protected void configure(HttpSecurity http)
throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.csrf()
.disable()
.authorizeRequests()
.expressionHandler(securityExpressionHandler)
.antMatchers("/asset/**").access("permitAll")
.antMatchers("/ws/ssoEnabled").access("permitAll")
.antMatchers("/**").access("hasRole('ROLE_USER') or hasRole('ROLE_TOKEN_ACCESS')")
.and()
.httpBasic()
.authenticationEntryPoint(ajaxAwareLoginUrlAuthenticationEntryPoint)
.and()
// OAuth filters
// Explicitly add a RequestContextFilter ahead of the Oauth filters to facilitate retrieval of a session-scoped oauth2ClientContext object
.addFilterAfter(requestContextFilter, SecurityContextPersistenceFilter.class)
// Handles unauthenticated requests, catching UserRedirectRequiredExceptions and redirecting to OAuth provider
.addFilterAfter(oAuth2ClientContextFilter, RequestContextFilter.class)
// Handles the oauth callback, exchanging the one-time code for a durable token
.addFilterAfter(openIdConnectFilter, OAuth2ClientContextFilter.class)
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/logincheck")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(ajaxAwareAuthenticationSuccessHandler)
.failureHandler(ajaxAwareAuthenticationFailureHandler)
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.and()
.rememberMe()
.rememberMeServices(rememberMeServices)
// Even though this key has been added directly to the rememberMeServices instance, the RememberMeConfigurer
// can instantiate a new RememberMeServices with a made-up key if the same key is not provided.
.key("the key value")
;
// We do not configure a bean for the SessionAuthenticationStrategy. We want to use the Spring default strategy,
// which is configured by the above builder chain. In order to share the correct, configured instance with our
// custom OpenIdConnectFilter, we first tell the builder to perform the configuration (normally this would be
// done long after this method returns)...
http.getConfigurer(SessionManagementConfigurer.class).init(http);
// ... then we get the shared object by interface (SessionAuthenticationStrategy) class name...
final SessionAuthenticationStrategy sessionAuthenticationStrategy = http.getSharedObject(SessionAuthenticationStrategy.class);
// ... then set it in our custom filter.
openIdConnectFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
}
}
public class ContextAwareCallable<T> implements Callable<T> {
private Callable<T> task;
private RequestAttributes context;
public ContextAwareCallable(Callable<T> task, RequestAttributes context) {
this.task = task;
this.context = cloneRequestAttributes(context);
}
@Override
public T call() throws Exception {
if (context != null) {
RequestContextHolder.setRequestAttributes(context);
}
try {
return task.call();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
private RequestAttributes cloneRequestAttributes(RequestAttributes requestAttributes) {
RequestAttributes clonedRequestAttribute = null;
try {
clonedRequestAttribute =
new ServletRequestAttributes(((ServletRequestAttributes) requestAttributes).getRequest(),
((ServletRequestAttributes) requestAttributes).getResponse());
if (requestAttributes.getAttributeNames(RequestAttributes.SCOPE_REQUEST).length > 0) {
for (String name : requestAttributes.getAttributeNames(RequestAttributes.SCOPE_REQUEST)) {
clonedRequestAttribute.setAttribute(name,
requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST),
RequestAttributes.SCOPE_REQUEST);
}
}
return clonedRequestAttribute;
} catch (Exception e) {
return requestAttributes;
}
}
}
然后创建执行器
public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
}
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
return super.submitListenable(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
}
我正在尝试在 Spring Boot 1.3.0 应用程序中实现 OpenId Connect 登录,在 Spring Framework 4.2.3 上使用 Spring Security 3.2.5。实现与这个问题非常相似:RequestContextFilter
.
@Configuration
@EnableOAuth2Client
public class OpenIdConnectConfig {
@Value("${oidc.clientId}")
private String clientId;
@Value("${oidc.clientSecret}")
private String clientSecret;
@Value("${oidc.accessTokenUrl}")
private String accessTokenUri;
@Value("${oidc.userAuthorizationUri}")
private String userAuthorizationUri;
@Value("${oidc.redirectUri}")
private String redirectUri;
@Value("#{'${oidc.scopes}'.split(',')}")
private List<String> oidcScopes;
@Bean
public OAuth2ProtectedResourceDetails openIdResourceDetails() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setClientId(clientId);
details.setClientSecret(clientSecret);
details.setAccessTokenUri(accessTokenUri);
details.setUserAuthorizationUri(userAuthorizationUri);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
details.setScope(oidcScopes);
details.setPreEstablishedRedirectUri(redirectUri);
details.setUseCurrentUri(false);
return details;
}
@Bean(name = "my.company.ui.security.OpenIdRestTemplate")
// ToDo: fix org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'session' is not active for the current thread
public OAuth2RestTemplate OpenIdRestTemplate(OAuth2ClientContext clientContext) {
return new OAuth2RestTemplate(openIdResourceDetails(), clientContext);
}
@Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
}
异常堆栈跟踪也类似
Caused by:
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'scopedTarget.oauth2ClientContext':
Scope 'session' is not active for the current thread; consider
defining a scoped proxy for this bean if you intend to refer to it
from a singleton;
nested exception is java.lang.IllegalStateException: No thread-bound
request found: Are you referring to request attributes outside of an
actual web request, or processing a request outside of the originally
receiving thread? If you are actually operating within a web request
and still receive this message, your code is probably running outside
of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:355)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:187)
at com.sun.proxy.$Proxy78.getAccessToken(Unknown Source)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:169)
at my.company.ui.security.OpenIdConnectFilter.attemptAuthentication(OpenIdConnectFilter.java:118)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:211)
at my.company.ui.security.OpenIdConnectFilter.doFilter(OpenIdConnectFilter.java:93)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at my.company.ui.security.UserCookieFilter.doFilter(UserCookieFilter.java:29)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at my.company.server.filter.UncaughtExceptionRequestFilter.doFilter(UncaughtExceptionRequestFilter.java:45)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at com.rmn.commons.web.metrics.AbstractInstrumentedFilter.doFilter(AbstractInstrumentedFilter.java:143)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:167)
at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:80)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:499)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection.run(AbstractConnection.java:544)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at org.springframework.web.context.request.SessionScope.get(SessionScope.java:91)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340)
... 48 more
Caused by:
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at org.springframework.web.context.request.SessionScope.get(SessionScope.java:91)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:187)
at com.sun.proxy.$Proxy78.getAccessToken(Unknown Source)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:169)
at my.company.ui.security.OpenIdConnectFilter.attemptAuthentication(OpenIdConnectFilter.java:118)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:211)
at my.company.ui.security.OpenIdConnectFilter.doFilter(OpenIdConnectFilter.java:93)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at my.company.ui.security.UserCookieFilter.doFilter(UserCookieFilter.java:29)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at my.company.server.filter.UncaughtExceptionRequestFilter.doFilter(UncaughtExceptionRequestFilter.java:45)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at com.rmn.commons.web.metrics.AbstractInstrumentedFilter.doFilter(AbstractInstrumentedFilter.java:143)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:167)
at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:80)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:499)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection.run(AbstractConnection.java:544)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Thread.java:748)
Powered by Jetty://
您可以清楚地看到过滤器调用是 Spring 安全性 FilterChainProxy
的一部分,所以我不知道如何理解错误消息的建议 "your code is probably running outside of DispatcherServlet/DispatcherPortlet"。此外,我尝试为建议的替代方案添加一个 bean RequestContextFilter
并抛出相同的异常。
执行身份验证的过滤器(删除了一些异常处理和用户处理代码):
public class OpenIdConnectFilter extends AbstractAuthenticationProcessingFilter {
public OpenIdConnectFilter(
RequestMatcher requiresAuthenticationRequestMatcher,
AuthenticationService authenticationService
) {
super(requiresAuthenticationRequestMatcher);
setAuthenticationManager(new NoopAuthenticationManager());
}
@SuppressWarnings("RedundantThrows") // Matching overridden method
@Override
public Authentication attemptAuthentication(
HttpServletRequest request,
HttpServletResponse response
) throws AuthenticationException, IOException, ServletException {
// Required parameters (one-time access code, state) are retrieved from the context
OAuth2AccessToken oAuth2AccessToken = restTemplate.getAccessToken();
// Process the token, get the user details, return an Authentication object.
}
public void setRestTemplate(OAuth2RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
private static class NoopAuthenticationManager implements AuthenticationManager {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
throw new UnsupportedOperationException("No authentication should be done with this AuthenticationManager");
}
}
private static final Logger LOGGER = LoggerFactory.getLogger(OpenIdConnectFilter.class);
@Value("${oidc.clientId}")
private String clientId;
@Value("${oidc.issuer}")
private String issuer;
@Value("${oidc.jwt.jwk.url}")
private String jwkUrl;
private final AuthenticationService authenticationService;
private OAuth2RestTemplate restTemplate;
}
以及设置 Spring 安全过滤器代理链的安全配置:
@Configuration
@EnableWebSecurity
@EnableOAuth2Client
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
@SuppressWarnings("unchecked")
protected void configure(HttpSecurity http)
throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.csrf()
.disable()
.authorizeRequests()
.expressionHandler(securityExpressionHandler)
.antMatchers("/asset/**").access("permitAll")
.antMatchers("/ws/ssoEnabled").access("permitAll")
.antMatchers("/**").access("hasRole('ROLE_USER') or hasRole('ROLE_TOKEN_ACCESS')")
.and()
.httpBasic()
.authenticationEntryPoint(ajaxAwareLoginUrlAuthenticationEntryPoint)
.and()
// Handles unauthenticated requests, catching UserRedirectRequiredExceptions and redirecting to OAuth provider
.addFilterAfter(new OAuth2ClientContextFilter(), SecurityContextPersistenceFilter.class)
// Handles the oauth callback, exchanging the one-time code for a durable token
.addFilterAfter(openIdConnectFilter, OAuth2ClientContextFilter.class)
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/logincheck")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(ajaxAwareAuthenticationSuccessHandler)
.failureHandler(ajaxAwareAuthenticationFailureHandler)
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.and()
.rememberMe()
.rememberMeServices(rememberMeServices)
// Even though this key has been added directly to the rememberMeServices instance, the RememberMeConfigurer
// can instantiate a new RememberMeServices with a made-up key if the same key is not provided.
.key("the key value")
;
// We do not configure a bean for the SessionAuthenticationStrategy. We want to use the Spring default strategy,
// which is configured by the above builder chain. In order to share the correct, configured instance with our
// custom OpenIdConnectFilter, we first tell the builder to perform the configuration (normally this would be
// done long after this method returns)...
http.getConfigurer(SessionManagementConfigurer.class).init(http);
// ... then we get the shared object by interface (SessionAuthenticationStrategy) class name...
final SessionAuthenticationStrategy sessionAuthenticationStrategy = http.getSharedObject(SessionAuthenticationStrategy.class);
// ... then set it in our custom filter.
openIdConnectFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
}
}
最后一个问题:我在 运行 我的机器上遇到这个错误。然后,添加 RequestContextListener
解决了它。将其部署到我们的测试环境时,异常消息再次出现。然而,几个小时或几天后,有时在 CI/CD 管道重新部署相同版本后,问题自行解决并且 OpenID 集成按预期工作,直到我们进行下一次更改、错误修复或改进,然后它通常在类似的短间隔内重复出现。
问题:
- 在 Spring 引导中 运行 时
DispatcherServlet/DispatcherPortlet
是一个因素吗? - 如何确定是否应该使用
RequestContextFilter
orRequestContextListener
?有实际区别吗?在我看来,(链接的)文档在这里没有多大帮助。Servlet listener that exposes the request to the current thread, through both LocaleContextHolder and RequestContextHolder. To be registered as listener in web.xml.
Alternatively, Spring's RequestContextFilter and Spring's DispatcherServlet also expose the same request context to the current thread. In contrast to this listener, advanced options are available there (e.g. "threadContextInheritable").
This listener is mainly for use with third-party servlets, e.g. the JSF FacesServlet. Within Spring's own web support, DispatcherServlet's processing is perfectly sufficient.
- RequestContextFilter/Listener 的 Bean 应该在 Spring 启动应用程序的什么地方定义?我们没有
web.xml
。 (我的理解是web.xml
仅适用于 Spring MVC - 如果有误请指正。) - 什么可能导致找到上下文有时?为什么我们的应用程序在持久故障后可以运行一段时间?
进一步调查,我发现 RequestContextFilter
肯定在 Oauth2ClientContextFilter
和 OpenIdConnectFilter
之前执行,而 运行 在本地执行。我决定尝试 M. Prokhorov 从评论中提出的建议,并将 RequestContextFilter
注册到 Spring 安全过滤器中;它扩展了 OncePerRequestFilter
以防止它执行多次。
总而言之,我进行了以下修改:
从 OpenIdConnectConfig 中删除了 RequestContextLister
bean 的声明。这是在应用程序的其他地方注册的,在我验证的父模块中进行了配置扫描。
@Bean
public ServletListenerRegistrationBean<RequestContextListener> requestContextListener() {
return new ServletListenerRegistrationBean<>(new RequestContextListener());
}
将 Oauth2ClientContextFilter
移动到 bean 声明中。似乎此过滤器中没有任何依赖于作为 Spring bean 填充的内容 - 在测试中,客户端配置附加到此过滤器处理 之前的 UserRedirectRequiredException
OAuth2RestTemplate
(确实有 Spring 注入的客户端配置 OAuth2ProtectedResourceDetails
)抛出它。但是,如果 Spring 的未来版本改变此过滤器、它处理的异常和 OAuth 客户端详细信息之间的相互作用,这感觉就像是防御性编程。
将过滤器添加到 SecurityConfig.java
中 WebSecurityConfigurerAdapter.configure()
调用中的 HttpSecurity
对象。 class 的最终代码如下所示:
@Configuration
@EnableWebSecurity
@EnableOAuth2Client
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
@SuppressWarnings("unchecked")
protected void configure(HttpSecurity http)
throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.csrf()
.disable()
.authorizeRequests()
.expressionHandler(securityExpressionHandler)
.antMatchers("/asset/**").access("permitAll")
.antMatchers("/ws/ssoEnabled").access("permitAll")
.antMatchers("/**").access("hasRole('ROLE_USER') or hasRole('ROLE_TOKEN_ACCESS')")
.and()
.httpBasic()
.authenticationEntryPoint(ajaxAwareLoginUrlAuthenticationEntryPoint)
.and()
// OAuth filters
// Explicitly add a RequestContextFilter ahead of the Oauth filters to facilitate retrieval of a session-scoped oauth2ClientContext object
.addFilterAfter(requestContextFilter, SecurityContextPersistenceFilter.class)
// Handles unauthenticated requests, catching UserRedirectRequiredExceptions and redirecting to OAuth provider
.addFilterAfter(oAuth2ClientContextFilter, RequestContextFilter.class)
// Handles the oauth callback, exchanging the one-time code for a durable token
.addFilterAfter(openIdConnectFilter, OAuth2ClientContextFilter.class)
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/logincheck")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(ajaxAwareAuthenticationSuccessHandler)
.failureHandler(ajaxAwareAuthenticationFailureHandler)
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.and()
.rememberMe()
.rememberMeServices(rememberMeServices)
// Even though this key has been added directly to the rememberMeServices instance, the RememberMeConfigurer
// can instantiate a new RememberMeServices with a made-up key if the same key is not provided.
.key("the key value")
;
// We do not configure a bean for the SessionAuthenticationStrategy. We want to use the Spring default strategy,
// which is configured by the above builder chain. In order to share the correct, configured instance with our
// custom OpenIdConnectFilter, we first tell the builder to perform the configuration (normally this would be
// done long after this method returns)...
http.getConfigurer(SessionManagementConfigurer.class).init(http);
// ... then we get the shared object by interface (SessionAuthenticationStrategy) class name...
final SessionAuthenticationStrategy sessionAuthenticationStrategy = http.getSharedObject(SessionAuthenticationStrategy.class);
// ... then set it in our custom filter.
openIdConnectFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
}
}
public class ContextAwareCallable<T> implements Callable<T> {
private Callable<T> task;
private RequestAttributes context;
public ContextAwareCallable(Callable<T> task, RequestAttributes context) {
this.task = task;
this.context = cloneRequestAttributes(context);
}
@Override
public T call() throws Exception {
if (context != null) {
RequestContextHolder.setRequestAttributes(context);
}
try {
return task.call();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
private RequestAttributes cloneRequestAttributes(RequestAttributes requestAttributes) {
RequestAttributes clonedRequestAttribute = null;
try {
clonedRequestAttribute =
new ServletRequestAttributes(((ServletRequestAttributes) requestAttributes).getRequest(),
((ServletRequestAttributes) requestAttributes).getResponse());
if (requestAttributes.getAttributeNames(RequestAttributes.SCOPE_REQUEST).length > 0) {
for (String name : requestAttributes.getAttributeNames(RequestAttributes.SCOPE_REQUEST)) {
clonedRequestAttribute.setAttribute(name,
requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST),
RequestAttributes.SCOPE_REQUEST);
}
}
return clonedRequestAttribute;
} catch (Exception e) {
return requestAttributes;
}
}
}
然后创建执行器
public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
}
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
return super.submitListenable(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
}