Множество входни точки в пролетната сигурност

1. Общ преглед

В този бърз урок ще разгледаме как да дефинираме множество входни точки в приложение Spring Spring .

Това включва основно дефиниране на множество http блокове в XML конфигурационен файл или множество екземпляри на HttpSecurity чрез разширяване на класа WebSecurityConfigurerAdapter няколко пъти.

2. Зависимости на Maven

За разработка ще ни трябват следните зависимости:

 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 

Най-новите версии на spring-boot-starter-security, spring-boot-starter-web, spring-boot-starter-thymeleaf, spring-boot-starter-test, spring-security-test могат да бъдат изтеглени от Maven Central.

3. Множество входни точки

3.1. Множество входни точки с множество HTTP елементи

Нека дефинираме основния конфигурационен клас, който ще съдържа потребителски източник:

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

Сега нека разгледаме как можем да дефинираме множество точки за вход в нашата конфигурация за сигурност.

Тук ще използваме пример, управляван от Basic Authentication, и ще използваме добре факта, че Spring Security поддържа дефиницията на множество HTTP елементи в нашите конфигурации.

Когато се използва конфигурация на Java, начинът за дефиниране на множество области на защита е да има множество класове @Configuration , които разширяват базовия клас WebSecurityConfigurerAdapter - всеки със собствена конфигурация на защита. Тези класове могат да бъдат статични и да се поставят вътре в основната конфигурация.

Основната мотивация за наличието на множество входни точки в едно приложение е, ако има различни типове потребители, които имат достъп до различни части от приложението.

Нека дефинираме конфигурация с три входни точки, всяка с различни разрешения и режими за удостоверяване:

  • един за административни потребители, използващи HTTP Basic Authentication
  • един за редовни потребители, които използват удостоверяване на формуляр
  • и един за гостуващи потребители, които не изискват удостоверяване

Входната точка, дефинирана за административни потребители, защитава URL адреси на формуляра / admin / **, за да разреши само на потребители с роля на ADMIN и изисква HTTP Basic Authentication с входна точка от тип BasicAuthenticationEntryPoint, която е зададена чрез метода 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; } }

В @Order анотация на всеки статичен клас показва реда, по който ще се счита за конфигурациите да се намери един, който съвпада с исканата URL. Стойността на поръчката за всеки клас трябва да бъде уникална.

Бобът от тип BasicAuthenticationEntryPoint изисква свойството realName да бъде зададено.

3.2. Множество входни точки, един и същ HTTP елемент

След това нека дефинираме конфигурацията за URL адреси на формуляра / потребител / **, които могат да бъдат достъпни от обикновени потребители с роля на ПОТРЕБИТЕЛ, използвайки удостоверяване на формуляра:

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

Както виждаме, друг начин за определяне на входни точки, освен метода authenticationEntryPoint (), е използването на метода defaultAuthenticationEntryPointFor () . Това може да дефинира множество входни точки, които съответстват на различни условия въз основа на обект RequestMatcher .

Интерфейсът RequestMatcher има изпълнения, базирани на различни видове условия, като например съвпадащ път, тип носител или регулярно изражение. В нашия пример използвахме AntPathRequestMatch, за да зададем две различни входни точки за URL адреси на формулярите / user / private / ** и / user / general / ** .

След това трябва да дефинираме входните точки в същия клас на статична конфигурация:

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

Основната точка тук е как да настроите тези множество входни точки - не е задължително подробностите за изпълнението на всяка една.

В този случай входните точки са от тип LoginUrlAuthenticationEntryPoint и използват различен URL адрес на страницата за вход: / userLogin за проста страница за вход и / userLoginWithWarning за страница за вход, която също показва предупреждение при опит за достъп до / потребител / частни URL адреси.

Тази конфигурация също ще изисква дефиниране на / userLogin и / userLoginWithWarning MVC mappings и две страници със стандартен формуляр за вход.

За удостоверяването на формуляра е много важно да запомните, че всеки URL, необходим за конфигурацията, като URL за обработка на вход, също трябва да следва формата / user / ** или да бъде конфигуриран по друг начин, за да бъде достъпен.

И двете от горните конфигурации ще пренасочат към URL адрес / 403, ако потребител без подходящата роля се опита да осъществи достъп до защитен URL адрес.

Внимавайте да използвате уникални имена за зърната, дори ако те са в различни статични класове , в противен случай едното ще замени другото.

3.3. Нов HTTP елемент, без входна точка

И накрая, нека дефинираме третата конфигурация за URL адреси на формуляра / гост / **, която ще позволи на всички типове потребители, включително неудостоверени:

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

3.4. XML конфигурация

Нека да разгледаме еквивалентната XML конфигурация за трите екземпляра HttpSecurity в предишния раздел.

Както се очакваше, това ще съдържа три отделни XML блокове.

За URL адресите / admin / ** XML конфигурацията ще използва атрибута entry-point-ref на http-basic елемента:

Тук трябва да се отбележи, че ако се използва XML конфигурация, ролите трябва да бъдат във формата ROLE_ .

Конфигурацията за URL адресите / user / ** ще трябва да бъде разделена на два http блока в xml, защото няма пряк еквивалент на метода defaultAuthenticationEntryPointFor () .

Конфигурацията за URL адреси / user / general / ** е:

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

Пълният изходен код за примерите може да бъде намерен в GitHub. За да стартирате приложението, разкоментирайте маркера за начален клас MultipleEntryPointsApplication в pom.xml и изпълнете командата mvn spring-boot: run , след което влизате в URL / multipleHttpLinks .

Обърнете внимание, че не е възможно да излезете, когато използвате HTTP Basic Authentication, така че ще трябва да затворите и отворите отново браузъра, за да премахнете това удостоверяване.

За да стартирате теста JUnit, използвайте дефинираните входни точки на профила на Maven със следната команда:

mvn clean install -PentryPoints