现代OAuth2与Symfony的Discord身份验证
#php #symfony #discord #oauth2

您正在制作与游戏相关的应用吗?您是否要求用户进行身份验证?但最重要的是,您是游戏玩家(尽管这不是必需的)吗?

本教程是为您创建的!


OAUTH2协议

如果您已经熟悉OAuth2协议,则可以跳到下一部分。

我不会就oauth2协议以及它的出色表现进行任何革命性的演讲,所以起初,我要引用官方的oauth2.net网站:

oauth 2.0是授权的行业标准协议。 OAuth 2.0专注于客户开发人员的简单性,同时为Web应用程序,桌面应用程序,手机和客厅设备提供特定的授权流。

简单的单词,这意味着用户,作为客户端,只需单击按钮即可使用Google,Facebook,Discord等外部服务连接到您的应用程序,等等。

除了简化身份验证外,它还使注册方式更加容易,因为没有!

使用此协议,主要资源是访问令牌。这里的目标是使用您的秘密API密钥获得此访问令牌,并使用它来从提供商那里检索资源(例如用户数据)。

在这里,我已经释放了我的技能,并在油漆上画了一幅画画,因此您可以理解:

OAuth2 drawing

请注意,身份验证和资源服务器可以相同。

注册您的应用

第一步,但并非最不重要的一点是,进入Discord's developer portal以注册您的应用程序。完成此操作后,您将获得API键,使您能够将请求发送到Discord API。

â!在您继续之前,请确保您有一个仍然可用的Discord帐户。

找到按钮

首先,单击“新应用程序”按钮。您可以在您的个人资料旁边的门户网站的右上角找到它:

Create application

输入名称

完成此操作后,将会出现弹出窗口,以便您可以为应用程序命名。出于教程的目的,该应用将称为“ Oauthtutorial”:

Application name popup

显然,不要忘记阅读服务条款ð

您现在可以访问该应用程序的仪表板。

创建Symfony应用程序

在这一部分中,我们将在基本的Symfony项目设置中运行。如果您已经熟悉Symfony,则可以转到下一部分。

设置项目

打开一个终端和以下命令:

symfony new --webapp DiscordOauthTutorial

如果您还没有安装Symfony CLI,尽管我强烈建议您这样做,但请运行以下无聊命令:

composer create-project symfony/skeleton:"6.3.*" DiscordOauthTutorial
cd DiscordOauthTutorial
composer require webapp

运行网络服务器

您在您喜欢的IDE 中打开了该项目后,显然是php Storm,我的意思是其他,运行以下命令:

symfony server:start

您现在已经启动了本地Web服务器,该应用程序默认情况下在https://127.0.0.1:8000/上可用。如果单击该链接,您将受到Symfony Welcome Page的欢迎。

如果您遇到问题或希望了解有关本地Web服务器的更多信息,click here

数据库内容

此部分适用于希望使用Symfony的身份验证系统对用户进行身份验证的人员。这需要使用User实体创建数据库。如果您不想这样做(which is possible),请跳到下一部分。

首先,让我们在我们的.env文件中修改数据库信息以匹配我们的数据库的设置:

DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"

让我们假设我们正在使用Mariadb。

然后运行:

php bin/console doctrine:database:create

创建实体

现在我们的数据库还活着,运行以下命令:

php bin/console make:entity

出于教程的目的,我们将调用实体User(normy)。

这里的主要目标是添加(例如)discordId属性,该属性将是string。这将保持用户的不和谐ID,因此您可以在身份验证时检索或创建它。

我们的实体应该看起来像这样:

#[ORM\Entity(repositoryClass: UserRepository::class)]
class User
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $discordId = null;

    // getters and setters
}

迁移到数据库

现在,让我们利用学说的魔法将实体推入我们的数据库。为此,学说使用migrations。这些是SQL的行,描述了自上次迁移以来数据库中发生的变化。

显然,我们不必自己生成迁移,只需运行:

php bin/console make:migration

这将在我们项目的migrations文件夹中生成一个文件。迁移名称为“版本”,后跟日期时间。

检查一下,推动它。

php bin/console doctrine:migrations:migrate

我们开始做吧

幸运的是,这个星球培养了制造伟大事物的伟大人物。我不知道KNP大学的人们是否很棒,但是据我所知,他们为帮助我们实施复杂的事情而制造了很棒的工具。因此,让我们使用以下工具之一,即KNP OAuth2 Client bundle

安装捆绑包

要安装捆绑包,运行:

composer require knpuniversity/oauth2-client-bundle

作曲家下载了捆绑包后,它将询问您是否希望执行此捆绑包的食谱,应该。食谱将生成捆绑包的配置文件,可以在config/packages/knpu_oauth2_client.yaml中找到。

如果由于某些原因没有发生,请创建文件并将其粘贴在其中:

knpu_oauth2_client:
    clients:
        # configure your clients as described here: https://github.com/knpuniversity/oauth2-client-bundle#configuration

安装Oauth Discord客户端

由于KNP软件包为OAuth身份验证提供了灵活的工具,因此您需要安装客户端才能工作。地球上另一个伟大的人已经做到了!因此,为了安装它,运行:

composer require wohali/oauth2-discord-new

More information about the client here

配置客户端

由于我们使用OAuth2对用户进行身份验证,因此我们需要API键。现场,您刚刚创建了一个Discord应用程序!让我们收集我们需要的东西!

首先,转到应用程序的仪表板,然后获取应用程序ID:

Discord developer dashboard

转到我们的.env.local文件,然后将应用程序ID粘贴到一个全新的变量中,该变量将称为DISCORD_APP_ID

DISCORD_APP_ID=app_id

对于秘密钥匙,它总是更复杂...

单击屏幕左侧的侧边栏的“ OAuth2”部分,然后单击“常规”链接:

Discord developer portal OAuth2 section

在此页面上,您应该找到以下内容:

Secret key button

单击此按钮,获取秘密键,紧紧握住它,然后将其粘贴到另一个变量中,该变量将称为DISCORD_SECRET_KEY

DISCORD_SECRET_KEY=app_secret

请注意,该应用程序的秘密密钥很重要且私密,不得泄漏。如果您在配置该项目时遇到问题,请不要发送屏幕截图,人们可以看到它。

现在我们的环境变量已经设置了,返回到yaml配置文件,然后像这样填充:

knpu_oauth2_client:
    clients:
        discord:
            type: discord
            client_id: '%env(DISCORD_APP_ID)%'
            client_secret: '%env(DISCORD_SECRET_KEY)%'
            redirect_route: auth_discord_login

您可以看到,我们有一个redirect_route参数,该参数设置了尚不存在的路由名称。这条路线是用户身份验证的魔术的地方。

创建登录路线

让我们创建一个名为DiscordControllerController。我建议您不要使用maker command,因为它也会生成一个模板,我们不想要这个模板。

创建控制器后,创建了这样的路由:

namespace App\Controller;

use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

#[AsController]
#[Route("/auth/discord", name: "auth_discord_")]
final class DiscordController
{
    #[Route("/login", name: "login")]
    public function login(Request $request, ClientRegistry $clientRegistry)
    {
    }
}

这里的方法是空的,因为登录过程将由自定义身份验证器处理。但是,如果您愿意,您可以在其中add logic in order to check authentication

为了使其清洁,我们将不和谐身份验证分为/auth/discord/前缀。

  • /auth零件要明确我们处于身份验证过程中。
  • /discord零件将不和谐验证与您可以添加的任何身份验证分开。

现在,我们的应用知道了方式,也必须不和谐。因此,让我们回到开发人员门户网站,进入OAuth2>“常规页面”。

在此页面中,您将能够设置重定向URL 。提醒您,在OAuth2过程中,是在用户在供应商页面上进行身份验证后生成的代码的URL(用户必须说“是”才能访问其数据)。

)。

所以让我们添加此URL:

Redirect URL configuration on Discord developer portal

创建身份验证器

为了管理身份验证,Symfony使用Authenticators。这些基本上是定义如何检索用户以及一旦完成工作的服务。

让我们现在创建大男孩:

<?php

namespace App\Security;

use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Security\Authenticator\OAuth2Authenticator;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Wohali\OAuth2\Client\Provider\DiscordResourceOwner;

final class DiscordAuthenticator extends OAuth2Authenticator implements AuthenticationEntryPointInterface
{
    public function __construct(
        private readonly ClientRegistry $clientRegistry,
        private readonly EntityManagerInterface $em,
        private readonly RouterInterface $router,
        private readonly UserRepository $userRepository
    ) {}

    public function start(Request $request, AuthenticationException $authException = null): RedirectResponse
    {
        return new RedirectResponse($this->router->generate("auth_discord_start"), Response::HTTP_TEMPORARY_REDIRECT);
    }

    public function supports(Request $request): ?bool
    {
        return $request->attributes->get("_route") === "auth_discord_login";
    }

    public function authenticate(Request $request): SelfValidatingPassport
    {
        $client = $this->clientRegistry->getClient("discord");
        $accessToken = $this->fetchAccessToken($client);

        return new SelfValidatingPassport(
            new UserBadge($accessToken->getToken(), function () use ($accessToken, $client) {
                /** @var DiscordResourceOwner $discordUser */
                $discordUser = $client->fetchUserFromToken($accessToken);

                $user = $this->userRepository->findOneBy(["discordId" => $discordUser->getId()]);

                if (null === $user) {
                    $user = new User();
                    $user->setDiscordId($discordUser->getId());

                    $this->em->persist($user);
                }

                $this->em->flush();

                return $user;
            })
        );
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        // redirect to user to your post authentication page (e.g. dashboard, home)
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        // do something
    }
}

让我们快速浏览这里发生的事情:

  • start方法定义了身份验证的入口点。这里的目标是将用户重定向到Discord的门户(换句话说,启动OAuth2进程)。如您所见,它引用了我们尚未创建的路由。

  • supports方法定义了当前请求是否必须由我们的身份验证器支持。仅当当前路由是我们之前创建的登录路线时,才会发生。

  • authenticate方法是身份验证者的核心。它必须返回一个将包含用户进行身份验证的koude18

  • onAuthenticationSuccess定义了我们的后认证过程。我们主要想将用户重定向到他们的主页。

  • onAuthenticationFailure允许您与身份验证失败相互作用。

现在我们的身份验证者都设置了,让我们知道它是由身份验证的。现在,让我们打开config/packages/security.yaml文件,并使其看起来像这样:

security:
    providers:
        users:
            entity:
                class: 'App\Entity\User'
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            lazy: true
            provider: users
            custom_authenticators:
                - App\Security\DiscordAuthenticator
            logout:
                path: app_logout
                target: app_index

Symfony现在意识到您正在使用User实体通过App\Security\DiscordAuthenticator Authenticator对用户进行身份验证。

但是

我们还没有完成ð

使用户实体对身份验证友好

按照Symfony建立身份验证的方式,我们现在必须使我们的User实体实现UserInterface。现在应该看起来像这样:

#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $discordId = null;

    // getters and setters

    public function getRoles(): array
    {
        // TODO: Implement getRoles() method.
    }

    public function eraseCredentials()
    {
        // TODO: Implement eraseCredentials() method.
    }

    public function getUserIdentifier(): string
    {
        // TODO: Implement getUserIdentifier() method.
    }
}

本教程并非旨在潜入Symfony身份验证。如果您需要有关用户的更多信息,click here

创建入口点

回到身份验证器,别忘了入口点!为了设置它,让我们回到我们的DiscordController,然后添加auth_discord_start路线:

#[Route("/start", name: "start")]
public function start(ClientRegistry $clientRegistry): RedirectResponse
{
    return $clientRegistry->getClient("discord")->redirect(["identify"]);
}

在此方法中,我们从注册表中获取了client cam discorddiscord这个名字并非无处不在。确实,这是我们在KNP捆绑包的YAML配置中较早设置的。如果您在yaml中做到了,请不要忘记更改名称。

获得合适的客户端后,我们调用redirect方法,以将用户重定向到Discord身份验证门户。请注意,我们将list of scopes设置为[identify]。它将允许客户向/users/@me URL提出请求并收集用户的信息。

现在已经设置了所有这些,您现在可以在UI中添加一个按钮,将其重定向到此URL。用户在此URL上,它将被重定向到Discord身份验证门户:

Discord OAuth2 portal

好吧,是的,这不是重点。

用户单击“ Autoriser”按钮后,应将其重定向到/auth/discord/login URL,并以code查询参数的形式稍有奖励。此代码将用于检索访问令牌,该代币在此处自动完成。

在对我们的DEV环境进行测试时,经过身份验证,我们应该能够使用Symfony Web调试工具栏目睹身份验证成功:

Symfony web debug toolbar showing successfull authentication

如果是这样,好工作,我们的完整oauth2身份验证过程已完成并成功!

结束登记

嘿!稍等一下 !我们不是忘记了什么吗?不用担心,与Symfony断开用户非常容易。确实,您已经开始配置要记录用户的方式!

config/packages/security.yaml文件中,我们补充说:

logout:
    path: app_logout
    target: app_index

本部分定义了我们的注销路由名称是app_logout,并且将用户重定向到app_index

我们现在需要做的就是声明注销路线:

namespace App\Controller;

use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Routing\Annotation\Route;

#[AsController]
final class SecurityController
{
    #[Route('/logout', name: 'app_logout')]
    public function logout()
    {
        throw new \Exception('Logging out');
    }
}

结论

感谢您一直关注本教程的人。我希望对您有所帮助,一切都清楚。我本可以在某些时候更深入,但是您知道,该教程必须保持可读!

更重要的是,我希望您明白了OAuth2!在本教程中,我们使用不和谐。值得庆幸的是,他们有一个干净的OAuth2系统,可以帮助您介绍该协议。一旦理解协议,这只是实施的问题!

如果需要,请去查看本教程的Github repository

再次感谢您阅读我的第一篇文章。如果您需要更多任何解释,让我们在评论部分见面!

愉快的编码,别忘了PHP没有死!