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:
Le serveur d'authentification fonctionne bien. Je ne parviens pas à configurer le serveur de ressources.
Voici quelques unes de mes configs. J'ai ce haricot:
@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;
}
}
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;
}
}
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;
}
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?
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>
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();
}
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 .
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
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.