Автор оригинала: 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 .