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

Сервер Ресурсов OAuth 2.0 С Spring Security 5

Узнайте, как настроить приложение сервера ресурсов на основе Spring Security для проверки JWT, а также непрозрачных токенов.

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

1. Обзор

В этом уроке мы узнаем как настроить сервер ресурсов OAuth 2.0 с помощью Spring Security 5 .

Мы сделаем это с помощью JWT, а также непрозрачных токенов, двух видов токенов на предъявителя, поддерживаемых Spring Security.

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

2. Немного Предыстории

2.1. Что такое JWT и Непрозрачные токены?

JWT, или JSON Web Token -это способ безопасной передачи конфиденциальной информации в широко распространенном формате JSON. Содержащаяся информация может быть о пользователе или о самом токене, например о сроке его действия и эмитенте.

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

2.2. Что такое Сервер Ресурсов?

В контексте OAuth 2.0 сервер ресурсов – это приложение, которое защищает ресурсы с помощью токенов OAuth . Эти токены выдаются сервером авторизации, как правило, клиентскому приложению. Задача сервера ресурсов заключается в проверке маркера перед подачей ресурса клиенту.

Действительность токена определяется несколькими факторами:

  • Пришел ли этот токен с настроенного сервера авторизации?
  • Он не истек?
  • Является ли этот сервер ресурсов его целевой аудиторией?
  • Имеет ли токен необходимые полномочия для доступа к запрошенному ресурсу?

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

Как мы видим на шаге 8, когда клиентское приложение вызывает API сервера ресурсов для доступа к защищенному ресурсу, оно сначала отправляется на сервер авторизации для проверки маркера, содержащегося в заголовке запроса Authorization: Bearer , а затем отвечает клиенту.

Шаг 9-это то, на чем мы сосредоточимся в этом уроке.

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

3. Сервер авторизации

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

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

Наш встроенный сервер Keycloak имеет два определенных клиента – fooClient и barClient– , соответствующих нашим двум приложениям сервера ресурсов.

4. Сервер ресурсов – Использование JWTs

Наш сервер ресурсов будет состоять из четырех основных компонентов:

  • Модель – ресурс для защиты
  • API – контроллер REST для предоставления ресурса
  • Конфигурация безопасности – класс для определения контроля доступа к защищенному ресурсу, предоставляемому API
  • application.yml – конфигурационный файл для объявления свойств, включая информацию о сервере авторизации

Давайте рассмотрим их один за другим для нашего сервера ресурсов, обрабатывающего токены JWT, после того, как взглянем на зависимости.

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

В основном нам понадобится spring-boot-starter-oauth2-resource-server , стартер Spring Boot для поддержки сервера ресурсов. Этот стартер по умолчанию включает в себя Spring Security, поэтому нам не нужно добавлять его явно:


    org.springframework.boot
    spring-boot-starter-web
    2.2.6.RELEASE


    org.springframework.boot
    spring-boot-starter-oauth2-resource-server
    2.2.6.RELEASE


    org.apache.commons
    commons-lang3
    3.11

Кроме того, мы также добавили веб-поддержку.

Для наших демонстрационных целей мы будем генерировать ресурсы случайным образом, а не получать их из базы данных, с некоторой помощью библиотеки Apache commons-lang3 .

4.2. Модель

Чтобы все было просто, мы будем использовать Foo , POJO, в качестве нашего защищенного ресурса:

public class Foo {
    private long id;
    private String name;
    
    // constructor, getters and setters
}

4.3. API

Вот наш контроллер отдыха, чтобы сделать Еду доступной для манипуляций:

@RestController
@RequestMapping(value = "/foos")
public class FooController {

    @GetMapping(value = "/{id}")
    public Foo findOne(@PathVariable Long id) {
        return new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4));
    }

    @GetMapping
    public List findAll() {
        List fooList = new ArrayList();
        fooList.add(new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4)));
        fooList.add(new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4)));
        fooList.add(new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4)));
        return fooList;
    }

    @ResponseStatus(HttpStatus.CREATED)
    @PostMapping
    public void create(@RequestBody Foo newFoo) {
        logger.info("Foo created");
    }
}

Как очевидно, у нас есть возможность ПОЛУЧИТЬ все Foo s, ПОЛУЧИТЬ Foo по идентификатору и ОПУБЛИКОВАТЬ Фотографию .

4.4. Настройка безопасности

В этом классе конфигурации мы определяем уровни доступа для нашего ресурса:

@Configuration
public class JWTSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .authorizeRequests(authz -> authz
            .antMatchers(HttpMethod.GET, "/foos/**").hasAuthority("SCOPE_read")
            .antMatchers(HttpMethod.POST, "/foos").hasAuthority("SCOPE_write")
            .anyRequest().authenticated())
          .oauth2ResourceServer(oauth2 -> oauth2.jwt());
	}
}

Любой, у кого есть маркер доступа с областью read , может получить Foo s. Чтобы ОПУБЛИКОВАТЬ новый Food , их токен должен иметь область write .

Кроме того, мы добавили вызов jwt() с помощью oauth2 ResourceServer () | DSL , чтобы указать тип токенов, поддерживаемых нашим сервером здесь .

4.5. application.yml

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

server: 
  port: 8081
  servlet: 
    context-path: /resource-server-jwt

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8083/auth/realms/baeldung

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

Чтобы эта проверка работала с использованием свойства issuer-uri , сервер авторизации должен быть запущен и запущен. В противном случае сервер ресурсов не запустится.

Если нам нужно запустить его самостоятельно, мы можем вместо этого указать свойство jwk-set-url , чтобы указать на конечную точку сервера авторизации, предоставляющую открытые ключи:

jwk-set-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/certs

И это все, что нам нужно, чтобы наш сервер проверял токены JWT.

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

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

Давайте проверим, что мы можем получить Foo s из resource-server-jw t с помощью токена read в нашем тесте:

@Test
public void givenUserWithReadScope_whenGetFooResource_thenSuccess() {
    String accessToken = obtainAccessToken("read");

    Response response = RestAssured.given()
      .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
      .get("http://localhost:8081/resource-server-jwt/foos");
    assertThat(response.as(List.class)).hasSizeGreaterThan(0);
}

В приведенном выше коде в строке № 3 мы получаем маркер доступа с областью read от сервера авторизации, охватывающий шаги с 1 по 7 нашей схемы последовательности.

Шаг 8 выполняется с помощью RestAssured ‘s get() call. Шаг 9 выполняется сервером ресурсов с конфигурациями, которые мы видели, и прозрачен для нас как пользователей.

5. Сервер Ресурсов – Использование Непрозрачных Токенов

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

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

Для поддержки непрозрачных токенов нам дополнительно понадобится oauth2-oidc-sdk зависимость:


    com.nimbusds
    oauth2-oidc-sdk
    8.19
    runtime

5.2. Модель и контроллер

Для этого мы добавим Бар ресурс:

public class Bar {
    private long id;
    private String name;
    
    // constructor, getters and setters
}

У нас также будет контроллер Bar с конечными точками, похожими на наш FooController раньше, чтобы подавать Bar s.

5.3. application.yml

В application.yml здесь нам нужно будет добавить introspection-uri , соответствующий конечной точке интроспекции нашего сервера авторизации. Как упоминалось ранее, именно так проверяется непрозрачный токен:

server: 
  port: 8082
  servlet: 
    context-path: /resource-server-opaque

spring:
  security:
    oauth2:
      resourceserver:
        opaque:
          introspection-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/token/introspect
          introspection-client-id: barClient
          introspection-client-secret: barClientSecret

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

Сохраняя уровни доступа, аналогичные уровням доступа Food для ресурса Bar , этот класс конфигурации также вызывает OpaqueToken() с помощью oauth2ResourceServer() DSL, чтобы указать использование непрозрачного типа маркера :

@Configuration
public class OpaqueSecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${spring.security.oauth2.resourceserver.opaque.introspection-uri}")
    String introspectionUri;

    @Value("${spring.security.oauth2.resourceserver.opaque.introspection-client-id}")
    String clientId;

    @Value("${spring.security.oauth2.resourceserver.opaque.introspection-client-secret}")
    String clientSecret;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .authorizeRequests(authz -> authz
            .antMatchers(HttpMethod.GET, "/bars/**").hasAuthority("SCOPE_read")
            .antMatchers(HttpMethod.POST, "/bars").hasAuthority("SCOPE_write")
            .anyRequest().authenticated())
          .oauth2ResourceServer(oauth2 -> oauth2
            .opaqueToken(token -> token.introspectionUri(this.introspectionUri)
              .introspectionClientCredentials(this.clientId, this.clientSecret)));
    }
}

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

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

Мы настроим JUnit для нашего непрозрачного сервера ресурсов на основе токенов, аналогично тому, как мы сделали это для JWT.

В этом случае давайте проверим, может ли маркер доступа write scoped РАЗМЕЩАТЬ Bar в resource-server-непрозрачный :

@Test
public void givenUserWithWriteScope_whenPostNewBarResource_thenCreated() {
    String accessToken = obtainAccessToken("read write");
    Bar newBar = new Bar(Long.parseLong(randomNumeric(2)), randomAlphabetic(4));

    Response response = RestAssured.given()
      .contentType(ContentType.JSON)
      .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
      .body(newBar)
      .log()
      .all()
      .post("http://localhost:8082/resource-server-opaque/bars");
    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED.value());
}

Если мы получим статус СОЗДАНО обратно, это означает, что сервер ресурсов успешно проверил непрозрачный токен и создал для нас Bar .

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

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

Как мы видели, при минимальной настройке Spring позволил легко проверить токены у эмитента и отправить ресурсы запрашивающей стороне – в нашем случае тест JUnit.

Как всегда, исходный код доступен на GitHub .