«1. Обзор

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

Кроме того, мы продемонстрируем, как извлекать и PrincipalExtractor, и AuthoritiesExtractor, используя интерфейсы Spring PrincipalExtractor и AuthoritiesExtractor.

Для ознакомления с Spring Security OAuth2 обратитесь к этим статьям.

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

Для начала нам нужно добавить зависимость spring-security-oauth2-autoconfigure в наш pom.xml:

<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>

3. Аутентификация OAuth с использованием Github

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

@Configuration
@EnableOAuth2Sso
public class SecurityConfig 
  extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) 
      throws Exception {
 
        http.antMatcher("/**")
          .authorizeRequests()
          .antMatchers("/login**")
          .permitAll()
          .anyRequest()
          .authenticated()
          .and()
          .formLogin().disable();
    }
}

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

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

Хотя Spring по умолчанию создает для нас большинство компонентов, нам все равно нужно настроить некоторые свойства:

security.oauth2.client.client-id=89a7c4facbb3434d599d
security.oauth2.client.client-secret=9b3b08e4a340bd20e866787e4645b54f73d74b6a
security.oauth2.client.access-token-uri=https://github.com/login/oauth/access_token
security.oauth2.client.user-authorization-uri=https://github.com/login/oauth/authorize
security.oauth2.client.scope=read:user,user:email
security.oauth2.resource.user-info-uri=https://api.github.com/user

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

4. Извлечение принципала и полномочий

При работе в качестве клиента OAuth и аутентификации пользователей через третью сторону нам необходимо рассмотреть три шага:

  1. User authentication – the user authenticates with the third party
  2. User authorization – follows authentication, it’s when the user allows our application to perform certain operations on their behalf; this is where scopes come in
  3. Fetch user data – use the OAuth token we’ve obtained to retrieve user’s data

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

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

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

    По умолчанию Spring предоставляет два компонента — FixedPrincipalExtractor и FixedAuthoritiesExtractor — которые реализуют эти интерфейсы и имеют предопределенную стратегию их создания для нас.

4.1. Настройка аутентификации Github

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

Таким образом, чтобы переопределить компоненты Spring по умолчанию, нам просто нужно создать два Bean-компонента, которые также реализуют эти интерфейсы.

Для принципала нашего приложения мы просто будем использовать имя пользователя Github:

В зависимости от подписки нашего пользователя на Github — бесплатной или какой-либо другой — мы дадим ему GITHUB_USER_SUBSCRIBED или полномочия GITHUB_USER_FREE:

public class GithubPrincipalExtractor 
  implements PrincipalExtractor {

    @Override
    public Object extractPrincipal(Map<String, Object> map) {
        return map.get("login");
    }
}

Затем нам также нужно создать bean-компоненты, используя эти классы:

public class GithubAuthoritiesExtractor 
  implements AuthoritiesExtractor {
    List<GrantedAuthority> GITHUB_FREE_AUTHORITIES
     = AuthorityUtils.commaSeparatedStringToAuthorityList(
     "GITHUB_USER,GITHUB_USER_FREE");
    List<GrantedAuthority> GITHUB_SUBSCRIBED_AUTHORITIES 
     = AuthorityUtils.commaSeparatedStringToAuthorityList(
     "GITHUB_USER,GITHUB_USER_SUBSCRIBED");

    @Override
    public List<GrantedAuthority> extractAuthorities
      (Map<String, Object> map) {
 
        if (Objects.nonNull(map.get("plan"))) {
            if (!((LinkedHashMap) map.get("plan"))
              .get("name")
              .equals("free")) {
                return GITHUB_SUBSCRIBED_AUTHORITIES;
            }
        }
        return GITHUB_FREE_AUTHORITIES;
    }
}

4.2. Использование собственного сервера авторизации

@Configuration
@EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    // ...

    @Bean
    public PrincipalExtractor githubPrincipalExtractor() {
        return new GithubPrincipalExtractor();
    }

    @Bean
    public AuthoritiesExtractor githubAuthoritiesExtractor() {
        return new GithubAuthoritiesExtractor();
    }
}

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

Несмотря на сервер авторизации, который мы решили использовать, компоненты, необходимые для настройки как Принципала, так и Авторитетов, остаются прежними: PrincipalExtractor и AuthoritiesExtractor.

Нам просто нужно знать о данных, возвращаемых конечной точкой user-info-uri, и использовать их по своему усмотрению.

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

Теперь, когда мы указываем на наш сервер авторизации, нам нужно создать оба экстрактора; в этом случае наш PrincipalExtractor будет извлекать принципала из карты, используя ключ имени:

security.oauth2.client.client-id=SampleClientId
security.oauth2.client.client-secret=secret
security.oauth2.client.access-token-uri=http://localhost:8081/auth/oauth/token
security.oauth2.client.user-authorization-uri=http://localhost:8081/auth/oauth/authorize
security.oauth2.resource.user-info-uri=http://localhost:8081/auth/user/me

Что касается полномочий, наш Сервер авторизации уже помещает их в свои данные user-info-uri.

public class BaeldungPrincipalExtractor 
  implements PrincipalExtractor {

    @Override
    public Object extractPrincipal(Map<String, Object> map) {
        return map.get("name");
    }
}

Таким образом, мы собираемся извлечь и обогатить их:

Затем мы добавим bean-компоненты в наш класс SecurityConfig:

public class BaeldungAuthoritiesExtractor 
  implements AuthoritiesExtractor {

    @Override
    public List<GrantedAuthority> extractAuthorities
      (Map<String, Object> map) {
        return AuthorityUtils
          .commaSeparatedStringToAuthorityList(asAuthorities(map));
    }

    private String asAuthorities(Map<String, Object> map) {
        List<String> authorities = new ArrayList<>();
        authorities.add("BAELDUNG_USER");
        List<LinkedHashMap<String, String>> authz = 
          (List<LinkedHashMap<String, String>>) map.get("authorities");
        for (LinkedHashMap<String, String> entry : authz) {
            authorities.add(entry.get("authority"));
        }
        return String.join(",", authorities);
    }
}

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

@Configuration
@EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // ...

    @Bean
    public PrincipalExtractor baeldungPrincipalExtractor() {
        return new BaeldungPrincipalExtractor();
    }

    @Bean
    public AuthoritiesExtractor baeldungAuthoritiesExtractor() {
        return new BaeldungAuthoritiesExtractor();
    }
}

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

Как обычно, реализацию этого примера можно найти на Github.

При локальном запуске вы можете запустить и протестировать приложение на локальном хосте: 8082

«