it-swarm.com.de

Spring-Sicherheit mit Oauth2- oder HTTP-Basic-Authentifizierung für dieselbe Ressource

Ich versuche, eine API mit Ressourcen zu implementieren, die entweder durch die Oauth2 OR Http-Basic-Authentifizierung geschützt werden.

Wenn ich zuerst den WebSecurityConfigurerAdapter lade, der die http-basic-Authentifizierung auf die Ressource anwendet, wird die Oauth2-Tokenauthentifizierung nicht akzeptiert. Und umgekehrt.

Beispielkonfigurationen: Dies wendet die http-basic-Authentifizierung auf alle Ressourcen/user/** an

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

Dies gilt für den Oauth-Token-Schutz für die 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')");
    }
}

Ich bin mir sicher, dass es einen magischen Code gibt, den ich vermisse und der Frühling anweist, beide zu versuchen, wenn der erste fehlgeschlagen ist.

Jede Hilfe wäre sehr dankbar.

21
user3613594

Ich habe es geschafft, diese Arbeit basierend auf den Hinweisen von Michael Resslers Antwort zu erhalten, jedoch mit einigen Verbesserungen. 

Mein Ziel war es, sowohl Basic Auth als auch Oauth auf den gleichen Ressourcenendpunkten zuzulassen, z. B./leafcase/123. Ich war längere Zeit aufgrund der Anordnung der filterChains gefangen (kann in FilterChainProxy.filterChains überprüft werden); Die Standardreihenfolge lautet wie folgt: 

  • Oauth-Authentifizierungsserver (falls im selben Projekt aktiviert) filterChains. Standardreihenfolge 0 (siehe AuthorizationServerSecurityConfiguration)
  • FilterChains des Ressourcenservers. Standardreihenfolge 3 (siehe ResourceServerConfiguration). Es verfügt über eine Request-Matcher-Logik, die mit anderen als den Oauth-Authentifizierungsendpunkten übereinstimmt (z. B./oauth/token,/oauth/authorize usw.). Siehe ResourceServerConfiguration $ NotOauthRequestMatcher.matches ()). 
  • Die filterChains, die config (HttpSecurity http) entsprechen - Standardreihenfolge 100, siehe WebSecurityConfigurerAdapter. 

Da die filterChains des Ressourcenservers höher ist als die von WebSecurityConfigurerAdapter konfigurierte filterchain und die erste mit praktisch jedem Ressourcenendpunkt übereinstimmt, ist die Oauth-Ressourcenserverlogik immer für jede Anforderung an Ressourcenendpunkte (selbst wenn die Anforderung den Header Authorization: Basic verwendet). Der Fehler, den Sie erhalten würden, ist:

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

Ich habe 2 Änderungen vorgenommen, um diese Arbeit zu erhalten:

Bestellen Sie zunächst den WebSecurityConfigurerAdapter höher als den Ressourcenserver (Reihenfolge 2 ist höher als Reihenfolge 3). 

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

Lassen Sie zweitens configure (HttpSecurity) einen Kunden-RequestMatcher verwenden, der nur mit "Authorization: Basic" übereinstimmt. 

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

Daher wird eine Basisauthentifizierungs-Ressourcenanforderung abgeglichen und verarbeitet, bevor die filterChain des Ressourcenservers die Möglichkeit hat, diese abzugleichen. Er behandelt auch NUR Autorisierung: Basisressourcenanforderung, daher werden alle Anforderungen mit Autorisierung: Träger durchgefiltert und dann von filterChain des Ressourcenservers behandelt (d. H., Oauths Filter tritt ein). Außerdem rangiert es niedriger als der AuthenticationServer (falls AuthenticationServer für dasselbe Projekt aktiviert ist), sodass die Filterkette von AuthenticaitonServer die Anforderung an/oauth/token usw. nicht verarbeiten kann. 

25
kca2ply

Dies könnte nahe an dem liegen, wonach Sie gesucht haben:

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

Das einzige, was dies nicht bietet, ist ein Weg, um "zurückzugreifen", wenn die Authentifizierung nicht erfolgreich ist.

Für mich ist dieser Ansatz sinnvoll. Wenn ein Benutzer die Anforderung über Basic auth direkt authentifiziert, ist OAuth nicht erforderlich. Wenn der Client derjenige ist, der agiert, benötigen wir diesen Filter, um die Anforderung ordnungsgemäß zu authentifizieren.

5
Michael Ressler

Sie können der Sicherheitsfilterkette einen BasicAuthenticationFilter hinzufügen, um OAuth2 OR Basic-Authentifizierungssicherheit für eine geschützte Ressource zu erhalten. Beispiel config ist unten ...

@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

Die von @cca2ply bereitgestellte Lösung funktioniert sehr gut. Mir ist aufgefallen, dass der Browser keine Herausforderung darstellt. Ich habe den Code ein wenig an Folgendes angepasst:

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

Durch die Verwendung von requestMatcher() und antMatcher() funktionierten die Dinge perfekt. Browser und HTTP-Clients fordern nun zuerst grundlegende Creds heraus, sofern noch nicht vorhanden. Wenn keine Anmeldeinformationen bereitgestellt werden, wird OAuth2 verwendet.

1

Ich glaube, dass es nicht möglich ist, beide Authentifizierungen zu haben. Sie können eine Basisauthentifizierung und eine Oauth2-Authentifizierung haben, jedoch für unterschiedliche Endpunkte. Die erste Konfiguration wird wie die zweite Konfiguration überwunden. In diesem Fall wird http basic verwendet.

1
raonirenosto

Und warum nicht andersherum? Umgehen Sie den Ressourcenserver einfach, wenn kein Token angeschlossen ist, und wenden Sie sich dann an die normale Sicherheitsfilterkette an. Dies ist übrigens der Filter des Ressourcenservers.

@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

Ich kann Ihnen kein vollständiges Beispiel geben, aber hier ein Hinweis zu Dig:

Spring Auth ist in etwa nur eine Kombination aus Anforderungsfilter, die Authentifizierungsdaten aus Anforderungsdaten (Header) extrahieren, und Authentifizierungsmanager, die ein Authentifizierungsobjekt für diese Authentifizierung bereitstellen.

Um Basic und Oauth unter derselben URL zu erhalten, benötigen Sie zwei Filter, die in der Filterkette BasicAuthenticationFilter und OAuth2AuthenticationProcessingFilter installiert sind.

Ich denke, das Problem ist, dass ConfiguringAdapters gut für einfachere Confs sind, da sie sich gegenseitig überschreiben. Als ersten Schritt versuchen Sie sich zu bewegen 

.httpBasic();

rufen Sie ResourceServerConfiguration auf. Sie müssen auch zwei verschiedene Auth-Manager angeben: einen für grundlegende Auth und einen für Oauth

0
zeldigas