Plusieurs points d'entrée dans Spring Security

1. Vue d'ensemble

Dans ce tutoriel rapide, nous allons voir comment définir plusieurs points d'entrée dans une application Spring Security .

Cela implique principalement de définir plusieurs blocs http dans un fichier de configuration XML ou plusieurs instances HttpSecurity en étendant plusieurs fois la classe WebSecurityConfigurerAdapter .

2. Dépendances de Maven

Pour le développement, nous aurons besoin des dépendances suivantes:

 org.springframework.boot spring-boot-starter-security 2.2.2.RELEASE   org.springframework.boot spring-boot-starter-web 2.2.2.RELEASE   org.springframework.boot spring-boot-starter-thymeleaf 2.2.2.RELEASE   org.springframework.boot spring-boot-starter-test 2.2.2.RELEASE   org.springframework.security spring-security-test 5.2.2.RELEASE 

Les dernières versions de spring-boot-starter-security, spring-boot-starter-web, spring-boot-starter-thymeleaf, spring-boot-starter-test, spring-security-test peuvent être téléchargées depuis Maven Central.

3. Points d'entrée multiples

3.1. Plusieurs points d'entrée avec plusieurs éléments HTTP

Définissons la classe de configuration principale qui contiendra une source utilisateur:

@Configuration @EnableWebSecurity public class MultipleEntryPointsSecurityConfig { @Bean public UserDetailsService userDetailsService() throws Exception { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User .withUsername("user") .password(encoder().encode("userPass")) .roles("USER").build()); manager.createUser(User .withUsername("admin") .password(encoder().encode("adminPass")) .roles("ADMIN").build()); return manager; } @Bean public PasswordEncoder encoder() { return new BCryptPasswordEncoder(); } }

Voyons maintenant comment nous pouvons définir plusieurs points d'entrée dans notre configuration de sécurité.

Nous allons utiliser ici un exemple basé sur l'authentification de base, et nous allons faire bon usage du fait que Spring Security prend en charge la définition de plusieurs éléments HTTP dans nos configurations.

Lors de l'utilisation de la configuration Java, la façon de définir plusieurs domaines de sécurité consiste à avoir plusieurs classes @Configuration qui étendent la classe de base WebSecurityConfigurerAdapter , chacune avec sa propre configuration de sécurité. Ces classes peuvent être statiques et placées dans la configuration principale.

La principale motivation pour avoir plusieurs points d'entrée dans une application est s'il existe différents types d'utilisateurs qui peuvent accéder à différentes parties de l'application.

Définissons une configuration avec trois points d'entrée, chacun avec des autorisations et des modes d'authentification différents:

  • un pour les utilisateurs administratifs utilisant l'authentification de base HTTP
  • un pour les utilisateurs réguliers qui utilisent l'authentification par formulaire
  • et un pour les utilisateurs invités qui ne nécessitent pas d'authentification

Le point d'entrée défini pour les utilisateurs administratifs sécurise les URL de la forme / admin / ** pour autoriser uniquement les utilisateurs avec un rôle ADMIN et nécessite une authentification HTTP de base avec un point d'entrée de type BasicAuthenticationEntryPoint qui est défini à l'aide de la méthode authenticationEntryPoint () :

@Configuration @Order(1) public static class App1ConfigurationAdapter extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/admin/**") .authorizeRequests().anyRequest().hasRole("ADMIN") .and().httpBasic().authenticationEntryPoint(authenticationEntryPoint()); } @Bean public AuthenticationEntryPoint authenticationEntryPoint(){ BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint(); entryPoint.setRealmName("admin realm"); return entryPoint; } }

L' annotation @Order sur chaque classe statique indique l'ordre dans lequel les configurations seront considérées pour en trouver une qui correspond à l'URL demandée. La valeur de la commande pour chaque classe doit être unique.

Le bean de type BasicAuthenticationEntryPoint nécessite que la propriété realName soit définie.

3.2. Plusieurs points d'entrée, même élément HTTP

Ensuite, définissons la configuration des URL de la forme / user / ** auxquelles les utilisateurs réguliers peuvent accéder avec un rôle USER en utilisant l'authentification par formulaire:

@Configuration @Order(2) public static class App2ConfigurationAdapter extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/user/**") .authorizeRequests().anyRequest().hasRole("USER") .and() // formLogin configuration .and() .exceptionHandling() .defaultAuthenticationEntryPointFor( loginUrlauthenticationEntryPointWithWarning(), new AntPathRequestMatcher("/user/private/**")) .defaultAuthenticationEntryPointFor( loginUrlauthenticationEntryPoint(), new AntPathRequestMatcher("/user/general/**")); } }

Comme nous pouvons le voir, une autre façon de définir des points d'entrée, en plus de la méthode authenticationEntryPoint (), consiste à utiliser la méthode defaultAuthenticationEntryPointFor () . Cela peut définir plusieurs points d'entrée qui correspondent à différentes conditions en fonction d'un objet RequestMatcher .

L' interface RequestMatcher a des implémentations basées sur différents types de conditions, telles que le chemin de correspondance, le type de média ou l'expression régulière. Dans notre exemple, nous avons utilisé AntPathRequestMatch pour définir deux points d'entrée différents pour les URL des formulaires / user / private / ** et / user / general / ** .

Ensuite, nous devons définir les beans points d'entrée dans la même classe de configuration statique:

@Bean public AuthenticationEntryPoint loginUrlauthenticationEntryPoint(){ return new LoginUrlAuthenticationEntryPoint("/userLogin"); } @Bean public AuthenticationEntryPoint loginUrlauthenticationEntryPointWithWarning(){ return new LoginUrlAuthenticationEntryPoint("/userLoginWithWarning"); }

Le point principal ici est de savoir comment configurer ces multiples points d'entrée - pas nécessairement les détails de mise en œuvre de chacun.

Dans ce cas, les points d'entrée sont tous deux de type LoginUrlAuthenticationEntryPoint et utilisent une URL de page de connexion différente: / userLogin pour une page de connexion simple et / userLoginWithWarning pour une page de connexion qui affiche également un avertissement lors de la tentative d'accès aux URL / user / private.

Cette configuration nécessitera également de définir les mappages / userLogin et / userLoginWithWarning MVC et deux pages avec un formulaire de connexion standard.

Pour l'authentification par formulaire, il est très important de se rappeler que toute URL nécessaire à la configuration, telle que l'URL de traitement de connexion, doit également suivre le format / user / ** ou être configurée pour être accessible.

Les deux configurations ci-dessus redirigeront vers une URL / 403 si un utilisateur sans le rôle approprié tente d'accéder à une URL protégée.

Veillez à utiliser des noms uniques pour les beans même s'ils sont dans des classes statiques différentes , sinon l'un remplacera l'autre.

3.3. Nouvel élément HTTP, pas de point d'entrée

Enfin, définissons la troisième configuration des URL de la forme / guest / ** qui permettra à tous les types d'utilisateurs, y compris les non authentifiés:

@Configuration @Order(3) public static class App3ConfigurationAdapter extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/guest/**").authorizeRequests().anyRequest().permitAll(); } }

3.4. Configuration XML

Jetons un coup d'œil à la configuration XML équivalente pour les trois instances HttpSecurity dans la section précédente.

Comme prévu, cela contiendra trois fichiers XML distincts blocs.

Pour les URL / admin / **, la configuration XML utilisera l' attribut entry-point-ref de l' élément http-basic :

Il est à noter que si vous utilisez la configuration XML, les rôles doivent être de la forme ROLE_ .

La configuration des URL / user / ** devra être divisée en deux blocs http en xml car il n'y a pas d'équivalent direct à la méthode defaultAuthenticationEntryPointFor () .

La configuration des URL / user / general / ** est:

  //form-login configuration    

For the /user/private/** URLs we can define a similar configuration:

  //form-login configuration    

For the /guest/** URLs we will have the http element:

Also important here is that at least one XML block must match the /** pattern.

4. Accessing Protected URLs

4.1. MVC Configuration

Let's create request mappings that match the URL patterns we have secured:

@Controller public class PagesController { @GetMapping("/admin/myAdminPage") public String getAdminPage() { return "multipleHttpElems/myAdminPage"; } @GetMapping("/user/general/myUserPage") public String getUserPage() { return "multipleHttpElems/myUserPage"; } @GetMapping("/user/private/myPrivateUserPage") public String getPrivateUserPage() { return "multipleHttpElems/myPrivateUserPage"; } @GetMapping("/guest/myGuestPage") public String getGuestPage() { return "multipleHttpElems/myGuestPage"; } @GetMapping("/multipleHttpLinks") public String getMultipleHttpLinksPage() { return "multipleHttpElems/multipleHttpLinks"; } }

The /multipleHttpLinks mapping will return a simple HTML page with links to the protected URLs:

Admin page User page Private user page Guest page

Each of the HTML pages corresponding to the protected URLs will have a simple text and a backlink:

Welcome admin! Back to links

4.2. Initializing the Application

We will run our example as a Spring Boot application, so let's define a class with the main method:

@SpringBootApplication public class MultipleEntryPointsApplication { public static void main(String[] args) { SpringApplication.run(MultipleEntryPointsApplication.class, args); } }

If we want to use the XML configuration, we also need to add the @ImportResource({“classpath*:spring-security-multiple-entry.xml”}) annotation to our main class.

4.3. Testing the Security Configuration

Let's set up a JUnit test class that we can use to test our protected URLs:

@RunWith(SpringRunner.class) @WebAppConfiguration @SpringBootTest(classes = MultipleEntryPointsApplication.class) public class MultipleEntryPointsTest { @Autowired private WebApplicationContext wac; @Autowired private FilterChainProxy springSecurityFilterChain; private MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac) .addFilter(springSecurityFilterChain).build(); } }

Next, let's test the URLs using the admin user.

When requesting the /admin/adminPage URL without an HTTP Basic Authentication, we should expect to receive an Unauthorized status code, and after adding the authentication the status code should be 200 OK.

If attempting to access the /user/userPage URL with the admin user, we should receive status 302 Forbidden:

@Test public void whenTestAdminCredentials_thenOk() throws Exception { mockMvc.perform(get("/admin/myAdminPage")).andExpect(status().isUnauthorized()); mockMvc.perform(get("/admin/myAdminPage") .with(httpBasic("admin", "adminPass"))).andExpect(status().isOk()); mockMvc.perform(get("/user/myUserPage") .with(user("admin").password("adminPass").roles("ADMIN"))) .andExpect(status().isForbidden()); }

Let's create a similar test using the regular user credentials to access the URLs:

@Test public void whenTestUserCredentials_thenOk() throws Exception { mockMvc.perform(get("/user/general/myUserPage")).andExpect(status().isFound()); mockMvc.perform(get("/user/general/myUserPage") .with(user("user").password("userPass").roles("USER"))) .andExpect(status().isOk()); mockMvc.perform(get("/admin/myAdminPage") .with(user("user").password("userPass").roles("USER"))) .andExpect(status().isForbidden()); }

In the second test, we can see that missing the form authentication will result in a status of 302 Found instead of Unauthorized, as Spring Security will redirect to the login form.

Finally, let's create a test in which we access the /guest/guestPage URL will all three types of authentication and verify we receive a status of 200 OK:

@Test public void givenAnyUser_whenGetGuestPage_thenOk() throws Exception { mockMvc.perform(get("/guest/myGuestPage")).andExpect(status().isOk()); mockMvc.perform(get("/guest/myGuestPage") .with(user("user").password("userPass").roles("USER"))) .andExpect(status().isOk()); mockMvc.perform(get("/guest/myGuestPage") .with(httpBasic("admin", "adminPass"))) .andExpect(status().isOk()); }

5. Conclusion

In this tutorial, we have demonstrated how to configure multiple entry points when using Spring Security.

Le code source complet des exemples est disponible à l'adresse over sur GitHub. Pour exécuter l'application, supprimez la mise en commentaire de la balise de classe de démarrage MultipleEntryPointsApplication dans le fichier pom.xml et exécutez la commande mvn spring-boot: run , puis accédez à l' URL / multipleHttpLinks .

Notez qu'il n'est pas possible de se déconnecter lors de l'utilisation de l'authentification de base HTTP, vous devrez donc fermer et rouvrir le navigateur pour supprimer cette authentification.

Pour exécuter le test JUnit, utilisez les points d' entrée de profil Maven définis avec la commande suivante:

mvn clean install -PentryPoints