使用OAuth2保护REST API:创建名为“scopedTarget.oauth2ClientContext”的bean时出错:范围“会话”未激活

时间:2016-03-08 18:25:47

标签: spring spring-security spring-security-oauth2

我已经工作了几天,试图在REST API上实现oauth2保护。我已经尝试了很多不同的配置,但仍然没有设法让它工作。

我正在证明我现在拥有的代码,但我与这个实现没有任何关系。如果你能告诉我一些完全不同的方式来实现我想要完成的目标,那就太好了。

我的流程如下:

  1. 客户端检查Auth Server,获取令牌。
  2. 客户端将令牌发送到资源服务器。
  3. Resource Server使用Auth Server确保令牌有效。
  4. Auth服务器运行正常。我在配置资源服务器时遇到了问题。

    资源服务器上的配置

    这是我的一些配置。我有这个豆子:

    誓言休息模板

    @EnableOAuth2Client
    @Configuration
    @Import({PropertiesConfig.class}) //Imports properties from properties files.
    public class OauthRestTemplateConfig {
    
    
    
     @Bean
        public OAuth2RestTemplate oAuth2RestTemplate(OAuth2ClientContext oauth2ClientContext) {
            OAuth2RestTemplate template = new OAuth2RestTemplate(oauth2ResourceDetails(), oauth2ClientContext);
            return template;
        }
    
        @Bean
        OAuth2ProtectedResourceDetails oauth2ResourceDetails() {
            AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
            details.setId("theOauth");
            details.setClientId("clientID");
            details.setClientSecret("SecretKey");
            details.setAccessTokenUri("https://theAuthenticationServer.com/oauthserver/oauth2/token");
            details.setUserAuthorizationUri("https://theAuthenticationServer.com/oauthserver/oauth2/token");
            details.setTokenName("oauth_token");
            details.setPreEstablishedRedirectUri("http://localhost/login");
            details.setUseCurrentUri(true);
            return details;
        }
    }
    

    安全配置

    我在资源服务器的主安全配置中使用该bean:

    @Slf4j
    @Configuration
    @EnableWebSecurity
    @EnableOAuth2Client
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true, proxyTargetClass = true)
    @Import({PropertiesConfig.class, OauthRestTemplateConfig.class})
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        @Qualifier("oAuth2RestTemplate")
        private OAuth2RestTemplate oAuth2RestTemplate;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http
                    .authorizeRequests()
                    .accessDecisionManager(accessDecisionManager()) //This is a WebExpressionVoter. I don't think it's related to the problem so didn't include the source.
                        .antMatchers("/login").permitAll()      
                    .antMatchers("/api/**").authenticated()
                    .anyRequest().authenticated();
            http
                    .exceptionHandling()
                    .authenticationEntryPoint(delegatingAuthenticationEntryPoint());
            http
                    .addFilterBefore(new OAuth2ClientContextFilter(), BasicAuthenticationFilter.class)
                    .addFilterAfter(oauth2ClientAuthenticationProcessingFilter(), OAuth2ClientContextFilter.class)
            ;
        }
    
        private OAuth2ClientAuthenticationProcessingFilter oauth2ClientAuthenticationProcessingFilter() {
            OAuth2ClientAuthenticationProcessingFilter
                    daFilter = new OAuth2ClientAuthenticationProcessingFilter("/api/**");
            daFilter.setRestTemplate(oAuth2RestTemplate);
            daFilter.setTokenServices(inMemoryTokenServices());
            return daFilter;
        } 
    
        private DefaultTokenServices inMemoryTokenServices() {
            InMemoryTokenStore tok = new InMemoryTokenStore();
            DefaultTokenServices tokenService = new DefaultTokenServices();
            tokenService.setTokenStore(tok);
    
            return tokenService;
        }
    }
    

    安全配置中的额外内容

    Aaand,我认为其中一些豆类不太相关,但在这里它们以防万一你需要它们:

    @Bean
    public DelegatingAuthenticationEntryPoint delegatingAuthenticationEntryPoint() {
        LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> matchers =
                Maps.newLinkedHashMap();
    
        //Match all HTTP methods
        matchers.put(new RegexRequestMatcher("\\/api\\/v\\d+\\/.*", null), oAuth2AuthenticationEntryPoint());
        matchers.put(AnyRequestMatcher.INSTANCE, casAuthenticationEntryPoint());
    
        DelegatingAuthenticationEntryPoint entryPoint = new DelegatingAuthenticationEntryPoint(matchers);
        entryPoint.setDefaultEntryPoint(casAuthenticationEntryPoint());
    
        return entryPoint;
    }
    @Bean(name = "casEntryPoint")
    public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
        CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
        casAuthenticationEntryPoint.setLoginUrl(casUrl + "/login");
        casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
    
        return casAuthenticationEntryPoint;
    }
    

    错误

    资源服务器启动就好了。客户端从AuthenticationServer.com获取其身份验证令牌,并将其在请求标头中发送到api URL。我收到以下错误:

      

    HTTP状态500 - 创建名为'scopedTarget.oauth2ClientContext'的bean时出错:当前线程的作用域'session'无效;考虑为这个bean定义一个范围代理,如果你想从一个单例引用它;嵌套异常是java.lang.IllegalStateException:找不到线程绑定请求:您是指在实际Web请求之外的请求属性,还是在最初接收线程之外处理请求?如果您实际上是在Web请求中操作并仍然收到此消息,则您的代码可能在DispatcherServlet / DispatcherPortlet之外运行:在这种情况下,请使用RequestContextListener或RequestContextFilter来公开当前请求。

         

    例外报告

         

    创建名为'scopedTarget.oauth2ClientContext'的bean时出错:当前线程的作用域'session'无效;考虑为这个bean定义一个范围代理,如果你想从一个单例引用它;嵌套异常是java.lang.IllegalStateException:找不到线程绑定请求:您是指在实际Web请求之外的请求属性,还是在最初接收线程之外处理请求?如果您实际上是在Web请求中操作并仍然收到此消息,则您的代码可能在DispatcherServlet / DispatcherPortlet之外运行:在这种情况下,请使用RequestContextListener或RequestContextFilter来公开当前请求。

         

    服务器遇到内部错误,导致无法完成此请求。

            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.
        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.$Proxy26.getAccessToken(Unknown Source)
        org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:169)
        org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:94)
        org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217)
        org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60)
        org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120)
        org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
        org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
        org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
        org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
        org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
        org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
        org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
        org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
        org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    
    root cause
            <pre>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.
        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.$Proxy26.getAccessToken(Unknown Source)
        org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:169)
        org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:94)
        org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217)
        org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60)
        org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120)
        org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
        org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
        org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
        org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
        org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
        org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
        org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
        org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
        org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
        org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    

    我尝试了很多不同的配置,在网上查找了大量的资源,而我却无处可去。我使用正确的课程吗?知道我可能需要改变哪些配置?

6 个答案:

答案 0 :(得分:17)

启用请求上下文侦听器的更简单方法是在应用程序中添加bean注释。

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

答案 1 :(得分:4)

在查看Spring文档之后,我最终解决了这个问题。

事实证明,范围上下文实际上并不存在于我的应用中,因为我没有初始化它。

我通过添加此侦听器来初始化它:

<listener>
 <listener-class>
        org.springframework.web.context.request.RequestContextListener
 </listener-class>
</listener>

答案 2 :(得分:1)

  

我正在证明我现在的代码,但我没有结婚   这个实现。如果你能告诉我一些根本不同的东西   如何实现我想要完成的目标,非常好

如果您的主要问题是实现资源服务器,并且您可以使用完全不同的解决方案,则可以使用Spring Boot的资源服务器自动配置。这样你就会有ResourceServerConfiguration如下:

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                    .anyRequest().authenticated();
        // you can put your application specific configurations here
        // here i'm just authenticating every request
    }
}

application.yml中的src/main/resources配置文件:

security:
  oauth2:
    client:
      client-id: client
      client-secret: secret
    resource:
      token-info-uri: http://localhost:8888/oauth/check_token

您应该在那里添加client-idclient-secrettoken-info-uritoken-info-uri授权服务器上的端点,我们的资源服务器将查询传递的访问令牌的有效性。

通过这些安排,如果客户端向/api/greet API:

发出请求
GET /api/greet HTTP/1.1
Host: localhost:8080
Authorization: bearer cef63a29-f9aa-4dcf-9155-41fb035a6cdb

我们的资源服务器将从请求中提取 Bearer 访问令牌,并将以下请求发送到授权服务器以验证访问令牌:

GET /oauth/check_token?token=cef63a29-f9aa-4dcf-9155-41fb035a6cdb HTTP/1.1
Host: localhost:8888
Authorization: basic base64(client-id:client-secret)

如果令牌有效,授权服务器会发送200 OK响应,其中包含以下JSON正文:

{"exp":1457684735,"user_name":"me","authorities":["ROLE_USER"],"client_id":"client","scope":["auth"]}

否则,它将返回4xx Client Error

这是一个maven项目,其中pom.xml如下:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.3.RELEASE</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth</groupId>
        <artifactId>spring-security-oauth2</artifactId>
    </dependency>
</dependencies>

一个典型的Application类:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

您可以查看资源服务器自动配置here上的spring boot文档。

答案 3 :(得分:0)

我认为问题的根源在于您使用OAuth2ClientAuthenticationProcessingFilter运算符创建OAuth2ClientContextFilternew

如果你看一下stacktrace

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.$Proxy26.getAccessToken(Unknown Source)
org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:169)
org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:94)

它是如何从OAuth2ClientAuthenticationProcessingFilter转到JdkDynamicAopProxy并尝试获取bean的链。我可以假设因为bean是用Spring容器创建的,所以它不能从会话范围中获取bean。

尝试将过滤器包装到@Bean注释中,以便将它们放入上下文中。此外,我认为值得设置正确的范围:request在这里最匹配。

答案 4 :(得分:0)

当使用带有spock-spring 1.1-groovy-2.4-rc-2的弹簧靴1.4.1时,我遇到了同样的问题。解决它的最简单方法是使用Spock 1.0。

已经报告了一个错误:
https://github.com/spockframework/spock/issues/655

答案 5 :(得分:0)

公共类ContextAwareCallable实现Callable {

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

}


然后创建执行程序

公共类ContextAwarePoolExecutor扩展了ThreadPoolTask​​Executor {

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

}