Как использовать аутентификацию токенов доступа

Дата обновления перевода 2025-02-15

Как использовать аутентификацию токенов доступа

Токены доступа или токены API часто используются как механизм аутентификации в контексте API. Токен доступа - это строка, получення во время аутентификации (используя приложение или сервер авторизации). Роль токена доступа заключается в верификации личности пользователя и получения согласия до выпуска токена.

Токены доступа могут быть любого вида, например, непрозрачными строками, веб-токенами JSON (JWT) или SAML2 (XML-структурами). Пожалуйста, прочтите RFC6750: Фреймворк авторизации OAuth 2.0: Использование токена предъявителя, чтобы узнать детали.

Использование аутентификатора токена доступа

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

1) Сконфигурируйте аутентификатор токена доступа

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

1
2
3
4
5
6
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler: App\Security\AccessTokenHandler

Этот обработчик должен реализовывать AccessTokenHandlerInterface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/Security/AccessTokenHandler.php
namespace App\Security;

use App\Repository\AccessTokenRepository;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;

class AccessTokenHandler implements AccessTokenHandlerInterface
{
    public function __construct(
        private AccessTokenRepository $repository
    ) {
    }

    public function getUserBadgeFrom(string $accessToken): UserBadge
    {
        // например, запрос к базе данных "access token", чтобы искать этот токен
        $accessToken = $this->repository->findOneByValue($token);
        if (null === $accessToken || !$accessToken->isValid()) {
            throw new BadCredentialsException('Invalid credentials.');
        }

        // и вернуть объект UserBadge, содержащий идентификатор пользователя из найденного токена
        return new UserBadge($accessToken->getUserId());
    }
}

Аутентификатор токена доступа будет использовать возвращенный идентификатор пользователя, чтобы загрузить пользователя, используя поставщика пользователей .

Warning

Важно проверить, валиден ли токен. Например, пример выше верифицирует, не истек ли у токена срок действия. При использовании самодостаточных токенов доступа, таких как JWT, обработчик должен верифицировать цифровую подпись и понять все утверждения, особенно sub, iat, nbf и exp.

2) Сконфигурируйте экстрактор токенов (необязательно)

Теперь приложение готово к обработке входящих токенов. Экстрактор токенов извлекает токен из запроса (например, заголовка или тела запроса).

По умолчанию, токен доступа читается из параметра заголовка запроса Authorization со схемой Bearer (например, Authorization: Bearer the-token-value).

Symfony предоставляет другие экстракторы, в соответствии с RFC6750:

header (по умолчанию)
Токен отправляется через заголовок запроса. Обычно - Authorization со схемой Bearer.
query_string
Токен является частью строки запроса. Обычно - access_token.
request_body
Токен является частью тела запроса во время запроса POST. Обычно - access_token.

Warning

Из-за уязвимости безопасности, связанной с методом URI, включая высокую вероятность, что будет произведена запись лога URL или тела запроса, которые содержат токен доступа, методы query_string и request_body НЕ ДОЛЖНЫ быть использованы, кроме случаев, когда невозможно переместить токен доступа в поле заголовка запроса.

Вы также можете создать пользовательский экстрактор. Класс должен реализовывать AccessTokenExtractorInterface.

1
2
3
4
5
6
7
8
9
10
11
12
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler: App\Security\AccessTokenHandler

                # использовать другой встроенный экстрактор
                token_extractors: request_body

                # или предоставить ID сервиса пользовательского экстрактора
                token_extractors: 'App\Security\CustomTokenExtractor'

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

1
2
3
4
5
6
7
8
9
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler: App\Security\AccessTokenHandler
                token_extractors:
                    - 'header'
                    - 'App\Security\CustomTokenExtractor'

3) Отправьте запрос

Это все! Ваше приложение теперь может аутентифицировать входящие запросы, используя токен API.

Используя экстрактор заголовка по умолчанию, вы можете протестировать функцию, отправив запрос таким образом:

1
2
$ curl -H 'Authorization: Bearer an-accepted-token-value' \
    https://localhost:8000/api/some-route

Настройка обработчика успеха

По умолчанию, запрос продолжается (например, запускается контроллер для маршрута). Если вы хотите настроить обработку успеха, создайте собственного обработчика успеха, создав класс, реализующий AuthenticationSuccessHandlerInterface, и сконфигурируйте ID сервиса как success_handler:

1
2
3
4
5
6
7
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler: App\Security\AccessTokenHandler
                success_handler: App\Security\Authentication\AuthenticationSuccessHandler

Tip

Если вы хотите настроить обработку неудачи по умолчанию, используйте опцию failure_handler и создайте класс, реализующий AuthenticationFailureHandlerInterface.

Использование OpenID Connect (OIDC)

OpenID Connect (OIDC) - это третье поколение технологии OpenID, которое представляет собой RESTful HTTP API, использующий JSON в качестве формата данных. OpenID Connect - это уровень аутентификации на основе фреймворка авторизации OAuth 2.0. Он позволяет проверять личность конечного пользователя на основе аутентификации, выполненной сервером авторизации.

1) Сконфигурируйте the OidcUserInfoTokenHandler

OidcUserInfoTokenHandler требует пакет ymfony/http-client для выполнения необходимых HTTP-запросов. Если вы еще не установили его, выполните эту команду:

1
$ composer require symfony/http-client

Symfony предоставляет общий OidcUserInfoTokenHandler для вызова вашего сервера OIDC и получения информации о пользователе:

1
2
3
4
5
6
7
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    oidc_user_info: https://www.example.com/realms/demo/protocol/openid-connect/userinfo

Согласно спецификации OpenID Connect, в качестве идентификатора пользователя по умолчанию используется утверждение sub. Чтобы использовать другое утверждение, укажите его в конфигурации:

1
2
3
4
5
6
7
8
9
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    oidc_user_info:
                        claim: email
                        base_uri: https://www.example.com/realms/demo/protocol/openid-connect/userinfo

Обработчик токена oidc_user_info автоматически создаёт HTTP-клиент с указанным base_uri. Если вы хотите использовать собственный клиент, вы можете указать имя сервиса с помощью опции client:

1
2
3
4
5
6
7
8
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    oidc_user_info:
                        client: oidc.client

По умолчанию, OidcUserInfoTokenHandler создаёт OidcUser с утверждениями. Чтобы создать собственный объект пользователя на основе утверждений, вы должны создать собственный UserProvider:

1
2
3
4
5
6
7
8
9
10
// src/Security/Core/User/OidcUserProvider.php
use Symfony\Component\Security\Core\User\AttributesBasedUserProviderInterface;

class OidcUserProvider implements AttributesBasedUserProviderInterface
{
    public function loadUserByIdentifier(string $identifier, array $attributes = []): UserInterface
    {
        // реализуйте вашу собственную логику, чтобы загрузить и вернуть объект пользователя
    }
}

2) Сконфигрируйте OidcTokenHandler

OidcTokenHandler требует пакет web-token/jwt-library. Если вы ещё его не установили, выполните эти команды:

1
$ composer require web-token/jwt-library

Symfony предоставляет общий OidcTokenHandler для декодирования вашего токена, проверки и получения из него информации о пользователе:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    oidc:
                        # Алгоритмы, используемые для подписи JWS
                        algorithms: ['ES256', 'RS256']
                        # JSON-зашифрованный JWK
                        keyset: '{"keys":[{"kty":"...","k":"..."}]}'
                        # Аудитория (заявление `aud`): необходимо в целях валидации
                        audience: 'api-example'
                        # Эмитенты (заявление `iss`): необходимо в целях валидации
                        issuers: ['https://oidc.example.com']

7.1

Поддержка нескольких алгоритмов для подписи JWS была представлена в Symfony 7.1. В предыдущих версиях поддерживался только алгоритм ES256.

Согласно спецификации OpenID Connect, по умолчанию в качестве идентификатора пользователя используется утверждение sub. Чтобы использовать другое утверждение, укажите его в файле конфигурации:

1
2
3
4
5
6
7
8
9
10
11
12
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    oidc:
                        claim: email
                        algorithms: ['ES256', 'RS256']
                        keyset: '{"keys":[{"kty":"...","k":"..."}]}'
                        audience: 'api-example'
                        issuers: ['https://oidc.example.com']

По умолчанию, OidcTokenHandler создаёт OidcUser с утверждениями. Чтобы создать собственного Пользователя на основе утверждений, вы должны создать собственный UserProvider:

1
2
3
4
5
6
7
8
9
10
// src/Security/Core/User/OidcUserProvider.php
use Symfony\Component\Security\Core\User\AttributesBasedUserProviderInterface;

class OidcUserProvider implements AttributesBasedUserProviderInterface
{
    public function loadUserByIdentifier(string $identifier, array $attributes = []): UserInterface
    {
        // реализуйте вашу собственную логику, чтобы загрузить и вернуть объект пользователя
    }
}

Использование CAS 2.0

7.1

Поддержка обработчиков токенов CAS была представлена в Symfony 7.1.

Центральный сервис аутентификации (CAS) - это корпоративное многоязычное
решение для единой регистрации и поставщик идентификационных данных для Интернета,
которое пытается стать комплексной платформой для аутентификации и авторизации.

Сконфигурируйте Cas2Handler

Symfony предоставляет общий Cas2Handler для вызова вашего CAS-сервера. Он требует пакет symfony/http-client для выполнения необходимых HTTP-запросов. Если вы еще не
установили его, выполните эту команду:

1
$ composer require symfony/http-client

Вы можете сконфигурировать обработчик токенов cas следующим образом:

1
2
3
4
5
6
7
8
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    cas:
                        validation_url: https://www.example.com/cas/validate

Обработчик токенов cas автоматически создает HTTP-клиента для вызова указанного validation_url. Если вы предпочитаете использовать собственного клиента, вы можете указать имя сервиса с помощью опции http_client:

1
2
3
4
5
6
7
8
9
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    cas:
                        validation_url: https://www.example.com/cas/validate
                        http_client: cas.client

По умолчанию обработчик токенов будет читать XML-ответ URL-адреса валидации с префиксом cas, но вы можете сконфигурировать другой префикс:

1
2
3
4
5
6
7
8
9
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    cas:
                        validation_url: https://www.example.com/cas/validate
                        prefix: cas-example

Создание пользователей из токена

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Security/AccessTokenHandler.php
namespace App\Security;

// ...
class AccessTokenHandler implements AccessTokenHandlerInterface
{
    // ...

    public function getUserBadgeFrom(string $accessToken): UserBadge
    {
        // получить данные из токена
        $payload = ...;

        return new UserBadge(
            $payload->getUserId(),
            fn (string $userIdentifier) => new User($userIdentifier, $payload->getRoles())
        );
    }
}

При использовании этой стратегии вы можете опустить конфигурацию user_provider.
для брандмауэров без состояния .