Punti di ingresso multipli in Spring Security

1. Panoramica

In questo breve tutorial, daremo un'occhiata a come definire più punti di ingresso in un'applicazione Spring Security .

Ciò comporta principalmente la definizione di più blocchi http in un file di configurazione XML o più istanze di HttpSecurity estendendo più volte la classe WebSecurityConfigurerAdapter .

2. Dipendenze di Maven

Per lo sviluppo, avremo bisogno delle seguenti dipendenze:

 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 

Le ultime versioni di spring-boot-starter-security, spring-boot-starter-web, spring-boot-starter-thymeleaf, spring-boot-starter-test, spring-security-test possono essere scaricate da Maven Central.

3. Punti di ingresso multipli

3.1. Più punti di ingresso con più elementi HTTP

Definiamo la classe di configurazione principale che conterrà un'origine utente:

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

Ora, diamo un'occhiata a come possiamo definire più punti di ingresso nella nostra configurazione di sicurezza.

Useremo un esempio guidato dall'autenticazione di base qui e faremo buon uso del fatto che Spring Security supporta la definizione di più elementi HTTP nelle nostre configurazioni.

Quando si utilizza la configurazione Java, il modo per definire più ambiti di sicurezza consiste nell'avere più classi @Configuration che estendono la classe base WebSecurityConfigurerAdapter , ciascuna con la propria configurazione di sicurezza. Queste classi possono essere statiche e collocate all'interno della configurazione principale.

La motivazione principale per avere più punti di ingresso in un'unica applicazione è se ci sono diversi tipi di utenti che possono accedere a parti diverse dell'applicazione.

Definiamo una configurazione con tre punti di ingresso, ciascuno con autorizzazioni e modalità di autenticazione differenti:

  • uno per gli utenti amministrativi che utilizzano l'autenticazione di base HTTP
  • uno per gli utenti regolari che utilizzano l'autenticazione del modulo
  • e uno per gli utenti guest che non richiedono l'autenticazione

Il punto di ingresso definito per gli utenti amministrativi protegge gli URL del modulo / admin / ** per consentire solo agli utenti con un ruolo di ADMIN e richiede l'autenticazione HTTP di base con un punto di ingresso di tipo BasicAuthenticationEntryPoint impostato utilizzando il metodo 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' annotazione @Order su ciascuna classe statica indica l'ordine in cui le configurazioni verranno considerate per trovarne una che corrisponda all'URL richiesto. Il valore dell'ordine per ogni classe deve essere univoco.

Il bean di tipo BasicAuthenticationEntryPoint richiede l' impostazione della proprietà realName .

3.2. Più punti di ingresso, stesso elemento HTTP

Successivamente, definiamo la configurazione per gli URL del modulo / utente / ** a cui possono accedere gli utenti normali con un ruolo USER utilizzando l'autenticazione del modulo:

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

Come possiamo vedere, un altro modo per definire i punti di ingresso, oltre al metodo authenticationEntryPoint (), è utilizzare il metodo defaultAuthenticationEntryPointFor () . Ciò può definire più punti di ingresso che soddisfano condizioni diverse in base a un oggetto RequestMatcher .

L' interfaccia RequestMatcher ha implementazioni basate su diversi tipi di condizioni, come percorso di corrispondenza, tipo di supporto o regexp. Nel nostro esempio, abbiamo utilizzato AntPathRequestMatch per impostare due diversi punti di ingresso per gli URL dei moduli / user / private / ** e / user / general / ** .

Successivamente, dobbiamo definire i bean dei punti di ingresso nella stessa classe di configurazione statica:

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

Il punto principale qui è come impostare questi punti di ingresso multipli, non necessariamente i dettagli di implementazione di ciascuno.

In questo caso, i punti di ingresso sono entrambi di tipo LoginUrlAuthenticationEntryPoint e utilizzano un URL della pagina di accesso diverso: / userLogin per una pagina di accesso semplice e / userLoginWithWarning per una pagina di accesso che visualizza anche un avviso quando si tenta di accedere agli URL / utente / privati.

Questa configurazione richiederà anche la definizione delle mappature / userLogin e / userLoginWithWarning MVC e due pagine con un modulo di login standard.

Per l'autenticazione del modulo, è molto importante ricordare che qualsiasi URL necessario per la configurazione, come l'URL di elaborazione del login, deve anche seguire il formato / utente / ** o essere configurato in altro modo per essere accessibile.

Entrambe le configurazioni precedenti reindirizzeranno a un URL / 403 se un utente senza il ruolo appropriato tenta di accedere a un URL protetto.

Fai attenzione a usare nomi univoci per i bean anche se si trovano in classi statiche diverse , altrimenti uno sovrascriverà l'altro.

3.3. Nuovo elemento HTTP, nessun punto di ingresso

Infine, definiamo la terza configurazione per gli URL della forma / guest / ** che consentirà tutti i tipi di utenti, compresi quelli non autenticati:

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

3.4. Configurazione XML

Diamo un'occhiata alla configurazione XML equivalente per le tre istanze di HttpSecurity nella sezione precedente.

Come previsto, questo conterrà tre XML separati blocchi.

Per gli URL / admin / ** la configurazione XML utilizzerà l' attributo entry-point-ref dell'elemento http-basic :

Da notare che se si utilizza la configurazione XML, i ruoli devono essere nella forma ROLE_ .

La configurazione per gli URL / user / ** dovrà essere suddivisa in due blocchi http in xml perché non esiste un equivalente diretto al metodo defaultAuthenticationEntryPointFor () .

La configurazione per URL / utente / generale / ** è:

  //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.

Il codice sorgente completo per gli esempi può essere trovato su GitHub. Per eseguire l'applicazione, rimuovere il commento dal tag di classe di avvio MultipleEntryPointsApplication nel pom.xml ed eseguire il comando mvn spring-boot: run , quindi accedere all'URL / multipleHttpLinks .

Si noti che non è possibile disconnettersi quando si utilizza l'autenticazione di base HTTP, quindi sarà necessario chiudere e riaprire il browser per rimuovere questa autenticazione.

Per eseguire il test JUnit, utilizzare entryPoints del profilo Maven definito con il seguente comando:

mvn clean install -PentryPoints