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