web-dev-qa-db-fra.com

Protéger REST API avec OAuth2: Erreur lors de la création d'un bean avec le nom 'scopedTarget.oauth2ClientContext': Scope 'session' n'est pas actif

Je travaille depuis quelques jours pour essayer de mettre en œuvre la protection oauth2 sur une API REST. J'ai essayé une multitude de configurations différentes mais je n'ai toujours pas réussi à le faire fonctionner. 

Je prouve le code que j'ai actuellement, mais je ne suis nullement marié à cette implémentation. Si vous pouvez me montrer une manière radicalement différente d'accomplir ce que je veux accomplir, c'est bien. 

Mon flux ressemble à ceci:

  1. Le client vérifie le serveur Auth, obtient un jeton.
  2. Le client envoie un jeton au serveur de ressources.
  3. Resource Server utilise Auth Server pour s'assurer que le jeton est valide.

Le serveur d'authentification fonctionne bien. Je ne parviens pas à configurer le serveur de ressources.

Configs sur le serveur de ressources

Voici quelques unes de mes configs. J'ai ce haricot: 

Modèle Ouath Rest

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

Configuration de sécurité

J'utilise ce haricot dans ma configuration de sécurité principale dans 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;
    }
}

Trucs supplémentaires dans la configuration de sécurité

Aaand, certains des haricots que je crois sont moins pertinents, mais les voici au cas où vous en auriez besoin:

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

Erreur

Le serveur de ressources démarre correctement. Le client obtient son jeton d’authentification auprès du ServeurAuthenticationServer.com et l’envoie dans l’en-tête de la demande à une URL d’API. Et j'obtiens l'erreur suivante:

Statut HTTP 500 - Erreur lors de la création du bean avec le nom 'scopedTarget.oauth2ClientContext': Scope 'session' n'est pas actif pour le thread actuel; Pensez à définir un proxy délimité pour ce bean si vous souhaitez le référencer à partir d'un singleton. L'exception imbriquée est Java.lang.IllegalStateException: aucune requête liée à un thread n'a été trouvée: faites-vous référence à des attributs de requête en dehors d'une requête Web réelle ou traitez-vous une requête en dehors du thread à l'origine de la réception? Si vous opérez réellement dans une requête Web et continuez à recevoir ce message, votre code s'exécute probablement en dehors de DispatcherServlet/DispatcherPortlet: Dans ce cas, utilisez RequestContextListener ou RequestContextFilter pour exposer la requête en cours.

Rapport d'exception

Erreur lors de la création du bean nommé 'scopedTarget.oauth2ClientContext': Scope 'session' n'est pas actif pour le thread actuel; Pensez à définir un proxy délimité pour ce bean si vous souhaitez le référencer à partir d'un singleton. L'exception imbriquée est Java.lang.IllegalStateException: aucune requête liée à un thread n'a été trouvée: faites-vous référence à des attributs de requête en dehors d'une requête Web réelle ou traitez-vous une requête en dehors du thread à l'origine de la réception? Si vous opérez réellement dans une requête Web et continuez à recevoir ce message, votre code s'exécute probablement en dehors de DispatcherServlet/DispatcherPortlet: Dans ce cas, utilisez RequestContextListener ou RequestContextFilter pour exposer la requête en cours.

Le serveur a rencontré une erreur interne qui l'a empêché de répondre à cette demande.

        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)

J'ai essayé beaucoup de configurations différentes, cherché une tonne de ressources en ligne et je ne suis nulle part. Est-ce que j'utilise les bonnes classes? Avez-vous une idée de la configuration à modifier? 

17
Joey

J'ai fini par résoudre ce problème après avoir consulté la documentation de Spring.

Il s'est avéré que le contexte de la portée n'existait pas dans mon application, car je ne l'avais pas initialisé.

Je l'ai initialisé en ajoutant cet écouteur:

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

Un moyen encore plus simple d'activer l'écouteur de contexte de requête consiste à ajouter une annotation de bean dans votre application. 

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

Je prouve le code que j'ai en ce moment, mais je ne suis en aucun cas marié. à cette mise en œuvre. Si vous pouvez me montrer des noms radicalement différents façon d'accomplir ce que je veux accomplir, super

Si votre problème principal est la mise en œuvre du serveur de ressources et que vous êtes ouvert à des solutions totalement différentes, vous pouvez utiliser les configurations automatiques du serveur de ressources de Spring Boot. De cette façon, vous auriez une ResourceServerConfiguration comme suit:

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

Avec un fichier de configuration application.yml dans votre src/main/resources:

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

Vous devriez y ajouter votre client-id, client-secret et token-info-uri. token-info-uri est le noeud final du serveur d'autorisations que notre serveur de ressources va consulter sur la validité des jetons d'accès} passés. 

Avec ces arrangements, si le client déclenche une demande vers, disons, /api/greet API:

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

Notre serveur de ressources extraira le jeton d'accès porteur de la requête et enverra la requête suivante au serveur d'autorisations pour valider le jeton d'accès:

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

Si le jeton était valide, le serveur d'autorisation envoie une réponse 200 OK avec un corps JSON comme suit:

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

Sinon, il retournera un 4xx Client Error.

C'était un projet maven avec un pom.xml comme suit:

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

Et une classe Application typique:

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

Vous pouvez consulter la documentation de Spring Spring sur les configurations automatiques du serveur de ressources ici .

1
Ali Dehghani

J'ai rencontré le même problème lorsque j'utilisais Spring-Boot 1.4.1 avec spock-spring 1.1-groovy-2.4-rc-2. La solution la plus simple consiste à utiliser Spock 1.0.

Il y a déjà un bug signalé:
https://github.com/spockframework/spock/issues/655

0
rgrebski

Je pense que la racine du problème est que vous créez les opérateurs OAuth2ClientAuthenticationProcessingFilter et OAuth2ClientContextFilter avec new.

Si vous regardez le 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)

il y a une chaîne qui va de OAuth2ClientAuthenticationProcessingFilter à JdkDynamicAopProxy et qui tente d'obtenir le haricot. Et je peux supposer que, parce que le bean a été créé à partir du conteneur Spring, il ne peut pas l'obtenir à partir de l'étendue de la session.

Essayez d'encapsuler vos filtres dans l'annotation @Bean afin de les mettre en contexte. De plus, j'estime qu'il vaut la peine de définir la bonne portée: la request conviendrait mieux ici.

0
ikryvorotenko