Beberapa Titik Masuk dalam Keamanan Musim Semi

1. Ikhtisar

Dalam tutorial singkat ini, kita akan melihat bagaimana mendefinisikan banyak titik masuk dalam aplikasi Keamanan Musim Semi .

Ini terutama memerlukan penentuan beberapa blok http dalam file konfigurasi XML atau beberapa instance HttpSecurity dengan memperluas kelas WebSecurityConfigurerAdapter beberapa kali.

2. Ketergantungan Maven

Untuk pengembangan, kami membutuhkan dependensi 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 terbaru dari spring-boot-starter-security, spring-boot-starter-web, spring-boot-starter-thymeleaf, spring-boot-starter-test, spring-security-test dapat diunduh dari Maven Central.

3. Beberapa Titik Masuk

3.1. Beberapa Titik Masuk Dengan Beberapa Elemen HTTP

Mari tentukan kelas konfigurasi utama yang akan menampung 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 mendefinisikan banyak titik masuk dalam konfigurasi keamanan kita.

Kami akan menggunakan contoh yang didorong oleh Autentikasi Dasar di sini, dan kami akan memanfaatkan fakta bahwa Spring Security mendukung definisi beberapa elemen HTTP dalam konfigurasi kami.

Saat menggunakan konfigurasi Java, cara untuk menentukan beberapa ranah keamanan adalah dengan memiliki beberapa kelas @Configuration yang memperluas kelas dasar WebSecurityConfigurerAdapter - masing-masing dengan konfigurasi keamanannya sendiri. Kelas-kelas ini bisa statis dan ditempatkan di dalam konfigurasi utama.

Motivasi utama untuk memiliki banyak titik masuk dalam satu aplikasi adalah jika terdapat berbagai jenis pengguna yang dapat mengakses bagian aplikasi yang berbeda.

Mari tentukan konfigurasi dengan tiga titik masuk, masing-masing dengan izin dan mode otentikasi yang berbeda:

  • satu untuk pengguna administratif yang menggunakan Autentikasi Dasar HTTP
  • satu untuk pengguna biasa yang menggunakan otentikasi formulir
  • dan satu untuk pengguna tamu yang tidak memerlukan otentikasi

Titik masuk yang ditentukan untuk pengguna administratif mengamankan URL formulir / admin / ** untuk hanya mengizinkan pengguna dengan peran ADMIN dan memerlukan Autentikasi Dasar HTTP dengan titik masuk jenis BasicAuthenticationEntryPoint yang disetel menggunakan metode otentikasiEntryPoint () :

@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 penjelasan pada setiap kelas statis menunjukkan urutan di mana konfigurasi akan dianggap untuk menemukan satu yang cocok dengan URL yang diminta. Nilai pesanan untuk setiap kelas harus unik.

Kacang tipe BasicAuthenticationEntryPoint membutuhkan properti realName disetel.

3.2. Beberapa Titik Masuk, Elemen HTTP yang Sama

Selanjutnya, mari kita tentukan konfigurasi untuk URL dengan format / pengguna / ** yang dapat diakses oleh pengguna biasa dengan peran USER menggunakan otentikasi formulir:

@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 bisa kita lihat, cara lain untuk menentukan titik masuk, selain metode authenticationEntryPoint (), adalah dengan menggunakan metode defaultAuthenticationEntryPointFor () . Ini dapat menentukan beberapa titik masuk yang cocok dengan kondisi berbeda berdasarkan objek RequestMatcher .

The RequestMatcher antarmuka memiliki implementasi berdasarkan pada berbagai jenis kondisi, seperti jalan yang cocok, jenis media atau regexp. Dalam contoh kami, kami telah menggunakan AntPathRequestMatch untuk menyetel dua titik masuk berbeda untuk URL dengan format / user / private / ** dan / user / general / ** .

Selanjutnya, kita perlu mendefinisikan kacang titik masuk dalam kelas konfigurasi statis yang sama:

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

Poin utama di sini adalah bagaimana mengatur beberapa titik masuk ini - tidak harus detail implementasi masing-masing.

Dalam kasus ini, titik masuk keduanya berjenis LoginUrlAuthenticationEntryPoint , dan menggunakan URL halaman login yang berbeda: / userLogin untuk halaman login sederhana dan / userLoginWithWarning untuk halaman login yang juga menampilkan peringatan saat mencoba mengakses URL / user / private.

Konfigurasi ini juga memerlukan pendefinisian pemetaan MVC / userLogin dan / userLoginWithWarning dan dua halaman dengan formulir login standar.

Untuk otentikasi formulir, sangat penting untuk diingat bahwa setiap URL yang diperlukan untuk konfigurasi, seperti URL pemrosesan login juga harus mengikuti format / user / ** atau dikonfigurasi agar dapat diakses.

Kedua konfigurasi di atas akan dialihkan ke URL / 403 jika pengguna tanpa peran yang sesuai mencoba mengakses URL yang dilindungi.

Berhati-hatilah untuk menggunakan nama unik untuk kacang meskipun mereka berada dalam kelas statis yang berbeda , jika tidak salah satu akan menimpa yang lain.

3.3. Elemen HTTP Baru, Tanpa Titik Masuk

Terakhir, mari kita tentukan konfigurasi ketiga untuk URL dengan bentuk / guest / ** yang akan mengizinkan semua jenis pengguna, termasuk yang tidak diautentikasi:

@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 tiga instance HttpSecurity di bagian sebelumnya.

Seperti yang diharapkan, ini akan berisi tiga XML terpisah blok.

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

Catatan di sini adalah jika menggunakan konfigurasi XML, peran harus dalam bentuk ROLE_ .

Konfigurasi untuk / user / ** URL harus dipecah menjadi dua blok http di xml karena tidak ada padanan langsung dengan metode 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.

Kode sumber lengkap untuk contoh dapat ditemukan di GitHub. Untuk menjalankan aplikasi, hapus tanda komentar pada tag kelas awal MultipleEntryPointsApplication di pom.xml dan jalankan perintah mvn spring-boot: run , lalu akses URL / multipleHttpLinks .

Perhatikan bahwa tidak mungkin untuk keluar saat menggunakan Otentikasi Dasar HTTP, jadi Anda harus menutup dan membuka kembali browser untuk menghapus otentikasi ini.

Untuk menjalankan pengujian JUnit, gunakan entryPoints profil Maven yang ditentukan dengan perintah berikut:

mvn clean install -PentryPoints