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

Дата обновления перевода 2024-07-27

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

Токены доступа или токены 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
// src/Security/AccessTokenHandler.php
namespace App\Security;

use App\Repository\AccessTokenRepository;
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());
    }
}

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

Caution

Важно проверить, валиден ли токен. Например, пример выше верифицирует, не истек ли у токена срок действия. При использовании самодостаточных токенов доступа, таких как 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.

Caution

Из-за уязвимости безопасности, связанной с методом 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-signature, web-token/jwt-checker и web-token/jwt-signature-algorithm-ecdsa. Если вы ещё их не установили, выполните эти команды:

1
2
3
$ composer require web-token/jwt-signature
$ composer require web-token/jwt-checker
$ composer require web-token/jwt-signature-algorithm-ecdsa

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:
                        # Algorithm used to sign the JWS
                        algorithm: 'ES256'
                        # A JSON-encoded JWK
                        key: '{"kty":"...","k":"..."}'
                        # Audience (`aud` claim): required for validation purpose
                        audience: 'api-example'
                        # Issuers (`iss` claim): required for validation purpose
                        issuers: ['https://oidc.example.com']

Согласно спецификации 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
                        algorithm: 'ES256'
                        key: '{"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
    {
        // реализуйте вашу собственную логику, чтобы загрузить и вернуть объект пользователя
    }
}

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

Некоторые типы токенов (например, 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.
для брандмауэров без состояния .