如何克服间歇性范围“会话"对于 OauthClientContext 的当前线程不活动? [英] How to overcome intermittent Scope 'session' is not active for the current thread for OauthClientContext?

查看:19
本文介绍了如何克服间歇性范围“会话"对于 OauthClientContext 的当前线程不活动?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在 Spring 框架 4.2.3 上使用 Spring Security 3.2.5 在 Spring Boot 1.3.0 应用程序中实现 OpenId Connect 登录.实现与这个问题非常相似:Protecting REST API with OAuth2: Error created bean with name 'scopedTarget.oauth2ClientContext': Scope 'session'未激活,除了我已经为 RequestContextFilter 实现了建议的 bean.

@Configuration@EnableOAuth2Client公共类 OpenIdConnectConfig {@Value("${oidc.clientId}")私人字符串clientId;@Value("${oidc.clientSecret}")私人字符串clientSecret;@Value("${oidc.accessTokenUrl}")私人字符串 accessTokenUri;@Value("${oidc.userAuthorizationUri}")私人字符串 userAuthorizationUri;@Value("${oidc.redirectUri}")私人字符串redirectUri;@Value("#{'${oidc.scopes}'.split(',')}")私人列表<字符串>oidcScopes;@豆公共 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);退货详情;}@Bean(name = "my.company.ui.security.OpenIdRestTemplate")//待办事项:修复 org.springframework.beans.factory.BeanCreationException:创建名为scopedTarget.oauth2ClientContext"的 bean 时出错:当前线程的作用域会话"未激活公共 OAuth2RestTemplate OpenIdRestTemplate(OAuth2ClientContext clientContext) {返回新的 OAuth2RestTemplate(openIdResourceDetails(), clientContext);}@豆公共 RequestContextListener requestContextListener() {返回新的 RequestContextListener();}}

异常堆栈跟踪也类似

造成的:org.springframework.beans.factory.BeanCreationException:创建名为scopedTarget.oauth2ClientContext"的 bean 时出错:当前线程的范围会话"未激活;考虑如果您打算引用它,请为该 bean 定义一个作用域代理来自单身人士;嵌套异常是 java.lang.IllegalStateException: No thread-bound找到的请求:您指的是请求属性之外的实际的网络请求,或处理原始请求之外的请求接收线程?如果您实际上是在 Web 请求中操作仍然收到此消息,您的代码可能在外面运行DispatcherServlet/DispatcherPortlet:在这种情况下,使用RequestContextListener 或 RequestContextFilter 来公开当前请求.在 org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:355)在 org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)在 org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)在 org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:187)在 com.sun.proxy.$Proxy78.getAccessToken(来源不明)在 org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:169)在 my.company.ui.security.OpenIdConnectFilter.attemptAuthentication(OpenIdConnectFilter.java:118)在 org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:211)在 my.company.ui.security.OpenIdConnectFilter.doFilter(OpenIdConnectFilter.java:93)在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)在 org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60)在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)在 org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)在 org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)在 org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)在 org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)在 org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)在 org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)在 my.company.ui.security.UserCookieFilter.doFilter(UserCookieFilter.java:29)在 org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)在 my.company.server.filter.UncaughtExceptionRequestFilter.doFilter(UncaughtExceptionRequestFilter.java:45)在 org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)在 com.rmn.commons.web.metrics.AbstractInstrumentedFilter.doFilter(AbstractInstrumentedFilter.java:143)在 org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)在 org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:167)在 org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:80)在 org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)在 org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)在 org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)在 org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)在 org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)在 org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)在 org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)在 org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)在 org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)在 org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)在 org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)在 org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)在 org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)在 org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)在 org.eclipse.jetty.server.Server.handle(Server.java:499)在 org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)在 org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)在 org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:544)在 org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)在 org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)在 java.lang.Thread.run(Thread.java:748)引起:java.lang.IllegalStateException: No thread-bound request found: 你是指实际Web请求之外的请求属性,还是在原始接收线程之外处理请求?如果您实际上是在 Web 请求中操作并且仍然收到此消息,则您的代码可能在 DispatcherServlet/DispatcherPortlet 之外运行:在这种情况下,请使用 RequestContextListener 或 RequestContextFilter 来公开当前请求.在 org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)在 org.springframework.web.context.request.SessionScope.get(SessionScope.java:91)在 org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340)... 48 更多引起的:java.lang.IllegalStateException: No thread-bound request found: 您是指实际 Web 请求之外的请求属性,还是在原始接收线程之外处理请求?如果您实际上是在 Web 请求中操作并且仍然收到此消息,则您的代码可能在 DispatcherServlet/DispatcherPortlet 之外运行:在这种情况下,请使用 RequestContextListener 或 RequestContextFilter 来公开当前请求.在 org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)在 org.springframework.web.context.request.SessionScope.get(SessionScope.java:91)在 org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340)在 org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)在 org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)在 org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:187)在 com.sun.proxy.$Proxy78.getAccessToken(来源不明)在 org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:169)在 my.company.ui.security.OpenIdConnectFilter.attemptAuthentication(OpenIdConnectFilter.java:118)在 org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:211)在 my.company.ui.security.OpenIdConnectFilter.doFilter(OpenIdConnectFilter.java:93)在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)在 org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60)在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)在 org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)在 org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)在 org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)在 org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)在 org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)在 org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)在 my.company.ui.security.UserCookieFilter.doFilter(UserCookieFilter.java:29)在 org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)在 my.company.server.filter.UncaughtExceptionRequestFilter.doFilter(UncaughtExceptionRequestFilter.java:45)在 org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)在 com.rmn.commons.web.metrics.AbstractInstrumentedFilter.doFilter(AbstractInstrumentedFilter.java:143)在 org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)在 org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:167)在 org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:80)在 org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)在 org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)在 org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)在 org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)在 org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)在 org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)在 org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)在 org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)在 org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)在 org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)在 org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)在 org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)在 org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)在 org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)在 org.eclipse.jetty.server.Server.handle(Server.java:499)在 org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)在 org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)在 org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:544)在 org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)在 org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)在 java.lang.Thread.run(Thread.java:748)由 Jetty 提供支持://

您可以清楚地看到过滤器调用是 Spring Security FilterChainProxy 的一部分,所以我不知道如何理解错误消息中的您的代码可能在 DispatcherServlet 之外运行"的建议/DispatcherPortlet".此外,我尝试为建议的替代 RequestContextFilter 添加一个 bean,并抛出相同的异常.

执行身份验证的过滤器(删除了一些异常处理和用户处理代码):

public class OpenIdConnectFilter extends AbstractAuthenticationProcessingFilter {公共 OpenIdConnectFilter(RequestMatcher requiresAuthenticationRequestMatcher,AuthenticationService authenticationService){超级(需要AuthenticationRequestMatcher);setAuthenticationManager(new NoopAuthenticationManager());}@SuppressWarnings("RedundantThrows")//匹配被覆盖的方法@覆盖公共身份验证尝试身份验证(HttpServletRequest 请求,HttpServletResponse 响应) 抛出 AuthenticationException, IOException, ServletException {//从上下文中检索必需的参数(一次性访问代码、状态)OAuth2AccessToken oAuth2AccessToken = restTemplate.getAccessToken();//处理令牌,获取用户详细信息,返回一个 Authentication 对象.}公共无效 setRestTemplate(OAuth2RestTemplate restTemplate) {this.restTemplate = restTemplate;}私有静态类 NoopAuthenticationManager 实现 AuthenticationManager {@覆盖公共身份验证身份验证(身份验证身份验证)抛出 AuthenticationException {throw new UnsupportedOperationException("不应使用此 AuthenticationManager 进行身份验证");}}私有静态最终记录器 LOGGER = LoggerFactory.getLogger(OpenIdConnectFilter.class);@Value("${oidc.clientId}")私人字符串clientId;@Value("${oidc.issuer}")私人字符串发行者;@Value("${oidc.jwt.jwk.url}")私人字符串 jwkUrl;私有最终AuthenticationService authenticationService;私有 OAuth2RestTemplate restTemplate;}

以及设置 Spring Security FilterProxyChain 的安全配置:

@Configuration@启用网络安全@EnableOAuth2Client公共类 SecurityConfig 扩展了 WebSecurityConfigurerAdapter{@覆盖@SuppressWarnings("未选中")受保护的无效配置(HttpSecurity http)抛出异常{http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).and().csrf().禁用().authorizeRequests().expressionHandler(securityExpressionHandler).antMatchers("/asset/**").access("permitAll").antMatchers("/ws/ssoEnabled").access("permitAll").antMatchers("/**").access("hasRole('ROLE_USER') 或 hasRole('ROLE_TOKEN_ACCESS')").and().httpBasic().authenticationEntryPoint(ajaxAwareLoginUrlAuthenticationEntryPoint).and()//处理未经身份验证的请求,捕获 UserRedirectRequiredExceptions 并重定向到 OAuth 提供程序.addFilterAfter(new OAuth2ClientContextFilter(), SecurityContextPersistenceFilter.class)//处理 oauth 回调,将一次性代码交换为持久令牌.addFilterAfter(openIdConnectFilter, OAuth2ClientContextFilter.class).formLogin().loginPage("/登录").loginProcessingUrl("/logincheck").usernameParameter("用户名").passwordParameter("密码").successHandler(ajaxAwareAuthenticationSuccessHandler).failureHandler(ajaxAwareAuthenticationFailureHandler).and().注销().logoutUrl("/注销").logoutSuccessUrl("/登录").and().rememberMe().rememberMeServices(rememberMeServices)//即使该键已直接添加到 rememberMeServices 实例中,RememberMeConfigurer//如果未提供相同的密钥,则可以使用生成的密钥实例化新的 RememberMeServices..key("键值");//我们没有为 SessionAuthenticationStrategy 配置 bean.我们要使用 Spring 默认策略,//这是由上面的构建器链配置的.为了与我们的共享正确的配置实例//自定义 OpenIdConnectFilter,我们首先告诉构建器执行配置(通常这将是//在此方法返回后很久就完成了)...http.getConfigurer(SessionManagementConfigurer.class).init(http);//... 然后我们通过接口 (SessionAuthenticationStrategy) 类名获取共享对象...最终 SessionAuthenticationStrategy sessionAuthenticationStrategy = http.getSharedObject(SessionAuthenticationStrategy.class);//...然后在我们的自定义过滤器中设置它.openIdConnectFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);}}

最后一个问题:我在我的机器上运行时遇到了这个错误.然后,添加 RequestContextListener 解决了它.将其部署到我们的测试环境时,异常消息再次出现.然而,在几个小时或几天后,有时在 CI/CD 管道重新部署相同版本后,问题会自行解决,OpenID 集成按预期工作,直到我们进行下一次更改、错误修复或改进,然后它通常会再次发生类似的短间隔.

<小时>

问题:

  1. DispatcherServlet/DispatcherPortlet 在 Spring Boot 中运行时是否是一个因素?
  2. 您如何确定是否应该使用 RequestContextFilterRequestContextListener?有实际区别吗?在我看来,(链接的)文档在这里没有多大帮助.<块引用>

    通过 LocaleContextHolder 和 RequestContextHolder 向当前线程公开请求的 Servlet 侦听器.在 web.xml 中注册为监听器.

    或者,Spring 的 RequestContextFilter 和 Spring 的 DispatcherServlet 也将相同的请求上下文暴露给当前线程.与此侦听器相比,此处提供了高级选项(例如threadContextInheritable").

    此侦听器主要用于第三方 servlet,例如JSF FacesServlet.在 Spring 自己的 web 支持中,DispatcherServlet 的处理完全足够.

  3. 应在 Spring Boot 应用程序中的何处定义 RequestContextFilter/Listener 的 Bean?我们没有 web.xml.(我的理解是 web.xml 仅适用于 Spring MVC - 如果有误,请纠正我.)
  4. 什么可能导致有时找到上下文?为什么我们的应用在忍受了一段时间的失败后还能运行?

解决方案

进一步调查,我发现 RequestContextFilter 肯定在 Oauth2ClientContextFilter 之前执行>OpenIdConnectFilter 在本地运行时.我决定抓住 M. Prokhorov 从评论中提出的建议,并将 RequestContextFilter 注册到 Spring Security 的过滤器中;它扩展了 OncePerRequestFilter 以防止它多次执行.

总而言之,我进行了以下修改:

从 OpenIdConnectConfig 中删除了 RequestContextLister bean 的声明.这是在应用程序的其他地方注册的,在我验证过的父模块中进行了配置扫描.

@Bean公共 ServletListenerRegistrationBeanrequestContextListener() {返回新的 ServletListenerRegistrationBean<>(new RequestContextListener());}

<小时>

Oauth2ClientContextFilter 移动到 bean 声明中.此过滤器中似乎没有任何依赖于作为 Spring bean 填充的内容 – 在测试中,客户端配置附加到此过滤器之前处理的 UserRedirectRequiredException> OAuth2RestTemplate(其中确实有 Spring 注入的客户端配置 OAuth2ProtectedResourceDetails)抛出它.但是,如果 Spring 的未来版本更改此过滤器、它处理的异常和 OAuth 客户端详细信息之间的相互作用,这感觉就像是防御性编程.

<小时>

将过滤器添加到 SecurityConfig.java 中的 WebSecurityConfigurerAdapter.configure() 调用中的 HttpSecurity 对象.该类的最终代码如下所示:

@Configuration@启用网络安全@EnableOAuth2Client公共类 SecurityConfig 扩展了 WebSecurityConfigurerAdapter{@覆盖@SuppressWarnings("未选中")受保护的无效配置(HttpSecurity http)抛出异常{http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).and().csrf().禁用().authorizeRequests().expressionHandler(securityExpressionHandler).antMatchers("/asset/**").access("permitAll").antMatchers("/ws/ssoEnabled").access("permitAll").antMatchers("/**").access("hasRole('ROLE_USER') 或 hasRole('ROLE_TOKEN_ACCESS')").and().httpBasic().authenticationEntryPoint(ajaxAwareLoginUrlAuthenticationEntryPoint).and()//OAuth 过滤器//在 Oauth 过滤器之前显式添加 RequestContextFilter 以方便检索会话范围的 oauth2ClientContext 对象.addFilterAfter(requestContextFilter, SecurityContextPersistenceFilter.class)//处理未经身份验证的请求,捕获 UserRedirectRequiredExceptions 并重定向到 OAuth 提供程序.addFilterAfter(oAuth2ClientContextFilter, RequestContextFilter.class)//处理 oauth 回调,将一次性代码交换为持久令牌.addFilterAfter(openIdConnectFilter, OAuth2ClientContextFilter.class).formLogin().loginPage("/登录").loginProcessingUrl("/logincheck").usernameParameter("用户名").passwordParameter("密码").successHandler(ajaxAwareAuthenticationSuccessHandler).failureHandler(ajaxAwareAuthenticationFailureHandler).and().注销().logoutUrl("/注销").logoutSuccessUrl("/登录").and().rememberMe().rememberMeServices(rememberMeServices)//即使该键已直接添加到 rememberMeServices 实例中,RememberMeConfigurer//如果未提供相同的密钥,则可以使用生成的密钥实例化新的 RememberMeServices..key("键值");//我们没有为 SessionAuthenticationStrategy 配置 bean.我们要使用 Spring 默认策略,//这是由上面的构建器链配置的.为了与我们的共享正确的配置实例//自定义 OpenIdConnectFilter,我们首先告诉构建器执行配置(通常这将是//在此方法返回后很久就完成了)...http.getConfigurer(SessionManagementConfigurer.class).init(http);//... 然后我们通过接口 (SessionAuthenticationStrategy) 类名获取共享对象...最终 SessionAuthenticationStrategy sessionAuthenticationStrategy = http.getSharedObject(SessionAuthenticationStrategy.class);//...然后在我们的自定义过滤器中设置它.openIdConnectFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);}}

I'm attempting to implement OpenId Connect sign-on in a Spring Boot 1.3.0 application with Spring Security 3.2.5 on Spring Framework 4.2.3. The implementation is very similar to this question: Protecting REST API with OAuth2: Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'session' is not active, except that I've implemented the suggested bean for the 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();
    }
}

The exception stack trace is likewise similar

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$2.run(AbstractConnection.java:544)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.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$2.run(AbstractConnection.java:544)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
    at java.lang.Thread.run(Thread.java:748)
Powered by Jetty://

You can clearly see that the filter call is part of the Spring Security FilterChainProxy, so I don't know what to make of the error message's suggestion that "your code is probably running outside of DispatcherServlet/DispatcherPortlet". Also, I have tried adding a bean for the suggested alternative RequestContextFilter and the same exception is thrown.

The filter that performs the authentication (some exception handling and user processing code removed):

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;
}

And the Security Config that sets up the Spring Security FilterProxyChain:

@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);
    }
}

One final wrinkle: I was getting this error while running on my machine. Then, adding the RequestContextListener resolved it. When deploying it to our test environment, the exception message resurfaces. However, after a few hours or days, sometimes after the CI/CD pipeline re-deploys the same version, the problem resolves itself and the OpenID integration works as expected until we make the next change, bugfix or improvement, then it usually reoccurs for a similar short interval.


Questions:

  1. Is DispatcherServlet/DispatcherPortlet a factor at all when running in Spring Boot?
  2. How do you determine if you should use a RequestContextFilter or RequestContextListener? Is there an actual difference? The (linked) documentation isn't much help here, in my opinion.

    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.

  3. Where should the Beans for the RequestContextFilter/Listener be defined in a Spring Boot app? We don't have a web.xml. (My understanding is that web.xml is only for Spring MVC - please correct me if that is wrong.)
  4. What could possibly be causing the context to be found sometimes? Why is it that our app can work after enduring failure for a little while?

解决方案

Investigating this further, I found that the RequestContextFilter was definitely being executed before the Oauth2ClientContextFilter and OpenIdConnectFilter when running locally. I decide to take a chance on M. Prokhorov's suggestion from the comments and register the RequestContextFilter into Spring Security's filters anyways; it extends the OncePerRequestFilter preventing it from executing more than once anyways.

All said and done, I went with the following modifications:

Removed the declaration of the RequestContextLister bean from the OpenIdConnectConfig. This was registered elsewhere in the application, in a parent module that I verified was getting configuration-scanned.

@Bean
public ServletListenerRegistrationBean<RequestContextListener> requestContextListener() {
    return new ServletListenerRegistrationBean<>(new RequestContextListener());
}


Moved the Oauth2ClientContextFilter into a bean declaration. It doesn't seem like there's anything in this filter that relies on being populated as a Spring bean – in testing, the client configuration was attached to the UserRedirectRequiredException that this filter processes before the OAuth2RestTemplate (which does have the client configuration OAuth2ProtectedResourceDetails injected into it by Spring) throws it. However, it felt like defensive programming in case a future version of Spring changes the interplay between this filter, the exceptions it handles, and the OAuth client details.


Adding the filter to the HttpSecurity object in the WebSecurityConfigurerAdapter.configure() call in SecurityConfig.java. The final code for that class looked like this:

@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);
    }
}

这篇关于如何克服间歇性范围“会话"对于 OauthClientContext 的当前线程不活动?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆