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.

11
demandé sur Szymon Stepniak 2015-03-03 20:51:29

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.

11
répondu Szymon Stepniak 2015-08-22 10:17:24

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.

6
répondu Dave Syer 2015-03-05 08:38:05

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?

2
répondu turgos 2015-03-06 18:26:57