用户认证与授权:在 Symfony 中实现安全控制

用户认证与授权:在 Symfony 中实现安全控制

Symfony 是一个功能强大且高度可扩展的 PHP 框架,广泛用于开发复杂的 Web 应用程序。在现代 Web 应用程序中,用户认证和授权是不可或缺的功能,用于确保应用程序的安全性和数据的隐私性。本文将详细介绍如何在 Symfony 中实现用户认证和授权,具体到源码示例,帮助您全面掌握这一重要技能。

一、项目初始化

首先,我们需要创建一个 Symfony 项目。如果您还没有安装 Symfony,可以使用以下命令进行安装:

bash 复制代码
composer create-project symfony/skeleton my_project
cd my_project

接下来,安装用于用户认证和授权的必要包:

bash 复制代码
composer require symfony/security-bundle
composer require symfony/orm-pack
composer require symfony/maker-bundle --dev

二、创建用户实体

在 Symfony 中,用户实体通常用来表示应用程序中的用户。我们可以使用 maker-bundle 快速生成用户实体:

bash 复制代码
php bin/console make:user

按照提示输入必要的信息,例如用户类名 User,是否需要存储密码等。生成的用户实体类如下:

php 复制代码
<?php

namespace App\Entity;

use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;

#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private $id;

    #[ORM\Column(type: 'string', length: 180, unique: true)]
    private $email;

    #[ORM\Column(type: 'json')]
    private $roles = [];

    #[ORM\Column(type: 'string')]
    private $password;

    public function getId(): ?int
    {
        return $id;
    }

    public function getEmail(): ?string
    {
        return $email;
    }

    public function setEmail(string $email): self
    {
        $this->email = $email;

        return $this;
    }

    /**
     * A visual identifier that represents this user.
     *
     * @see UserInterface
     */
    public function getUserIdentifier(): string
    {
        return (string) $this->email;
    }

    /**
     * @see UserInterface
     */
    public function getRoles(): array
    {
        $roles = $this->roles;
        // guarantee every user at least has ROLE_USER
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    public function setRoles(array $roles): self
    {
        $this->roles = $roles;

        return $this;
    }

    /**
     * @see PasswordAuthenticatedUserInterface
     */
    public function getPassword(): string
    {
        return $this->password;
    }

    public function setPassword(string $password): self
    {
        $this->password = $password;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function eraseCredentials()
    {
        // If you store any temporary, sensitive data on the user, clear it here
        // $this->plainPassword = null;
    }
}

三、数据库配置与迁移

接下来,我们需要配置数据库连接并进行迁移。首先,在 .env 文件中配置数据库连接信息:

dotenv 复制代码
DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name"

然后,运行以下命令以创建数据库和迁移用户实体:

bash 复制代码
php bin/console doctrine:database:create
php bin/console make:migration
php bin/console doctrine:migrations:migrate

四、实现用户注册功能

用户注册功能允许新用户在应用程序中创建账户。首先,我们需要创建一个注册表单。使用以下命令生成表单:

bash 复制代码
php bin/console make:registration-form

按照提示选择生成表单所需的选项。生成的表单类如下:

php 复制代码
<?php

namespace App\Form;

use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Length;

class RegistrationFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('email', EmailType::class)
            ->add('plainPassword', RepeatedType::class, [
                'type' => PasswordType::class,
                'first_options' => ['label' => 'Password'],
                'second_options' => ['label' => 'Repeat Password'],
                'invalid_message' => 'The password fields must match.',
                'options' => ['attr' => ['class' => 'password-field']],
                'required' => true,
                'mapped' => false,
                'constraints' => [
                    new NotBlank([
                        'message' => 'Please enter a password',
                    ]),
                    new Length([
                        'min' => 6,
                        'minMessage' => 'Your password should be at least {{ limit }} characters',
                        'max' => 4096,
                    ]),
                ],
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => User::class,
        ]);
    }
}

接下来,创建一个控制器来处理用户注册请求:

php 复制代码
<?php

namespace App\Controller;

use App\Entity\User;
use App\Form\RegistrationFormType;
use App\Security\LoginFormAuthenticator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;

class RegistrationController extends AbstractController
{
    #[Route('/register', name: 'app_register')]
    public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, UserAuthenticatorInterface $userAuthenticator, LoginFormAuthenticator $authenticator, EntityManagerInterface $entityManager): Response
    {
        $user = new User();
        $form = $this->createForm(RegistrationFormType::class, $user);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            // encode the plain password
            $user->setPassword(
                $userPasswordHasher->hashPassword(
                    $user,
                    $form->get('plainPassword')->getData()
                )
            );

            $entityManager->persist($user);
            $entityManager->flush();

            // do anything else you need here, like send an email

            return $userAuthenticator->authenticateUser(
                $user,
                $authenticator,
                $request
            );
        }

        return $this->render('registration/register.html.twig', [
            'registrationForm' => $form->createView(),
        ]);
    }
}

创建注册页面模板 templates/registration/register.html.twig

twig 复制代码
{% extends 'base.html.twig' %}

{% block body %}
<h1>Register</h1>

{{ form_start(registrationForm) }}
    {{ form_widget(registrationForm) }}
    <button type="submit" class="btn">Register</button>
{{ form_end(registrationForm) }}
{% endblock %}

五、实现用户登录功能

用户登录功能允许已注册用户访问受保护的资源。首先,使用以下命令生成登录表单:

bash 复制代码
php bin/console make:auth

按照提示选择 Login form authenticator 选项并提供必要的信息。生成的 LoginFormAuthenticator 类如下:

php 复制代码
<?php

namespace App\Security;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
    use TargetPathTrait;

    private RouterInterface $router;

    public function __construct(RouterInterface $router)
    {
        $this->router = $router;
    }

    public function supports(Request $request): bool
    {
        return 'app_login' === $request->attributes->get('_route') && $request->isMethod('POST');
    }

    public function getCredentials(Request $request)
    {
        $credentials = [
            'email' => $request->request->get('email'),
            'password' => $request->request->get('password'),
        ];
        $request->getSession()->set(Security::LAST_USERNAME, $credentials['email']);

        return $credentials;
    }

    public function getUser($credentials, UserProviderInterface $userProvider): ?UserInterface
    {
        return $userProvider->load

UserByUsername($credentials['email']);
    }

    public function checkCredentials($credentials, UserInterface $user): bool
    {
        // Check the user's password, return true if it's valid
        // For example:
        // return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
        return true;
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?RedirectResponse
    {
        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
            return new RedirectResponse($targetPath);
        }

        return new RedirectResponse($this->router->generate('homepage'));
    }

    protected function getLoginUrl(): string
    {
        return $this->router->generate('app_login');
    }
}

接下来,创建一个控制器来处理登录请求:

php 复制代码
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

class SecurityController extends AbstractController
{
    #[Route('/login', name: 'app_login')]
    public function login(AuthenticationUtils $authenticationUtils): Response
    {
        // get the login error if there is one
        $error = $authenticationUtils->getLastAuthenticationError();
        // last username entered by the user
        $lastUsername = $authenticationUtils->getLastUsername();

        return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
    }

    #[Route('/logout', name: 'app_logout')]
    public function logout(): void
    {
        throw new \Exception('This method can be blank - it will be intercepted by the logout key on your firewall');
    }
}

创建登录页面模板 templates/security/login.html.twig

twig 复制代码
{% extends 'base.html.twig' %}

{% block body %}
<h1>Login</h1>

<form action="{{ path('app_login') }}" method="post">
    <label for="email">Email:</label>
    <input type="text" id="email" name="_username" value="{{ last_username }}" required autofocus>

    <label for="password">Password:</label>
    <input type="password" id="password" name="_password" required>

    <button type="submit">Login</button>

    {% if error %}
        <div>{{ error.messageKey|trans(error.messageData, 'security') }}</div>
    {% endif %}
</form>
{% endblock %}

六、配置安全策略

为了保护应用程序的某些部分,我们需要配置安全策略。在 config/packages/security.yaml 文件中进行以下配置:

yaml 复制代码
security:
    encoders:
        App\Entity\User:
            algorithm: auto

    providers:
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        main:
            anonymous: true
            lazy: true
            provider: app_user_provider

            form_login:
                login_path: app_login
                check_path: app_login
                csrf_token_generator: security.csrf.token_manager

            logout:
                path: app_logout
                target: /

    access_control:
        - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/, roles: ROLE_USER }

七、实现角色授权

在许多应用程序中,用户的权限根据其角色而不同。我们可以在用户实体中定义角色,并在控制器中根据角色进行授权。例如:

php 复制代码
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;

class AdminController extends AbstractController
{
    #[Route('/admin', name: 'admin')]
    #[IsGranted('ROLE_ADMIN')]
    public function index(): Response
    {
        return new Response('Admin area');
    }
}

在上述示例中,只有具有 ROLE_ADMIN 角色的用户才能访问 /admin 路由。

八、保护特定资源

您可以使用注释来保护特定的控制器方法。例如:

php 复制代码
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;

class AccountController extends AbstractController
{
    #[Route('/account', name: 'account')]
    #[IsGranted('ROLE_USER')]
    public function index(): Response
    {
        return new Response('Account area');
    }
}

在上述示例中,只有具有 ROLE_USER 角色的用户才能访问 /account 路由。

九、总结

本文详细介绍了在 Symfony 中实现用户认证与授权的全过程。从项目初始化、创建用户实体、配置数据库、实现用户注册和登录功能,到配置安全策略和实现角色授权,涵盖了每个步骤的具体代码和配置示例。通过这些示例,您可以全面掌握在 Symfony 中实现安全控制的技巧,为构建安全的 Web 应用程序奠定坚实的基础。

希望本文能对您有所帮助,如果您有任何问题或需要进一步的帮助,请随时与我联系。

相关推荐
网络研究院5 小时前
英国对LastPass处以120万英镑罚款,原因是其在2022年发生数据泄露事件,影响了160万用户
网络·安全·数据·泄露·用户
是喵斯特ya6 小时前
MSF的基础使用
安全
xixixi777777 小时前
从宏观架构、核心技术、战术对抗、治理挑战和未来趋势五个层面,系统性地剖析“短信反诈骗”
安全·架构·安全架构·通信·反诈·短信反诈
金士镧(厦门)新材料有限公司8 小时前
稀土抑烟剂让 PVC 更安全
科技·安全·全文检索·生活·能源
爱倒腾的老唐9 小时前
00、Altium Designer 23 使用问题记录
笔记·php
catchadmin10 小时前
PHP 8.5 垃圾回收改进
php
醇氧11 小时前
在 Spring Service 中使用 private final Gson gson = new Gson(); 是否安全?
java·安全·spring
梦里不知身是客1111 小时前
网络安全中对称算法和非对称算法的作用和区别
安全·web安全
bleach-12 小时前
buuctf系列解题思路祥讲--[极客大挑战 2019]HardSQL1——sql报错注入
数据库·sql·安全·web安全·网络安全
知白守黑V14 小时前
OWASP 2025 LLM 应用十大安全风险深度解析
人工智能·安全·ai agent·ai智能体·ai应用·ai安全·大模型安全