Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Protecting REST API with OAuth2: Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'session' is not active

Tags:

I've been working for a few days to attempt to implement oauth2 protection on a REST API. I've tried a ton of different configurations but still haven't managed to get it to work.

I'm proving the code that I have right now, but I'm in no way married to this implementation. If you can show me some radically different way to accomplish what I want to accomplish, great.

My flow looks like this:

  1. Client checks Auth Server, gets token.
  2. Client sends token to Resource Server.
  3. Resource Server uses Auth Server to make sure that token is valid.

The Auth Server works fine. I'm having trouble configuring the Resource Server.

Configs on Resource Server

Here's some of my configs. I have this bean:

Ouath Rest Template

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

Security Config

I use that bean in my main security config in Resource Server:

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

Extra stuff in Security Config

Aaand, some of the beans which I believe are less relevant, but here they are in case you need them:

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

Error

Resource Server starts up just fine. Client gets its auth token from theAuthenticationServer.com and sends it in the request header to an api url. And I get the following error:

HTTP Status 500 - 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.

Exception report

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.

The server encountered an internal error that prevented it from fulfilling this request.

        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)

I've tried a lot of different configs, looked up a ton of resources online, and I've gotten nowhere. Am I using the right classes? Any idea what configs I might need to change?

like image 463
Joey Avatar asked Mar 08 '16 18:03

Joey


3 Answers

An even easier way to enable the request context listener is to add a bean annotation into your app.

@Bean
public RequestContextListener requestContextListener() {
    return new RequestContextListener();
}
like image 186
Lucas Holt Avatar answered Oct 13 '22 04:10

Lucas Holt


I ended up resolving this thing after looking into Spring documentation.

It turned out that the scope context didn't actually exist in my app, because I hadn't initialized it.

I initialized it by adding this listener:

<listener>
 <listener-class>
        org.springframework.web.context.request.RequestContextListener
 </listener-class>
</listener>
like image 44
Joey Avatar answered Oct 13 '22 05:10

Joey


I'm proving the code that I have right now, but I'm in no way married to this implementation. If you can show me some radically different way to accomplish what I want to accomplish, great

If your main problem is implementing the Resource Server and also, you are open to totally different solutions, you can use Spring Boot's resource server auto configurations. This way you would have a ResourceServerConfiguration such as following:

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

With an application.yml config file in your src/main/resources:

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

You should add your client-id, client-secret and token-info-uri there. token-info-uri is the endpoint on Authorization Server that our resource server is going to consult about the validity of passed Access Tokens.

With these arrangements, if the client fire a request to, say, /api/greet API:

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

Our resource server will extract the Bearer access token from the request and send the following request to the authorization server to validate the access token:

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

If token was valid, authorization server send a 200 OK response with a JSON body like following:

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

Otherwise, it will return a 4xx Client Error.

This was a maven project with a pom.xml like following:

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

And a typical Application class:

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

You can check out the spring boot documentation on resource server auto configurations here.

like image 22
Ali Dehghani Avatar answered Oct 13 '22 03:10

Ali Dehghani