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

Spring Security 5 — вход через OAuth2

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

1. Обзор

Spring Security 5 представляет новый класс OAuth2LoginConfigurer , который мы можем использовать для настройки внешнего сервера авторизации.

В этом руководстве мы рассмотрим некоторые из различных параметров конфигурации, доступных для элемента oauth2Login() .

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

В проекте Spring Boot нам просто нужно добавить стартер spring-boot-starter-oauth2-client :

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>

В не-загрузочном проекте в дополнение к стандартным зависимостям Spring и Spring Security нам также потребуется явно добавить зависимости spring-security-oauth2-client и spring-security-oauth2-jose :

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>5.3.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>5.3.4.RELEASE</version>
</dependency>

3. Настройка клиентов

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

Давайте настроим наш проект для входа в систему с клиентами, зарегистрированными в Google и Facebook в качестве поставщиков аутентификации.

3.1. Получение учетных данных клиента

Чтобы получить учетные данные клиента для аутентификации Google OAuth2, перейдите в консоль API Google , раздел «Учетные данные».

Здесь мы создадим учетные данные типа «Идентификатор клиента OAuth2» для нашего веб-приложения. Это приводит к тому, что Google настраивает для нас идентификатор клиента и секрет.

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

По умолчанию Spring Boot настраивает этот URI перенаправления как /login/oauth2/code/{registrationId} .

Итак, для Google мы добавим этот URI:

http://localhost:8081/login/oauth2/code/google

Чтобы получить учетные данные клиента для аутентификации в Facebook, нам необходимо зарегистрировать приложение на веб-сайте Facebook для разработчиков и настроить соответствующий URI как «Действительный URI перенаправления OAuth»:

http://localhost:8081/login/oauth2/code/facebook

3.2. Конфигурация безопасности

Далее нам нужно добавить учетные данные клиента в файл application.properties .

Свойства Spring Security имеют префикс spring.security.oauth2.client.registration , за которым следует имя клиента, а затем имя свойства клиента :

spring.security.oauth2.client.registration.google.client-id=<your client id>
spring.security.oauth2.client.registration.google.client-secret=<your client secret>

spring.security.oauth2.client.registration.facebook.client-id=<your client id>
spring.security.oauth2.client.registration.facebook.client-secret=<your client secret>

Добавление этих свойств хотя бы для одного клиента активирует класс Oauth2ClientAutoConfiguration , который настраивает все необходимые bean-компоненты.

Автоматическая настройка веб-безопасности эквивалентна определению простого элемента oauth2Login() :

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login();
}
}

Здесь мы видим, что элемент oauth2Login() используется аналогично уже известным элементам httpBasic() и formLogin() .

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

./d51ed424ec2c2e02770d8b7ccb304cc0.png

3.3. Другие клиенты

Обратите внимание, что проект Spring Security также содержит конфигурации по умолчанию для GitHub и Okta в дополнение к Google и Facebook. Эти конфигурации по умолчанию предоставляют всю необходимую информацию для аутентификации, что позволяет нам вводить только учетные данные клиента.

Если мы хотим использовать другого поставщика аутентификации, не настроенного в Spring Security, нам нужно определить полную конфигурацию с такой информацией, как URI авторизации и URI токена. Вот посмотрите на конфигурации по умолчанию в Spring Security, чтобы получить представление о необходимых свойствах.

4. Настройка в незагрузочном проекте

4.1. Создание компонента ClientRegistrationRepository

Если мы не работаем с приложением Spring Boot, нам нужно определить bean -компонент ClientRegistrationRepository , который содержит внутреннее представление информации о клиенте, принадлежащей серверу авторизации:

@Configuration
@EnableWebSecurity
@PropertySource("classpath:application.properties")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static List<String> clients = Arrays.asList("google", "facebook");

@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
List<ClientRegistration> registrations = clients.stream()
.map(c -> getRegistration(c))
.filter(registration -> registration != null)
.collect(Collectors.toList());

return new InMemoryClientRegistrationRepository(registrations);
}
}

Здесь мы создаем InMemoryClientRegistrationRepository со списком объектов ClientRegistration .

4.2. Создание объектов ClientRegistration

Давайте посмотрим на метод getRegistration() , который создает эти объекты:

private static String CLIENT_PROPERTY_KEY 
= "spring.security.oauth2.client.registration.";

@Autowired
private Environment env;

private ClientRegistration getRegistration(String client) {
String clientId = env.getProperty(
CLIENT_PROPERTY_KEY + client + ".client-id");

if (clientId == null) {
return null;
}

String clientSecret = env.getProperty(
CLIENT_PROPERTY_KEY + client + ".client-secret");

if (client.equals("google")) {
return CommonOAuth2Provider.GOOGLE.getBuilder(client)
.clientId(clientId).clientSecret(clientSecret).build();
}
if (client.equals("facebook")) {
return CommonOAuth2Provider.FACEBOOK.getBuilder(client)
.clientId(clientId).clientSecret(clientSecret).build();
}
return null;
}

Здесь мы читаем учетные данные клиента из аналогичного файла application.properties . Затем мы используем перечисление CommonOauth2Provider, уже определенное в Spring Security, для остальных свойств клиента для клиентов Google и Facebook.

Каждый экземпляр ClientRegistration соответствует клиенту.

4.3. Регистрация ClientRegistrationRepository

Наконец, мы должны создать bean-компонент OAuth2AuthorizedClientService на основе bean-компонента ClientRegistrationRepository и зарегистрировать оба с помощью элемента oauth2Login() :

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.oauth2Login()
.clientRegistrationRepository(clientRegistrationRepository())
.authorizedClientService(authorizedClientService());
}

@Bean
public OAuth2AuthorizedClientService authorizedClientService() {

return new InMemoryOAuth2AuthorizedClientService(
clientRegistrationRepository());
}

Как мы видим, мы можем использовать метод clientRegistrationRepository() функции oauth2Login() для регистрации пользовательского репозитория регистрации.

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

Давайте продолжим дальнейшую настройку нашего процесса входа в систему.

5. Настройка oauth2Login()

Есть несколько элементов, которые использует процесс OAuth 2 и которые мы можем настроить с помощью методов oauth2Login() .

Обратите внимание, что все эти элементы имеют конфигурации по умолчанию в Spring Boot, и явная настройка не требуется.

Давайте посмотрим, как мы можем настроить их в нашей конфигурации.

5.1. Пользовательская страница входа

Несмотря на то, что Spring Boot создает для нас страницу входа по умолчанию, мы обычно хотим определить нашу собственную настраиваемую страницу.

Начнем с настройки нового URL-адреса входа для элемента oauth2Login() с помощью метода loginPage() :

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth_login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.oauth2Login()
.loginPage("/oauth_login");
}

Здесь мы настроили наш URL-адрес входа в систему как /oauth_login .

Далее давайте определим LoginController с методом, который сопоставляется с этим URL:

@Controller
public class LoginController {

private static String authorizationRequestBaseUri
= "oauth2/authorization";
Map<String, String> oauth2AuthenticationUrls
= new HashMap<>();

@Autowired
private ClientRegistrationRepository clientRegistrationRepository;

@GetMapping("/oauth_login")
public String getLoginPage(Model model) {
// ...

return "oauth_login";
}
}

Этот метод должен отправить карту доступных клиентов и их конечных точек авторизации в представление , которое мы получим из bean- компонента ClientRegistrationRepository :

public String getLoginPage(Model model) {
Iterable<ClientRegistration> clientRegistrations = null;
ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository)
.as(Iterable.class);
if (type != ResolvableType.NONE &&
ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
clientRegistrations = (Iterable<ClientRegistration>) clientRegistrationRepository;
}

clientRegistrations.forEach(registration ->
oauth2AuthenticationUrls.put(registration.getClientName(),
authorizationRequestBaseUri + "/" + registration.getRegistrationId()));
model.addAttribute("urls", oauth2AuthenticationUrls);

return "oauth_login";
}

Наконец, нам нужно определить нашу страницу oauth_login.html :

<h3>Login with:</h3>
<p th:each="url : ${urls}">
<a th:text="${url.key}" th:href="${url.value}">Client</a>
</p>

Это простая HTML-страница, на которой отображаются ссылки для аутентификации каждого клиента.

Добавив к нему некоторые стили, мы можем изменить внешний вид страницы входа:

./4fb7d6f998608384d01740fbac8a51f0.png

5.2. Пользовательское поведение при успешной и неудачной аутентификации

Мы можем контролировать поведение после аутентификации различными методами:

  • defaultSuccessUrl() и failureUrl() для перенаправления пользователя на указанный URL-адрес.
  • successHandler() и failureHandler() для запуска пользовательской логики после процесса аутентификации.

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

.oauth2Login()
.defaultSuccessUrl("/loginSuccess")
.failureUrl("/loginFailure");

Если пользователь посетил защищенную страницу до аутентификации, он будет перенаправлен на эту страницу после входа в систему. В противном случае он будет перенаправлен на /loginSuccess .

Если мы хотим, чтобы пользователь всегда направлялся по URL-адресу /loginSuccess независимо от того, были ли они ранее на защищенной странице или нет, мы можем использовать метод defaultSuccessUrl("/loginSuccess", true) .

Чтобы использовать пользовательский обработчик, нам пришлось бы создать класс, реализующий интерфейсы AuthenticationSuccessHandler или AuthenticationFailureHandler , переопределить унаследованные методы, а затем установить bean-компоненты с помощью методов successHandler() и failureHandler() .

5.3. Пользовательская конечная точка авторизации

Конечная точка авторизации — это конечная точка, которую Spring Security использует для запуска запроса авторизации на внешний сервер.

Во- первых, давайте установим новые свойства для конечной точки авторизации :

.oauth2Login() 
.authorizationEndpoint()
.baseUri("/oauth2/authorize-client")
.authorizationRequestRepository(authorizationRequestRepository());

Здесь мы изменили baseUri на /oauth2/authorize-client вместо стандартного /oauth2/authorization .

Мы также явно устанавливаем bean-компонент авторизацииRequestRepository() , который мы должны определить:

@Bean
public AuthorizationRequestRepository<OAuth2AuthorizationRequest>
authorizationRequestRepository() {

return new HttpSessionOAuth2AuthorizationRequestRepository();
}

Мы использовали реализацию нашего bean-компонента, предоставленную Spring, но мы также могли бы предоставить собственную.

5.4. Пользовательская конечная точка токена

Конечная точка токена обрабатывает токены доступа.

Давайте явно настроим tokenEndpoint() с реализацией клиента ответа по умолчанию:

.oauth2Login()
.tokenEndpoint()
.accessTokenResponseClient(accessTokenResponseClient());

А вот клиентский компонент ответа:

@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>
accessTokenResponseClient() {

return new NimbusAuthorizationCodeTokenResponseClient();
}

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

Конечно, мы могли бы также заменить клиента пользовательского ответа.

5.5. Пользовательская конечная точка перенаправления

Это конечная точка для перенаправления после аутентификации у внешнего провайдера.

Давайте посмотрим, как мы можем изменить baseUri для конечной точки перенаправления :

.oauth2Login()
.redirectionEndpoint()
.baseUri("/oauth2/redirect")

URI по умолчанию — login/oauth2/code .

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

5.6. Пользовательская конечная точка информации о пользователе

Конечная точка информации о пользователе — это место, которое мы можем использовать для получения информации о пользователе.

Мы можем настроить эту конечную точку, используя метод userInfoEndpoint() . Для этого мы можем использовать такие методы, как userService() и customUserType() , чтобы изменить способ получения информации о пользователе.

6. Доступ к информации о пользователе

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

Во-первых, нам нужно получить клиент, соответствующий текущему токену пользователя:

@Autowired
private OAuth2AuthorizedClientService authorizedClientService;

@GetMapping("/loginSuccess")
public String getLoginInfo(Model model, OAuth2AuthenticationToken authentication) {
OAuth2AuthorizedClient client = authorizedClientService
.loadAuthorizedClient(
authentication.getAuthorizedClientRegistrationId(),
authentication.getName());
//...
return "loginSuccess";
}

Затем мы отправим запрос в конечную точку информации о пользователе клиента и получим карту userAttributes :

String userInfoEndpointUri = client.getClientRegistration()
.getProviderDetails().getUserInfoEndpoint().getUri();

if (!StringUtils.isEmpty(userInfoEndpointUri)) {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + client.getAccessToken()
.getTokenValue());
HttpEntity entity = new HttpEntity("", headers);
ResponseEntity <map>response = restTemplate
.exchange(userInfoEndpointUri, HttpMethod.GET, entity, Map.class);
Map userAttributes = response.getBody();
model.addAttribute("name", userAttributes.get("name"));
}

Добавив свойство name в качестве атрибута Model , мы можем отобразить его в представлении loginSuccess как приветственное сообщение для пользователя:

./2099ec5ec3ccf1e1b44530deb5a97dc5.png

Помимо имени , карта userAttributes также содержит такие свойства, как электронная почта , имя_семьи , изображение и локаль .

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

В этой статье мы увидели, как использовать элемент oauth2Login() в Spring Security для аутентификации с помощью различных провайдеров, таких как Google и Facebook.

Мы также рассмотрели некоторые распространенные сценарии настройки этого процесса.

Полный исходный код примеров можно найти на GitHub .