Spring Security 5 - OAuth2 Войти

Spring Security 5 - OAuth2 Войти

1. обзор

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

В этой статьеwe’ll explore some of the various configuration options available for the oauth2Login() element.

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

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


    org.springframework.security
    spring-security-oauth2-client


   org.springframework.security
   spring-security-oauth2-jose

В нашем примере зависимостями управляет родительский элемент Spring Boot, версия2.x,, которая соответствует версии5.x артефактов Spring Security.

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

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

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

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

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

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

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

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

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

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

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

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

Затем нам нужно добавить учетные данные клиента в файлapplication.properties. The Spring Security properties are prefixed with “spring.security.oauth2.client.registration” followed by the client name, then the name of the client property:

spring.security.oauth2.client.registration.google.client-id=
spring.security.oauth2.client.registration.google.client-secret=

spring.security.oauth2.client.registration.facebook.client-id=
spring.security.oauth2.client.registration.facebook.client-secret=

Adding these properties for at least one client will enable the Oauth2ClientAutoConfiguration class, который устанавливает все необходимые бины.

Автоматическая конфигурация веб-безопасности эквивалентна определению простого элемента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-адресу, приложение отображает автоматически созданную страницу входа с двумя клиентами:

image

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

Note that in addition to Google and Facebook, the Spring Security project also contains default configurations for GitHub and Okta. Эти конфигурации по умолчанию предоставляют всю необходимую информацию для аутентификации, которая позволяет нам вводить только учетные данные клиента.

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

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

4.1. Создание бинаClientRegistrationRepository

If we’re not working with a Spring Boot application, we’ll need to define a ClientRegistrationRepository bean, который содержит внутреннее представление информации о клиенте, принадлежащей серверу авторизации:

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

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        List 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());
}

Как показано здесь,we can use the clientRegistrationRepository() method of oauth2Login() to register a custom registration repository.

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

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

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 oauth2AuthenticationUrls
      = new HashMap<>();

    @Autowired
    private ClientRegistrationRepository clientRegistrationRepository;

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

        return "oauth_login";
    }
}

This method has to send a map of the clients available and their authorization endpoints to the view, которое мы получим из bean-компонентаClientRegistrationRepository:

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

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

    return "oauth_login";
}

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

Login with:

Client

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

После того, как мы добавим немного стилей, у нас получится более приятная на вид страница входа:

image

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 использует для запуска запроса авторизации на внешний сервер.

Во-первых,let’s set new properties for the authorization endpoint:

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

Здесь мы изменилиbaseUri на/oauth2/authorize-client вместо значения по умолчанию/oauth2/authorization.. Мы также явно устанавливаем bean-компонентauthorizationRequestRepository(), который мы должны определить:

@Bean
public AuthorizationRequestRepository
  authorizationRequestRepository() {

    return new HttpSessionOAuth2AuthorizationRequestRepository();
}

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

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

Токенendpoint обрабатывает токены доступа.

Let’s explicitly configure the tokenEndpoint() с реализацией клиента ответа по умолчанию:

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

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

@Bean
public OAuth2AccessTokenResponseClient
  accessTokenResponseClient() {

    return new NimbusAuthorizationCodeTokenResponseClient();
}

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

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

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

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

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

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

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

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

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

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

We can customize this endpoint using the userInfoEndpoint() method. Для этого мы можем использовать такие методы, какuserService() иcustomUserType(), чтобы изменить способ получения информации о пользователе.

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

Распространенной задачей, которую мы можем решить, является поиск информации о вошедшем в систему пользователе. Для этогоwe can make a request to the user information endpoint.

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

@Autowired
private OAuth2AuthorizedClientService authorizedClientService;

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

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

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 response = restTemplate
      .exchange(userInfoEndpointUri, HttpMethod.GET, entity, Map.class);
    Map userAttributes = response.getBody();
    model.addAttribute("name", userAttributes.get("name"));
}

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

image

Помимоname,,userAttributes Map также содержит такие свойства, какemail, family_name,picture, locale.

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

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

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