Как стать автором
Обновить

Symfony, OAuth, ВКонтакте

Уровень сложностиПростой

Приветствую!

Расскажу про такую в общем-то несложную задачку, как настройка OAuth2 авторизации на сайте, написанном на php, а точнее на фреймворке Symfony 7. Вдруг кому-то пригодится.

Понадобилось мне тут обновить свой старый сайт, написанный более 10 лет назад. А там была реализована авторизация через ВКонтакт, причем шла проверка и допускались только подписчики определенного паблика. Все это было написано на голом php и js. А теперь я переписал сайт на Symfony и стал думать, как сделать аналогичную авторизацию на нем. Я был уверен, что все нужное давно написано за нас, и сочинять велосипеды не хотелось. Забегая вперед скажу, что немного повелосипедить все же пришлось.

Первым делом я ухватился за рекомендованный в документации фреймворка пакет HWIOAuthBundle. Но заплутав в его описании и примерах, я это дело не осилил, а тратить кучу времени на метод проб и ошибок мне не хотелось. Тогда я нагуглил другой пакет для своих целей - knpuniversity/oauth2-client-bundle.

Этот оказался более дружелюбным и гибким. Однако для его работы понадобился еще пакет-провайдер именно под ВКонтакт. Указанный в списке провайдеров прямо на странице бандла - давно устарел. И вот его-то пришлось форкнуть и немного допилить.

  • Первым делом обновил composer.json, указав свежую версию php. Было: "php" : ">=5.6", стало: "php" : ">=8.1".

  • Далее в файле J4k\OAuth2\Client\Provider\Vkontakte.php изменил версию api на актуальную (на данный момент это $version = 5.199).

В целом если вам больше ничего не надо делать после авторизации, этого достаточно. Но мне-то надо было еще добавить проверку на участие в определенном паблике. Поэтому в тот же файл я добавил метод проверки, который вызывает соответствующий API метод ВКонтакта. Обработку ответов тоже пришлось актуализировать, тк там используется guzzle http client, он возвращает стрим, а старый код ждет массив и очень удивляется, что его ожидания не оправдались.

Кому этого функционала достаточно и лень форкать самому, может использовать мой.

Итак, пакеты установлены, теперь надо заняться настройками. Настройки бандла не сложные и их можно брать прямо с документации на гитхабе. В конфиге должен появиться файл knpu_oauth2_client.yaml, в котором я написал:

knpu_oauth2_client:
    clients:
        vkontakte_client:
            type: vkontakte
            client_id: '%env(VK_API_ID)%'
            client_secret: '%env(VK_SECRET_KEY)%'
            redirect_route: connect_vkontakte_check
            redirect_params: { }

client_id и client_secret - это ИД приложения ВКонтакте и секретный ключ для работы с ним. Кто не знает что это и для чего надо - читайте тут. У меня данный сайт уже был зарегистрирован, ключ тоже был, так что я просто занес эти параметры в переменные окружения. redirect_route - это роут, который вам надо сделать для вывода страницы прверки (описано в документации бандла).

Я сделал контроллер, который отображал бы страницу авторизации через ВКонтакт.

<?php

namespace App\Controller;

use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

#[Route('/vkontakte', name: 'connect_vkontakte_')]
class VKController extends AbstractController
{
    #[Route('/', name: 'start')]
    public function connectAction(ClientRegistry $clientRegistry)
    {
        return $clientRegistry
            // Имя клиента, указанное в config/packages/knpu_oauth2_client.yaml
            ->getClient('vkontakte_client')
            // scopes (см доку ВКонтакта, мне нужны эти)
            ->redirect(['public_profile', 'email', 'groups']);
    }

    #[Route('/check/', name: 'check')]
    public function connectCheckAction(Request $request, ClientRegistry $clientRegistry)
    {
        // Вся проверка будет в сервисе App\Security\VKAuth.php
    }
}

Далее я по инструкции создал сервис, который реализовал авторизацию. Вся разница с указанным в примере была в том, что вместо фейсбука у меня контакт, да еще добавилась проверка на участие юзера в группе. Для этого получаем наш форкнутый провайдер и используем его метод.

    public function authenticate(Request $request): Passport
    {
       /** @var KnpU\OAuth2ClientBundle\Client\Provider\VKontakteClient $client */
        $client = $this->clientRegistry->getClient('vkontakte_client');
        $accessToken = $this->fetchAccessToken($client);

        /** @var \J4k\OAuth2\Client\Provider\User $vkUser */
        $vkUser = $client->fetchUserFromToken($accessToken);

        /** @var J4k\OAuth2\Client\Provider\Vkontakte $vkProvider */
        $vkProvider = $client->getOAuth2Provider();

        // Проверка на участие юзера в группе
        $groups = $vkProvider->groupsGet($vkUser->getId(), $accessToken, ['group_id' => $this->bag->get('group_id')]);

        if (!isset($groups['groups'][0]) || ($groups['groups'][0]['is_member'] === 0 && $groups['groups'][0]['is_admin'] === 0)) {
            throw new CustomUserMessageAuthenticationException('Вам сюда нельзя.');
        }
...

Дальше все как в примере. Только в методе start поправил редирект так, чтоб он вел на мою страницу авторизации в контроллере (см. выше функцию connectAction).

Осталось еще добавить настройку в файл app/config/packages/security.yaml, чтобы закрыть сокровенное нашей авторизацией. Тут есть нюанс. Если у вас кроме этой авторизации будут еще какие-то, вам надо добавить настройку провайдера для фаерволла, котрый будет использовать oauth авторизацию. У меня другая авторизация есть, в админку люди ходят иным способом. Поэтому конфиг выглядит так:

security:
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
    providers:
        admin_provider:
            entity:
                class: App\Entity\Admin # это сущность юзера админа
                property: email
        cabinet_provider:
            entity:
                class: App\Entity\User # это сущность юзера который заходит через oauth
                property: vkId
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        admin:
            lazy: true
            pattern: ^/admin
            provider: admin_provider
            form_login:
                login_path: app_admin_login
                check_path: app_admin_login
                enable_csrf: true
            logout:
                path: /logout/
                target: app_admin_login
        main:
            provider: cabinet_provider
            custom_authenticators:
                - App\Security\VKAuth # это наш сервис авторизации
    access_control:
        - { path: ^/admin/login, roles: PUBLIC_ACCESS }
        - { path: ^/admin, roles: ROLE_ADMIN }
        - { path: ^/cabinet, roles: ROLE_USER }

То есть я создал сущность юзера, отдельную от сущности админа.

Это только один из способов, как можно провернуть данную задачу. Я выбрал, как мне было проще. Ну, всяко проще, чем разбираться с HWIOAuthBundle.

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.