Як використовувати аутентифікацію токена доступу

Дата оновлення перекладу 2024-05-29

Як використовувати аутентифікацію токена доступу

Токени доступу або токени 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 заради брандмауерів без стану .