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 une authentification Oauth2 ou 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 configurations: Cela 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 y a un morceau de code magique qui me manque, qui demande à Spring d’essayer les deux si le premier a échoué?

Toute aide serait grandement appréciée.

J’ai réussi à obtenir ce travail basé sur les indications de la réponse de Michael Ressler, mais avec quelques modifications.

Mon objective était d’autoriser les opérations d’authentification de base et Oauth sur les mêmes noeuds finaux de ressource, par exemple, / leafcase / 123. J’ai été piégé pendant un certain temps en raison de la commande des filterChains (peut être inspecté dans FilterChainProxy.filterChains); l’ordre par défaut est le suivant:

  • Le filterChains du serveur d’authentification Oauth (s’il est activé dans le même projet). 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 élément autre que les noeuds finaux d’authentification Oauth (par exemple, / oauth / token, / oauth / authorize, etc. Voir ResourceServerConfiguration $ NotOauthRequestMatcher.matches ()).
  • Le 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 haut 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) { Ssortingng 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 (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.

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) { Ssortingng 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 le client est celui qui agit, nous avons besoin de ce filtre pour intervenir et nous assurer que la demande est correctement authentifiée.

Je crois que ce 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 surmontera la seconde, dans ce cas, http basic sera utilisé.

Vous pouvez append un BasicAuthenticationFilter à la chaîne de filtres de sécurité pour obtenir la sécurité d’authentification OAuth2 OR Basic sur une ressource protégée. Exemple de configuration ci-dessous …

 @Configuration @EnableResourceServer public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManagerBean; @Override public void configure(HttpSecurity http) throws Exception { // @formatter:off final Ssortingng[] userEndpoints = { "/v1/api/airline" }; final Ssortingng[] 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); } } 

La solution @ kca2ply fournie fonctionne très bien. J’ai remarqué que le navigateur n’émettait pas de défi, alors j’ai modifié le code de la manière suivante:

 @Configuration @Order(2) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // @formatter:off http.anonymous().disable() .requestMatcher(request -> { Ssortingng 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.

Je ne peux pas vous donner un exemple complet, mais voici quelques conseils:

En gros, Spring auth est simplement une combinaison de filtre de requête qui extrait les données d’authentification de la requête (en-têtes) et du gestionnaire d’authentification qui fournit un object d’authentification pour cette authentification.

Donc, pour obtenir basic et oauth sur la même url, vous devez installer 2 filtres dans les chaînes de filtrage 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(); 

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

Et pourquoi ne pas faire l’inverse? Ignorez simplement le serveur de ressources si aucun jeton n’est connecté, 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") } } }