Banyak Tempat Masuk dalam Keselamatan Musim Semi

1. Gambaran keseluruhan

Dalam tutorial ringkas ini, kita akan melihat bagaimana menentukan beberapa titik masuk dalam aplikasi Spring Security .

Ini terutama memerlukan penentuan beberapa blok http dalam fail konfigurasi XML atau beberapa contoh HttpSecurity dengan memperluas kelas WebSecurityConfigurerAdapter berkali-kali.

2. Pergantungan Maven

Untuk pembangunan, kita memerlukan pergantungan berikut:

 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 

Versi terkini spring-boot-starter-security, spring-boot-starter-web, spring-boot-starter-thymeleaf, spring-boot-starter-test, spring-security-test boleh dimuat turun dari Maven Central.

3. Pelbagai Tempat Masuk

3.1. Pelbagai Tempat Masuk Dengan Elemen HTTP Pelbagai

Mari tentukan kelas konfigurasi utama yang akan menyimpan sumber pengguna:

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

Sekarang, mari kita lihat bagaimana kita dapat menentukan beberapa titik masuk dalam konfigurasi keselamatan kita.

Kami akan menggunakan contoh yang didorong oleh Pengesahan Asas di sini, dan kami akan memanfaatkan fakta bahawa Spring Security menyokong definisi pelbagai elemen HTTP dalam konfigurasi kami.

Semasa menggunakan konfigurasi Java, cara untuk menentukan pelbagai alam keselamatan adalah dengan memiliki beberapa kelas @Configuration yang memperluas kelas asas WebSecurityConfigurerAdapter - masing-masing dengan konfigurasi keselamatannya sendiri. Kelas-kelas ini boleh menjadi statik dan diletakkan di dalam konfigurasi utama.

Motivasi utama untuk memiliki beberapa titik masuk dalam satu aplikasi adalah jika terdapat berbagai jenis pengguna yang dapat mengakses bahagian aplikasi yang berlainan.

Mari tentukan konfigurasi dengan tiga titik masuk, masing-masing dengan izin dan mod pengesahan yang berbeza:

  • satu untuk pengguna pentadbiran yang menggunakan Pengesahan Asas HTTP
  • satu untuk pengguna biasa yang menggunakan pengesahan borang
  • dan satu untuk pengguna tetamu yang tidak memerlukan pengesahan

Titik masuk yang ditentukan untuk pengguna pentadbiran mengamankan URL borang / admin / ** untuk hanya membenarkan pengguna dengan peranan ADMIN dan memerlukan Pengesahan Asas HTTP dengan titik masuk jenis BasicAuthenticationEntryPoint yang ditetapkan menggunakan kaedah pengesahanEntryPoint () :

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

The @Order anotasi pada setiap kelas statik menunjukkan tertib yang tatarajah akan dipertimbangkan untuk mencari satu yang sesuai dengan URL yang diminta. The perintah nilai bagi setiap kelas mestilah unik.

Kacang jenis BasicAuthenticationEntryPoint memerlukan harta nama sebenar ditetapkan.

3.2. Pelbagai Titik Masuk, Elemen HTTP Sama

Seterusnya, mari tentukan konfigurasi untuk URL borang / pengguna / ** yang dapat diakses oleh pengguna biasa dengan peranan PENGGUNA menggunakan pengesahan borang:

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

Seperti yang kita lihat, cara lain untuk menentukan titik masuk, selain kaedah authenticationEntryPoint (), adalah menggunakan kaedah defaultAuthenticationEntryPointFor () . Ini dapat menentukan beberapa titik masuk yang sesuai dengan keadaan yang berbeza berdasarkan objek RequestMatcher .

The RequestMatcher antara muka mempunyai pelaksanaan berdasarkan jenis keadaan, seperti jalan yang hampir sama, jenis media atau regexp. Dalam contoh kami, kami telah menggunakan AntPathRequestMatch untuk menetapkan dua titik masuk yang berbeza untuk URL borang / pengguna / swasta / ** dan / pengguna / umum / ** .

Seterusnya, kita perlu menentukan kacang titik masuk dalam kelas konfigurasi statik yang sama:

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

Perkara utama di sini adalah bagaimana mengatur beberapa titik masuk ini - tidak semestinya perincian pelaksanaan masing-masing.

Dalam kes ini, titik masuk adalah jenis LoginUrlAuthenticationEntryPoint , dan menggunakan URL halaman log masuk yang berbeza: / userLogin untuk halaman log masuk yang mudah dan / userLoginWithWarning untuk halaman log masuk yang juga memaparkan peringatan ketika cuba mengakses URL pengguna / peribadi.

Konfigurasi ini juga memerlukan menentukan pemetaan / userLogin dan / userLoginWithWarning MVC dan dua halaman dengan borang log masuk standard.

Untuk pengesahan borang, sangat penting untuk diingat bahawa apa-apa URL yang diperlukan untuk konfigurasi, seperti URL pemprosesan log masuk juga perlu mengikuti format / pengguna / ** atau dikonfigurasikan sebaliknya agar dapat diakses.

Kedua-dua konfigurasi di atas akan mengalihkan ke URL / 403 jika pengguna tanpa peranan yang sesuai cuba mengakses URL yang dilindungi.

Berhati-hati untuk menggunakan nama yang unik untuk kacang walaupun mereka berada dalam kelas statik yang berbeza , jika tidak, satu akan mengatasi yang lain.

3.3. Elemen HTTP Baru, Tiada Titik Masuk

Akhirnya, mari kita tentukan konfigurasi ketiga untuk URL borang / tetamu / ** yang akan membolehkan semua jenis pengguna, termasuk yang tidak disahkan:

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

3.4. Konfigurasi XML

Mari kita lihat konfigurasi XML yang setara untuk ketiga-tiga contoh HttpSecurity di bahagian sebelumnya.

Seperti yang dijangkakan, ini akan mengandungi tiga XML yang berasingan blok.

Untuk URL / admin / ** konfigurasi XML akan menggunakan atribut entry-point-ref elemen http-basic :

Yang perlu diperhatikan di sini ialah jika menggunakan konfigurasi XML, peranannya mestilah dalam bentuk ROLE_ .

Konfigurasi untuk URL / user / ** harus dipecah menjadi dua blok http dalam xml kerana tidak ada setara langsung dengan kaedah defaultAuthenticationEntryPointFor () .

Konfigurasi untuk URL / pengguna / umum / ** adalah:

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

Kod sumber lengkap untuk contoh boleh didapati di GitHub. Untuk menjalankan aplikasi, lepaskan tag kelas permulaan MultipleEntryPointsApplication di pom.xml dan jalankan perintah mvn spring-boot: run , kemudian akses ke URL / multipleHttpLinks .

Perhatikan bahawa tidak mungkin keluar semasa menggunakan Pengesahan Asas HTTP, jadi anda harus menutup dan membuka semula penyemak imbas untuk menghapus pengesahan ini.

Untuk menjalankan ujian JUnit, gunakan ditakrifkan profil Maven entryPoints dengan arahan berikut:

mvn clean install -PentryPoints