Рубрики
Без рубрики

Несколько точек входа в Spring Security

Краткое практическое руководство по настройке нескольких точек входа в Spring Security.

Автор оригинала: baeldung.

1. Обзор

В этом кратком руководстве мы рассмотрим, как определить несколько точек входа в приложение Spring Security .

Это в основном влечет за собой определение нескольких http блоков в файле конфигурации XML или нескольких Экземпляры Http Security расширяют класс WebSecurityConfigurerAdapter несколько раз.

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

Для разработки нам понадобятся следующие зависимости:


    org.springframework.boot
    spring-boot-starter-security
    2.4.0

 
    org.springframework.boot 
    spring-boot-starter-web 
    2.4.0 


    org.springframework.boot
    spring-boot-starter-thymeleaf
    2.4.0


    org.springframework.boot
    spring-boot-starter-test
    2.4.0
    

    org.springframework.security
    spring-security-test
    5.4.0

Последние версии 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();
    }
}

Теперь давайте рассмотрим как мы можем определить несколько точек входа в нашей конфигурации безопасности.

Мы собираемся использовать здесь пример, основанный на базовой аутентификации, и мы собираемся хорошо использовать тот факт, что Spring Security поддерживает определение нескольких HTTP-элементов в наших конфигурациях.

При использовании конфигурации Java способ определения нескольких областей безопасности состоит в том, чтобы иметь несколько классов @Configuration , которые расширяют базовый класс WebSecurityConfigurerAdapter – каждый со своей собственной конфигурацией безопасности. Эти классы могут быть статическими и помещаться внутри основной конфигурации.

Основная мотивация наличия нескольких точек входа в одном приложении заключается в том, что существуют различные типы пользователей, которые могут получить доступ к различным частям приложения.

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

  • один для административных пользователей, использующих базовую аутентификацию HTTP
  • один для обычных пользователей, использующих проверку подлинности формы
  • и один для гостевых пользователей, которые не требуют аутентификации

Точка входа, определенная для административных пользователей, защищает URL-адреса формы /admin/** , чтобы разрешить только пользователям с ролью АДМИНИСТРАТОРА, и требует базовой аутентификации HTTP с точкой входа типа 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-адресу. Значение order для каждого класса должно быть уникальным.

Боб типа BasicAuthenticationEntryPoint требует установки свойства realName .

3.2. Несколько Точек Входа, Один И Тот Же Элемент HTTP

Далее определим конфигурацию URL-адресов формы /user/** , доступ к которым могут получить обычные пользователи с ролью ПОЛЬЗОВАТЕЛЯ с помощью аутентификации формы:

@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 имеет реализации, основанные на различных типах условий, таких как соответствующий путь, тип носителя или регулярное выражение. В нашем примере мы использовали Antpathrequestmatcher для установки двух различных точек входа для 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-адресам.

Эта конфигурация также потребует определения /логина пользователя и /Логина пользователя с предупреждением MVC-сопоставлений и двух страниц со стандартной формой входа.

Для проверки подлинности формы очень важно помнить, что любой URL-адрес, необходимый для настройки, например URL-адрес обработки входа в систему, также должен соответствовать формату /user/** или быть иным образом настроен для доступа.

Обе вышеперечисленные конфигурации будут перенаправлены на /403 URL-адрес, если пользователь без соответствующей роли пытается получить доступ к защищенному URL-адресу.

Будьте осторожны , чтобы использовать уникальные имена для бобов, даже если они находятся в разных статических классах , иначе один будет переопределять другой.

3.3. Новый Элемент HTTP, Без Точки Входа

Наконец, давайте определим третью конфигурацию для URL-адресов формы /guest/** , которая позволит использовать все типы пользователей, включая не прошедших проверку подлинности:

@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      



  

Для URL-адресов /user/private/** мы можем определить аналогичную конфигурацию:


    
    //form-login configuration



    

Для URL-адресов /guest/** у нас будет элемент http :


      

Также важно здесь то, что по крайней мере один блок XML должен соответствовать шаблону/**.

4. Доступ к Защищенным URL-адресам

4.1. Конфигурация MVC

Давайте создадим сопоставления запросов, соответствующие защищенным нами шаблонам URL-адресов:

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

Сопоставление /multiple Http Links вернет простую HTML-страницу со ссылками на защищенные URL-адреса:

Каждая из HTML-страниц, соответствующих защищенным URL-адресам, будет иметь простой текст и обратную ссылку:

Welcome admin!

Back to links

4.2. Инициализация приложения

Мы запустим наш пример как приложение Spring Boot, поэтому давайте определим класс с основным методом:

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

Если мы хотим использовать конфигурацию XML, нам также нужно добавить @ImportResource({“classpath*:spring-security-multiple-entry.xml”}) аннотация к нашему основному классу.

4.3. Тестирование конфигурации безопасности

Давайте создадим тестовый класс JUnit, который мы можем использовать для тестирования наших защищенных URL-адресов:

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

Затем давайте протестируем URL-адреса с помощью admin user.

При запросе /admin/admin Page URL без базовой аутентификации HTTP мы должны ожидать получения несанкционированного кода состояния, и после добавления аутентификации код состояния должен быть 200 OK.

При попытке доступа к /user/user Page URL с пользователем admin мы должны получить статус 302 Запрещено:

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

Давайте создадим аналогичный тест, используя обычные учетные данные пользователя для доступа к URL-адресам:

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

Во втором тесте мы видим, что отсутствие аутентификации формы приведет к статусу 302 Найдено вместо несанкционированного, так как Spring Security перенаправит на форму входа.

Наконец, давайте создадим тест, в котором мы получим доступ к /гостевой/гостевой странице URL-адресу всех трех типов аутентификации и проверим, что мы получаем статус 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. Заключение

В этом уроке мы продемонстрировали, как настроить несколько точек входа при использовании Spring Security.

Полный исходный код примеров можно найти на GitHub . Чтобы запустить приложение, раскомментируйте тег Multiple Entry Points Application | start-class в теге pom.xml и выполните команду mvn spring-boot:run , затем получите доступ к /multipleHttpLinks URL .

Обратите внимание, что при использовании базовой аутентификации HTTP невозможно выйти из системы, поэтому вам придется закрыть и снова открыть браузер, чтобы удалить эту аутентификацию.

Чтобы запустить тест JUnit, используйте определенный профиль Maven точки входа со следующей командой:

mvn clean install-Точки входа