OAuth2.0 и динамическая регистрация клиентов

OAuth2.0 and Dynamic Client Registration

1. Вступление

В этом руководстве мы собираемся подготовить динамическую регистрацию клиента с помощью OAuth2.0. OAuth2.0 - это структура авторизации, которая позволяет получить ограниченный доступ к учетным записям пользователей в службе HTTP. Клиент OAuth2.0 - это приложение, которое хочет получить доступ к учетной записи пользователя. Этот клиент может быть внешним веб-приложением, пользовательским агентом или просто собственным клиентом.

Чтобы обеспечить динамическую регистрацию клиентов, мы собираемся хранить учетные данные в базе данных вместо жестко заданной конфигурации. Приложение, которое мы собираемся расширить, изначально было описано вSpring REST API + OAuth2 tutorial.

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

Сначала мы настроим следующий набор зависимостей:


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


    org.springframework
    spring-jdbc


    org.springframework.security.oauth
    spring-security-oauth2

Обратите внимание, что мы используемspring-jdbc, потому что мы собираемся использовать БД для хранения паролей недавно зарегистрированных пользователей.

3. OAuth2.0 Server Configuration

Сначала нам нужно настроить наш сервер авторизации OAuth2.0. Основная конфигурация находится внутри следующего класса:

@Configuration
@PropertySource({ "classpath:persistence.properties" })
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig
  extends AuthorizationServerConfigurerAdapter {

    // config
}

Нам нужно настроить несколько основных вещей; начнем сClientDetailsServiceConfigurer:

@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
    clients.jdbc(dataSource())

    // ...
}

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

Давайте, конечно, настроим этот стандартный источник данных:

@Bean
public DataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();

    dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
    dataSource.setUrl(env.getProperty("jdbc.url"));
    dataSource.setUsername(env.getProperty("jdbc.user"));
    dataSource.setPassword(env.getProperty("jdbc.pass"));
    return dataSource;
}

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

4. Схема БД

Теперь давайте определим структуру SQL для хранения наших клиентов OAuth:

create table oauth_client_details (
    client_id VARCHAR(256) PRIMARY KEY,
    resource_ids VARCHAR(256),
    client_secret VARCHAR(256),
    scope VARCHAR(256),
    authorized_grant_types VARCHAR(256),
    web_server_redirect_uri VARCHAR(256),
    authorities VARCHAR(256),
    access_token_validity INTEGER,
    refresh_token_validity INTEGER,
    additional_information VARCHAR(4096),
    autoapprove VARCHAR(256)
);

Наиболее важные поля изoauth_client_details, на которых мы должны сосредоточиться:

  • client_id - для хранения id вновь зарегистрированных клиентов

  • client_secret - для хранения паролей клиентов

  • access_token_validity - указывает, действителен ли еще клиент

  • authorities - чтобы указать, какие роли разрешены для конкретного клиента

  • scope - разрешенные действия, например написание статусов в Facebook и т. д.

  • authorized_grant_types, который предоставляет информацию о том, как пользователи могут войти в конкретный клиент (в нашем примере это форма входа с паролем)

Обратите внимание, что у каждого клиента есть отношения от одного до многих с пользователями, что, естественно, означает, чтоmultiple users can utilize a single client.

5. Давайте сохраним некоторых клиентов

С помощью определения схемы SQL мы можем, наконец, создать некоторые данные в системе и, в основном, определить клиента.

Мы собираемся использовать следующий скриптdata.sql, который Spring Boot будет запускать по умолчанию, для инициализации БД:

INSERT INTO oauth_client_details
    (client_id, client_secret, scope, authorized_grant_types,
    web_server_redirect_uri, authorities, access_token_validity,
    refresh_token_validity, additional_information, autoapprove)
VALUES
    ("fooClientIdPassword", "secret", "foo,read,write,
    "password,authorization_code,refresh_token", null, null, 36000, 36000, null, true);

Описание наиболее важных полей вoauth_client_details приведено в предыдущем разделе.

6. тестирование

Чтобы протестировать динамическую регистрацию клиентов, нам нужно запустить проектыspring-security-oauth-server иspring-security-oauth-resource на портах 8081 и 8082 соответственно.

Теперь мы можем наконец написать несколько тестов.

Предположим, что мы зарегистрировали клиента с идентификаторомfooClientIdPassword, у которого есть доступ к чтению файлов foos.

Сначала мы попытаемся получить токен доступа с сервера аутентификации, используя уже определенный клиент:

@Test
public void givenDBUser_whenRevokeToken_thenAuthorized() {
    String accessToken = obtainAccessToken("fooClientIdPassword", "john", "123");

    assertNotNull(accessToken);
}

А вот логика получения токена доступа:

private String obtainAccessToken(String clientId, String username, String password) {
    Map params = new HashMap();
    params.put("grant_type", "password");
    params.put("client_id", clientId);
    params.put("username", username);
    params.put("password", password);
    Response response = RestAssured.given().auth().preemptive()
      .basic(clientId, "secret").and().with().params(params).when()
      .post("http://localhost:8081/spring-security-oauth-server/oauth/token");
    return response.jsonPath().getString("access_token");
}

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

В этом уроке мы узнали, как динамически регистрировать неограниченное количество клиентов с помощью инфраструктуры OAuth2.0.

Полную реализацию этого руководства можно найти вover on GitHub - это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.

Обратите внимание, что для тестирования вам необходимо добавить клиентов в БД, и что конфигурация.inMemory() больше не будет действительной. Если вы хотите использовать старую конфигурацию.inMemory(), есть второй файл, содержащий конфигурацию с жестко запрограммированными клиентами.