Перейти к основному содержимому

Несколько поставщиков аутентификации в Spring Security

· 4 мин. чтения

1. Обзор

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

Мы сделаем это, настроив несколько поставщиков аутентификации.

2. Поставщики аутентификации

AuthenticationProvider — это абстракция для получения информации о пользователе из определенного репозитория (например, из базы данных , LDAP , пользовательского стороннего источника и т. д.). Он использует полученную информацию о пользователе для проверки предоставленных учетных данных.

Проще говоря, когда определено несколько провайдеров проверки подлинности, провайдеры будут запрашиваться в том порядке, в котором они объявлены.

Для быстрой демонстрации мы настроим двух поставщиков проверки подлинности — собственного поставщика проверки подлинности и поставщика проверки подлинности в памяти.

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

Давайте сначала добавим необходимые зависимости Spring Security в наше веб-приложение:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

И без Spring Boot:

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>

Последнюю версию этих зависимостей можно найти по адресу spring-security-web , spring-security-core и spring-security-config .

4. Пользовательский поставщик аутентификации

Давайте теперь создадим собственного поставщика аутентификации, реализуя интерфейс AuthneticationProvider .

Мы собираемся реализовать метод аутентификации , который пытается выполнить аутентификацию. Входной объект Authentication содержит учетные данные имени пользователя и пароля, предоставленные пользователем.

Метод аутентификации возвращает полностью заполненный объект аутентификации , если аутентификация прошла успешно. Если аутентификация не удалась, выдается исключение типа AuthenticationException :

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication auth)
throws AuthenticationException {
String username = auth.getName();
String password = auth.getCredentials()
.toString();

if ("externaluser".equals(username) && "pass".equals(password)) {
return new UsernamePasswordAuthenticationToken
(username, password, Collections.emptyList());
} else {
throw new
BadCredentialsException("External system authentication failed");
}
}

@Override
public boolean supports(Class<?> auth) {
return auth.equals(UsernamePasswordAuthenticationToken.class);
}
}

Естественно, это простая реализация для нашего примера.

5. Настройка нескольких поставщиков аутентификации

Давайте теперь добавим CustomAuthenticationProvider и поставщика аутентификации в памяти в нашу конфигурацию Spring Security.

5.1. Конфигурация Java

В нашем классе конфигурации давайте теперь создадим и добавим поставщиков аутентификации с помощью AuthenticationManagerBuilder .

Сначала CustomAuthenticationProvider , а затем поставщик проверки подлинности в памяти с помощью inMemoryAuthentication() .

Мы также гарантируем, что доступ к шаблону URL « /api/** » должен быть аутентифицирован:

@EnableWebSecurity
public class MultipleAuthProvidersSecurityConfig
extends WebSecurityConfigurerAdapter {
@Autowired
CustomAuthenticationProvider customAuthProvider;

@Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {

auth.authenticationProvider(customAuthProvider);
auth.inMemoryAuthentication()
.withUser("memuser")
.password(encoder().encode("pass"))
.roles("USER");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/api/**")
.authenticated();
}


@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

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

В качестве альтернативы, если мы хотим использовать конфигурацию XML вместо конфигурации Java:

<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="memuser" password="pass"
authorities="ROLE_USER" />
</security:user-service>
</security:authentication-provider>
<security:authentication-provider
ref="customAuthenticationProvider" />
</security:authentication-manager>

<security:http>
<security:http-basic />
<security:intercept-url pattern="/api/**"
access="isAuthenticated()" />
</security:http>

6. Приложение

Далее давайте создадим простую конечную точку REST, защищенную двумя нашими поставщиками аутентификации.

Для доступа к этой конечной точке необходимо указать действительное имя пользователя и пароль. Наши поставщики аутентификации проверят учетные данные и определят, разрешать доступ или нет:

@RestController
public class MultipleAuthController {
@GetMapping("/api/ping")
public String getPing() {
return "OK";
}
}

7. Тестирование

Наконец, давайте теперь проверим доступ к нашему защищенному приложению. Доступ будет разрешен только в том случае, если предоставлены действительные учетные данные:

@Autowired
private TestRestTemplate restTemplate;

@Test
public void givenMemUsers_whenGetPingWithValidUser_thenOk() {
ResponseEntity<String> result
= makeRestCallToGetPing("memuser", "pass");

assertThat(result.getStatusCodeValue()).isEqualTo(200);
assertThat(result.getBody()).isEqualTo("OK");
}

@Test
public void givenExternalUsers_whenGetPingWithValidUser_thenOK() {
ResponseEntity<String> result
= makeRestCallToGetPing("externaluser", "pass");

assertThat(result.getStatusCodeValue()).isEqualTo(200);
assertThat(result.getBody()).isEqualTo("OK");
}

@Test
public void givenAuthProviders_whenGetPingWithNoCred_then401() {
ResponseEntity<String> result = makeRestCallToGetPing();

assertThat(result.getStatusCodeValue()).isEqualTo(401);
}

@Test
public void givenAuthProviders_whenGetPingWithBadCred_then401() {
ResponseEntity<String> result
= makeRestCallToGetPing("user", "bad_password");

assertThat(result.getStatusCodeValue()).isEqualTo(401);
}

private ResponseEntity<String>
makeRestCallToGetPing(String username, String password) {
return restTemplate.withBasicAuth(username, password)
.getForEntity("/api/ping", String.class, Collections.emptyMap());
}

private ResponseEntity<String> makeRestCallToGetPing() {
return restTemplate
.getForEntity("/api/ping", String.class, Collections.emptyMap());
}

8. Заключение

В этом кратком руководстве мы увидели, как в Spring Security можно настроить несколько поставщиков аутентификации. Мы защитили простое приложение с помощью собственного поставщика аутентификации и поставщика аутентификации в памяти.

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

Как всегда, полный исходный код реализации можно найти на GitHub .