web-dev-qa-db-fra.com

Sécurité Spring avec authentification Oauth2 ou Http-Basic pour la même ressource

Je tente d'implémenter une API avec des ressources protégées par l'authentification Oauth2 OR Http-Basic.

Lorsque je charge WebSecurityConfigurerAdapter qui applique d'abord l'authentification http-basic à la ressource, l'authentification par jeton Oauth2 n'est pas acceptée. Et vice versa.

Exemples de configuration: Ceci applique l'authentification http-basic à toutes les ressources/user/**

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private LoginApi loginApi;

    @Autowired
    public void setLoginApi(LoginApi loginApi) {
        this.loginApi = loginApi;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new PortalUserAuthenticationProvider(loginApi));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/users/**").authenticated()
                .and()
            .httpBasic();
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

Ceci applique la protection des jetons oauth à la ressource/user/**

@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .requestMatchers().antMatchers("/users/**")
        .and()
            .authorizeRequests()
                .antMatchers("/users/**").access("#oauth2.clientHasRole('ROLE_CLIENT') and #oauth2.hasScope('read')");
    }
}

Je suis sûr qu'il me manque un morceau de code magique qui indique au printemps d'essayer les deux si le premier a échoué?

Toute aide serait grandement appréciée.

21
user3613594

J'ai réussi à obtenir ce travail en me basant sur les astuces de Michael Ressler, mais avec quelques ajustements. 

Mon objectif était d'autoriser les autorisations de base et Oauth sur les mêmes points de terminaison de ressources, par exemple,/leafcase/123. J'ai été pris au piège pendant un certain temps en raison de l'ordre des filterChains (peut être inspecté dans FilterChainProxy.filterChains); l'ordre par défaut est le suivant: 

  • Oauth authentification serveur (si activé dans le même projet) de filterChains. ordre par défaut 0 (voir AuthorizationServerSecurityConfiguration)
  • FilterChains du serveur de ressources Oauth. ordre par défaut 3 (voir ResourceServerConfiguration). Il a une logique d’appariement de requêtes qui correspond à tout autre point de terminaison d’authentification Oauth (par exemple,/oauth/token,/oauth/authorize, etc. Voir ResourceServerConfiguration $ NotOauthRequestMatcher.matches ()). 
  • FilterChains qui correspond à config (HttpSecurity http) - ordre par défaut 100, voir WebSecurityConfigurerAdapter. 

Comme la chaîne filterChains du serveur de ressources est supérieure à celle de WebSecurityConfigurerAdapter et qu'elle correspond à pratiquement tous les points de terminaison de ressources, la logique du serveur de ressources Oauth se déclenche toujours pour toute demande aux points de terminaison de ressources (même si la demande utilise l'en-tête Authorization: Basic). L'erreur que vous obtiendrez est:

{
    "error": "unauthorized",
    "error_description": "Full authentication is required to access this resource"
}

J'ai fait 2 changements pour obtenir ce travail:

Tout d'abord, commandez WebSecurityConfigurerAdapter plus élevé que le serveur de ressources (l'ordre 2 est supérieur à l'ordre 3). 

@Configuration
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

Deuxièmement, laissez configure (HttpSecurity) utiliser un client RequestMatcher qui correspond uniquement à "Authorization: Basic". 

@Override
protected void configure(HttpSecurity http) throws Exception {

    http
        .anonymous().disable()
        .requestMatcher(new BasicRequestMatcher())
        .authorizeRequests()
            .anyRequest().authenticated()
            .and()
        .httpBasic()
             .authenticationEntryPoint(oAuth2AuthenticationEntryPoint())
            .and()
        // ... other stuff
 }
 ...
 private static class BasicRequestMatcher implements RequestMatcher {
    @Override
    public boolean matches(HttpServletRequest request) {
        String auth = request.getHeader("Authorization");
        return (auth != null && auth.startsWith("Basic"));
    }
 }

En conséquence, il correspond et traite une demande de ressource d'authentification de base avant que la chaîne de filtrage filterChain du serveur de ressources ait une chance de la faire correspondre. Il traite également UNIQUEMENT les requêtes Authorizaiton: Basic, si toutes les requêtes avec Authorization: Bearer échouent, puis sont gérées par filterChain du serveur de ressources (c'est-à-dire que le filtre d'Oauth entre en action). De plus, il est moins bien classé que AuthenticationServer (si AuthenticationServer est activé sur le même projet), ce qui n’empêche pas la chaîne de filtres d’Authenticaiton Server de traiter la demande adressée à/oauth/token, etc. 

25
kca2ply

Cela peut être proche de ce que vous cherchiez:

@Override
public void configure(HttpSecurity http) throws Exception {
    http.requestMatcher(new OAuthRequestedMatcher())
    .authorizeRequests()
        .anyRequest().authenticated();
}

private static class OAuthRequestedMatcher implements RequestMatcher {
    @Override
    public boolean matches(HttpServletRequest request) {
        String auth = request.getHeader("Authorization");
        // Determine if the client request contained an OAuth Authorization
        return (auth != null) && auth.startsWith("Bearer");
    }
}

La seule chose que cela ne fournit pas est un moyen de "se replier" si l'authentification échoue.

Pour moi, cette approche a du sens. Si un utilisateur fournit directement une authentification à la demande via une authentification de base, alors OAuth n'est pas nécessaire. Si c'est le client qui agit, nous avons besoin de ce filtre pour nous assurer que la demande est correctement authentifiée.

5
Michael Ressler

Vous pouvez ajouter un filtre BasicAuthenticationFilter à la chaîne de filtres de sécurité pour obtenir la sécurité d'authentification de base OAuth2 OR sur une ressource protégée. Exemple de configuration est ci-dessous ...

@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManagerBean;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        final String[] userEndpoints = {
            "/v1/api/airline"
        };

        final String[] adminEndpoints = {
                "/v1/api/jobs**"
            };

        http
            .requestMatchers()
                .antMatchers(userEndpoints)
                .antMatchers(adminEndpoints)
                .antMatchers("/secure/**")
                .and()
            .authorizeRequests()
                .antMatchers("/secure/**").authenticated()
                .antMatchers(userEndpoints).hasRole("USER")
                .antMatchers(adminEndpoints).hasRole("ADMIN");

        // @formatter:on
        http.addFilterBefore(new BasicAuthenticationFilter(authenticationManagerBean),
                UsernamePasswordAuthenticationFilter.class);
    }

}
2
httPants

La solution fournie par @ kca2ply fonctionne très bien. J'ai remarqué que le navigateur n'émettait pas de défi, alors j'ai légèrement modifié le code:

@Configuration
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {

    // @formatter:off
    http.anonymous().disable()
      .requestMatcher(request -> {
          String auth = request.getHeader(HttpHeaders.AUTHORIZATION);
          return (auth != null && auth.startsWith("Basic"));
      })
      .antMatcher("/**")
      .authorizeRequests().anyRequest().authenticated()
    .and()
      .httpBasic();
    // @formatter:on
  }

  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
    .withUser("user").password("password").roles("USER");
  }
}

Utiliser à la fois requestMatcher() et antMatcher() a parfaitement fonctionné. Les navigateurs et les clients HTTP vont maintenant contester d'abord pour les crédits de base s'ils ne sont pas déjà fournis. Si aucun identifiant n'est fourni, il passe à OAuth2.

1
Ryan J. McDonough

Je crois qu'il n'est pas possible d'avoir les deux authentifications. Vous pouvez avoir une authentification de base et une authentification oauth2, mais pour des points de terminaison distincts. Comme vous l'avez fait, la première configuration vaincra la seconde, dans ce cas, http basic sera utilisé.

1
raonirenosto

Et pourquoi ne pas faire l'inverse? Ignorez simplement le serveur de ressources s'il n'y a pas de jeton attaché, puis revenez à la chaîne de filtrage de sécurité normale. C’est d’ailleurs le filtre de serveur de ressources qui s’arrête.

@Configuration
@EnableResourceServer
class ResourceServerConfig : ResourceServerConfigurerAdapter() {


    @Throws(Exception::class)
    override fun configure(resources: ResourceServerSecurityConfigurer) {
        resources.resourceId("aaa")
    }

    /**
     * Resources exposed via oauth. As we are providing also local user interface they are also accessible from within.
     */
    @Throws(Exception::class)
    override fun configure(http: HttpSecurity) {
        http.requestMatcher(BearerAuthorizationHeaderMatcher())
                .authorizeRequests()
                .anyRequest()
                .authenticated()
    }

    private class BearerAuthorizationHeaderMatcher : RequestMatcher {
        override fun matches(request: HttpServletRequest): Boolean {
            val auth = request.getHeader("Authorization")
            return auth != null && auth.startsWith("Bearer")
        }
    }

}
0
kboom

Je ne peux pas vous donner un exemple complet, mais voici quelques astuces pour Dig:

En gros, l'autorisation de ressort est simplement une combinaison de filtre de requête qui extrait les données d'authentification de la requête (en-têtes) et d'un gestionnaire d'authentification fournissant un objet d'authentification pour cette autorisation.

Donc, pour obtenir basic et oauth à la même URL, vous devez installer deux filtres dans la chaîne de filtres BasicAuthenticationFilter et OAuth2AuthenticationProcessingFilter.

Je pense que le problème est que ConfiguringAdapters est bon pour des confs plus simples car ils ont tendance à se remplacer les uns les autres. Donc, dans un premier temps, essayez de vous déplacer 

.httpBasic();

appelez à ResourceServerConfiguration Notez que vous devez également fournir 2 gestionnaires d’authentification différents: un pour l’authentification de base et un pour oauth.

0
zeldigas