Spring Boot avec Security OAuth2-comment utiliser resource server avec le formulaire de connexion web?
j'ai Botte De Printemps (1.2.1.RELEASE) application qui sert OAuth2 (2.0.6.RELEASE) autorisation et serveur de ressources dans une seule instance d'application. Il utilise personnalisé UserDetailsService
implémentation qui utilise MongoTemplate
pour rechercher des utilisateurs en MongoDB. Authentification avec grant_type=password
/oauth/token
fonctionne comme un charme, ainsi que de l'autorisation avec Authorization: Bearer {token}
en-tête lors de l'appel de ressources spécifiques.
maintenant je veux ajouter simple Ouauth confirmer la boîte de dialogue à le serveur, pour que je puisse authentifier et autoriser par exemple les appels Swagger UI dans api-docs pour les ressources protégées. Voici ce que j'ai fait jusqu'à présent:
@Configuration
@SessionAttributes("authorizationRequest")
class OAuth2ServerConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/oauth/confirm_access").setViewName("authorize");
}
@Configuration
@Order(2)
protected static class LoginConfig extends WebSecurityConfigurerAdapter implements ApplicationEventPublisherAware {
@Autowired
UserDetailsService userDetailsService
@Autowired
PasswordEncoder passwordEncoder
ApplicationEventPublisher applicationEventPublisher
@Bean
DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider()
provider.passwordEncoder = passwordEncoder
provider.userDetailsService = userDetailsService
return provider
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManagerBean())
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder())
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
//return super.authenticationManagerBean()
ProviderManager providerManager = new ProviderManager([daoAuthenticationProvider()], super.authenticationManagerBean())
providerManager.setAuthenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher))
return providerManager
}
@Bean
public PasswordEncoder passwordEncoder() {
new BCryptPasswordEncoder(5)
}
}
@Configuration
@EnableResourceServer
protected static class ResourceServer extends ResourceServerConfigurerAdapter {
@Value('${oauth.resourceId}')
private String resourceId
@Autowired
@Qualifier('authenticationManagerBean')
private AuthenticationManager authenticationManager
@Override
public void configure(HttpSecurity http) throws Exception {
http.setSharedObject(AuthenticationManager.class, authenticationManager)
http.csrf().disable()
http.httpBasic().disable()
http.formLogin().loginPage("/login").permitAll()
//http.authenticationProvider(daoAuthenticationProvider())
http.anonymous().and()
.authorizeRequests()
.antMatchers('/login/**').permitAll()
.antMatchers('/uaa/register/**').permitAll()
.antMatchers('/uaa/activate/**').permitAll()
.antMatchers('/uaa/password/**').permitAll()
.antMatchers('/uaa/account/**').hasAuthority('ADMIN')
.antMatchers('/api-docs/**').permitAll()
.antMatchers('/admin/**').hasAuthority('SUPERADMIN')
.anyRequest().authenticated()
//http.sessionManagement().sessionCreationPolicy(STATELESS)
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(resourceId)
resources.authenticationManager(authenticationManager)
}
}
@Configuration
@EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Value('${oauth.clientId}')
private String clientId
@Value('${oauth.secret:}')
private String secret
@Value('${oauth.resourceId}')
private String resourceId
@Autowired
@Qualifier('authenticationManagerBean')
private AuthenticationManager authenticationManager
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
return new JwtAccessTokenConverter();
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.checkTokenAccess("permitAll()")
oauthServer.allowFormAuthenticationForClients()
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter())
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(clientId)
.secret(secret)
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("USER", "ADMIN")
.scopes("read", "write", "trust")
.resourceIds(resourceId)
}
}
}
le problème principal est que je ne peux pas faire tourner les deux (formulaire de connexion web et token d'autorisation OAuth2 dans l'en-tête). Si ResourceServer
obtient une priorité plus élevée, alors l'Autorisation du token OAuth2 fonctionne, mais je ne peux pas me connecter en utilisant le formulaire web. D'un autre côté, si je mets la priorité supérieure à LoginConfig
classe, puis l'Autorisation du token OAuth2 s'arrête travailler.
étude de cas: le formulaire de connexion fonctionne, l'Autorisation du token OAuth2 ne fonctionne pas
j'ai compris que dans ce cas le problème est causé par la non-inscrits OAuth2AuthenticationProcessingFilter
. J'ai essayé d'enregistré manuellement dans ResourceServer.configure(HttpSecurity http)
méthode, mais ça n'a pas marché - je pouvais voir le filtre sur la liste FilterChain, mais ça n'a pas été déclenché. Ce n'était pas une bonne façon de le corriger, parce qu'il y a beaucoup d'autres magies faites pendant L'initialisation de ResourceServer donc j'ai déménagé à la seconde cas.
étude de cas: le formulaire de connexion ne fonctionne pas, l'Autorisation du token OAuth2 fonctionne
Dans ce cas, le principal problème est que par défaut UsernamePasswordAuthenticationFilter
impossible de trouver un AuthenticationProvider
exemple (dans ProviderManager
). Quand j'ai essayé de l'ajouter manuellement par:
http.authenticationProvide(daoAuthenticationProvider())
il en ait un, mais dans ce cas il n'y a pas de AuthenticationEventPublisher
l'authentification définie et réussie ne peut pas être publiée sur d'autres composants. Et en fait dans la prochaine itération il est remplacé par AnonymousAuthenticationToken
. C'est pourquoi j'ai essayé de définir manuellement AuthenticationManager
exemple DaoAuthenticationProvider
à l'intérieur:
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
//return super.authenticationManagerBean()
ProviderManager providerManager = new ProviderManager([daoAuthenticationProvider()], super.authenticationManagerBean())
providerManager.setAuthenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher))
return providerManager
}
je pensais que cela va fonctionner, mais il y a un autre problème avec la fourniture d' AuthenticationManager
instance vers les filtres enregistrés. Il s'avère que chaque filtre a authenticationManager
injecté manuellement en utilisant sharedObjects
composant:
authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
le problème ici est que vous n'êtes pas garanti d'avoir un jeu d'instance correct, parce qu'il y a un simple HashMap (le vérifier sur GitHub) utilisé pour stocker un objet partagé spécifique et il peut être modifié à tout moment. J'ai essayé de le mettre dans:
http.setSharedObject(AuthenticationManager.class, authenticationManager)
mais avant que j'arrive à l'endroit où il est lu, il est déjà remplacé par l'implémentation par défaut. Je l'ai vérifié avec le débogueur et il semble que pour chaque nouveau filtre il y a une nouvelle instance de gestionnaire d'authentification.
Ma question est: est-ce que je le fais correctement? Comment configurer le serveur d'autorisation avec les ressources le serveur intégré dans une application avec le formulaire de connexion (dialogue OAuth2) fonctionne-t-il? Peut-être que cela peut être fait d'une manière différente et beaucoup plus facile. Je serais reconnaissant pour toute aide.
3 réponses
Voici la solution au problème. Jetez un oeil à ce exemplaires Groovy
catégorie:
@Configuration
@EnableResourceServer
class ResourceServer extends ResourceServerConfigurerAdapter {
@Value('${oauth.resourceId}')
private String resourceId
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
http.httpBasic().disable()
http.requestMatchers().antMatchers('/admin/**', '/uaa/**')
.and().authorizeRequests()
.antMatchers('/uaa/authenticated/**').authenticated()
.antMatchers('/uaa/register/**').permitAll()
.antMatchers('/uaa/activate/**').permitAll()
.antMatchers('/uaa/password/**').permitAll()
.antMatchers('/uaa/auth/**').permitAll()
.antMatchers('/uaa/account/**').hasAuthority('ADMIN')
.antMatchers('/admin/**').hasAuthority('ADMIN')
.anyRequest().authenticated()
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(resourceId);
}
}
en gros, pour exécuter OAuth2.0 authentification parallèle à l'authentification de forme web, vous devez mettre
http.requestMatchers().antMatchers('/path/1/**', '/path/2/**')
à la classe de configuration. Ma configuration précédente a manqué cette partie importante donc seulement OAuth2.0 a participé au processus d'authentification.
Je ne pense pas que vous devriez essayer de configurer le login de formulaire ou http basic dans votre ResourceServerConfigurerAdapter
, et certainement pas si vous les avez déjà dans vos autres WebSecurityConfigurerAdapter
(vous le faire parce qu'ils sont par défaut). Cela pourrait fonctionner, mais les décisions d'authentification et d'accès sont tellement différentes pour une ressource protégée par OAuth2 et une interface utilisateur que je vous recommande de les garder séparées (comme elles le sont dans tous les exemples de github). Si vous suivez la recommandation et continuez avec les composants que vous avez déjà définis, le la clé pour obtenir ce droit est de savoir que les chaînes de filtrage sont essayées séquentiellement et le premier à correspondre gagne, donc seulement un d'entre eux va agir sur une requête donnée. Vous devez mettre les agendas de requêtes dans les deux chaînes (ou au moins celle avec l'ordre le plus bas), et assurez-vous qu'ils ne se chevauchent pas.
que faire si vous utilisez différents paramètres configurés avec une sécurité différente?
pour l'exemple ci-dessus, tout avec /uaa/** sécurisé avec WebSecurityConfigurerAdapter, et /api-docs/** avec ResourceServerConfigurerAdapter.
Dans ce cas, filtre chaînes toujours en conflit?