symfony3数据验证_在Symfony 3中使用Guard更轻松地进行身份验证

symfony3数据验证

The Symfony2 security system is a complex part of the framework, one that is difficult to understand and work with for many people. It is very powerful and flexible, however not the most straightforward. For an example of custom authentication, check out my previous article that integrates Symfony with the UserApp service.

Symfony2安全系统是框架的复杂部分,许多人很难理解和使用它。 它非常强大和灵活,但是并不是最简单的。 有关自定义身份验证的示例, 请查看我以前的文章 ,该文章将Symfony与UserApp服务集成在一起。

Lock image

With the release of version 2.8 (and the much awaited version 3), a new component was accepted into the Symfony framework: Guard. The purpose of this component is to integrate with the security system and provide a very easy way for creating custom authentications. It exposes a single interface, whose methods take you from the beginning to the end of the authentication chain: logical and all grouped together.

随着2.8版(以及期待已久的3版)的发布,Symfony框架接受了一个新组件: Guard 。 该组件的目的是与安全系统集成,并提供创建自定义身份验证的非常简单的方法。 它公开了一个接口,该接口的方法将您带到身份验证链的开始到结尾:逻辑和全部分组在一起。

In this article, we are going to create a simple form authentication that requires a user to be logged in and have the ROLE_ADMIN role for each page. The original way of building a form authentication can still be used, but we will use Guard to illustrate its simplicity. You can then apply the same concept to any kind of authentication (token, social media, etc).

在本文中,我们将创建一个简单的表单身份验证,该身份验证要求用户登录并为每个页面具有ROLE_ADMIN角色。 仍然可以使用构建表单身份验证的原始方法,但是我们将使用Guard来说明其简单性。 然后,您可以将相同的概念应用于任何类型的身份验证(令牌,社交媒体等)。

If you want to follow along in your own IDE, you can clone this repository which contains our Symfony application with Guard for authentication. So, let’s begin.

如果要在自己的IDE中进行操作,则可以克隆此存储库 ,其中包含带有Guard的Symfony应用程序以进行身份​​验证。 所以,让我们开始吧。

安全配置 (The security configuration)

Any security configuration will require a User class (to represent user data) and UserProvider (to fetch user data). To keep things simple, we will go with the InMemory user provider which, in turn, uses the default Symfony User class. So our security.yml file can start off like this:

任何安全配置都将需要一个User类(代表用户数据)和UserProvider(来获取用户数据)。 为简单InMemory ,我们将使用InMemory用户提供程序,后者使用默认的Symfony User类。 因此,我们的security.yml文件可以像这样开始:

security:
    providers:
        in_memory:
            memory:
                users:
                    admin:
                        password: admin
                        roles: 'ROLE_ADMIN'

For more information about the Symfony security system and what this file can contain, I strongly recommend you read the book entry on the Symfony website.

有关Symfony安全系统以及此文件可以包含的内容的更多信息,强烈建议您阅读Symfony网站上的书籍条目

Our InMemory provider now has one single hardcoded test user which has the ROLE_ADMIN.

现在,我们的InMemory提供程序只有一个具有ROLE_ADMIN硬编码测试用户。

Under the firewalls key, we can define our firewall:

firewalls键下,我们可以定义防火墙:

secured_area
            anonymous: ~
            logout:
                path:   /logout
                target: /
            guard:
                authenticators:
                    - form_authenticator

This basically says that anonymous users can access the firewall and that the path to log a user out is /logout. The new part is the guard key that indicates which authenticator is used for the Guard configuration of this firewall: form_authenticator. This needs to be the service name and we’ll see in a minute how and where it’s defined.

这基本上表明匿名用户可以访问防火墙,而/logout用户的路径为/logout 。 新的部分是guard密钥,该密钥指示用于此防火墙的保护配置的身份验证器: form_authenticator 。 这必须是服务名称,我们将在一分钟内看到其定义方式和位置。

Lastly in the security configuration, we can specify some access rules:

最后,在安全配置中,我们可以指定一些访问规则:

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

In this example we specify that users who are not logged in can access only the /login path. For all the rest, the ROLE_ADMIN role is needed.

在此示例中,我们指定未登录的用户只能访问/login路径。 对于所有其他内容,需要ROLE_ADMIN角色。

登录控制器 (The Login Controller)

Before moving on to the actual authenticator, let’s see what we have in place for the actual login form and controller. Inside of our DefaultController we have this action:

在继续使用实际的身份验证器之前,让我们看一下对实际的登录表单和控制器有什么用。 在我们的DefaultController内部,我们有以下操作:

/**
   * @Route("/login", name="login")
   */
  public function loginAction(Request $request)
  {
    $user = $this->getUser();
    if ($user instanceof UserInterface) {
      return $this->redirectToRoute('homepage');
    }

    /** @var AuthenticationException $exception */
    $exception = $this->get('security.authentication_utils')
      ->getLastAuthenticationError();

    return $this->render('default/login.html.twig', [
      'error' => $exception ? $exception->getMessage() : NULL,
    ]);
  }

Defining the /login route, this action is responsible for showing a rudimentary login form to users that are not logged in. The Twig template for this form looks something like this:

定义/login路由,此操作负责向未登录的用户显示基本的登录表单。此表单的Twig模板如下所示:

{{ error }}

<form action="{{ path('login') }}" method="POST">
    <label for="username">Username</label>
    <input type="text" name="username" class="form-control" id="username" placeholder="Username">
    <label for="password">Password</label>
    <input type="password" name="password" class="form-control"
           id="password" placeholder="Password">
    <button type="submit">Login</button>
</form>

So far nothing special. Just a simple form markup directly in HTML for quick scaffolding that posts back to the same /login path.

到目前为止,没有什么特别的。 只需直接在HTML中进行简单的表单标记即可快速生成返回到相同/login路径的脚手架。

卫兵认证服务 (The Guard Authenticator Service)

We referenced in the security configuration the service for our Guard authenticator. Let’s make sure that we define this service in the services.yml file:

我们在安全配置中引用了我们的Guard身份验证器的服务。 确保我们在services.yml文件中定义了该服务:

services:
    form_authenticator:
          class: AppBundle\Security\FormAuthenticator
          arguments: ["@router"]

This service references our FormAuthenticator class:

该服务引用了我们的FormAuthenticator类:

namespace AppBundle\Security;

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\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class FormAuthenticator extends AbstractGuardAuthenticator
{

  /**
   * @var \Symfony\Component\Routing\RouterInterface
   */
  private $router;

  /**
   * Default message for authentication failure.
   *
   * @var string
   */
  private $failMessage = 'Invalid credentials';

  /**
   * Creates a new instance of FormAuthenticator
   */
  public function __construct(RouterInterface $router) {
    $this->router = $router;
  }

  /**
   * {@inheritdoc}
   */
  public function getCredentials(Request $request)
  {
    if ($request->getPathInfo() != '/login' || !$request->isMethod('POST')) {
      return;
    }

    return array(
      'username' => $request->request->get('username'),
      'password' => $request->request->get('password'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getUser($credentials, UserProviderInterface $userProvider)
  {
    if (!$userProvider instanceof InMemoryUserProvider) {
      return;
    }

    try {
      return $userProvider->loadUserByUsername($credentials['username']);
    }
    catch (UsernameNotFoundException $e) {
      throw new CustomUserMessageAuthenticationException($this->failMessage);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function checkCredentials($credentials, UserInterface $user)
  {
    if ($user->getPassword() === $credentials['password']) {
      return true;
    }
    throw new CustomUserMessageAuthenticationException($this->failMessage);
  }

  /**
   * {@inheritdoc}
   */
  public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
  {
    $url = $this->router->generate('homepage');
    return new RedirectResponse($url);
  }

  /**
   * {@inheritdoc}
   */
  public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
  {
    $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
    $url = $this->router->generate('login');
    return new RedirectResponse($url);
  }

  /**
   * {@inheritdoc}
   */
  public function start(Request $request, AuthenticationException $authException = null)
  {
    $url = $this->router->generate('login');
    return new RedirectResponse($url);
  }

  /**
   * {@inheritdoc}
   */
  public function supportsRememberMe()
  {
    return false;
  }
}

Although this seems like a lot, it really isn’t. Let’s go step by step and understand what is happening here.

尽管这看起来很多,但实际上并非如此。 让我们一步一步来了解这里发生了什么。

First, we are placing this in the Security folder of our bundle. This is just a personal choice, we are not obligated to it. Then, we are extending from the AbstractGuardAuthenticator because that already takes care of implementing a required method from the GuardAuthenticatorInterface interface. If we needed a specific token class to represent our authentication, we could just implement the interface and its createAuthenticatedToken() method as well. For now, we don’t have to.

首先,我们将其放置在捆绑软件的“ Security文件夹中。 这只是个人选择,我们没有义务。 然后,我们从AbstractGuardAuthenticator进行扩展,因为该方法已经可以通过GuardAuthenticatorInterface接口实现所需的方法。 如果我们需要一个特定的令牌类来表示我们的身份验证,则可以只实现该接口及其createAuthenticatedToken()方法。 目前,我们不必这样做。

The implementation of the interface methods is the crux of the matter and together these form the pipeline of authentication from the moment a user tries to access the site to the one in which they are granted or denied access.

接口方法的实现是问题的关键,并且这些方法共同构成了从用户尝试访问站点到授予或拒绝访问该站点的那一刻的身份验证管道。

We start with the getCredentials() method which gets called on every request. The purpose of this method is to either return credentials data from the request or NULL (either denies access, allows another authenticator to then provide credentials, or calls the start() method). Since only POST requests to our /login path are containers of credentials, we return an array with the submitted username and password only if this is the case.

我们从getCredentials()方法开始,该方法在每次请求时都会被调用。 此方法的目的是从请求中返回凭据数据或返回NULL(拒绝访问,允许其他身份验证器随后提供凭据或调用start()方法)。 由于只有对我们/login路径的POST请求是凭据的容器,因此只有在这种情况下,我们才返回包含提交的用户名和密码的数组。

The very next method that gets called if getCredentials() does not return NULL is getUser(). The latter is responsible for loading up a user based on the credentials we receive from the former method. Using the default user provider (in our case the InMemory provider), we load and return the user based on their username. Although we can return NULL here as well to trigger authentication failure, we can choose to throw a CustomUserMessageAuthenticationException to specify our own custom failure message.

如果getCredentials()不返回NULL的下一个方法就是getUser() 。 后者负责根据我们从前一种方法获得的凭据加载用户。 使用默认的用户提供程序(在我们的示例中为InMemory提供程序),我们根据用户名加载并返回用户。 尽管我们也可以在此处返回NULL来触发身份验证失败,但是我们可以选择引发CustomUserMessageAuthenticationException来指定我们自己的自定义失败消息。

If, however, a user is retrieved and returned, the checkCredentials() method kicks in. Quite expectedly, the purpose of this method is to verify that the passed credentials match those of the user that was found. And the same rules apply, if we return NULL or throw an exception, we fail the authentication.

但是,如果检索并返回了用户,则将使用checkCredentials()方法。可以预期,此方法的目的是验证传递的凭证是否与找到的用户的凭证匹配。 同样的规则适用,如果我们返回NULL或引发异常,则认证将失败。

At this point, the user gets logged in if the credentials match. In this case, the onAuthenticationSuccess() method is called and here we can again do what we want. In our case, redirecting to the homepage seems like a good enough example. On the contrary, if the authentication fails, the onAuthenticationFailure() method is called. What we do is redirect back to the /login page but not before setting the last authentication exception in the session so that we can display the error message above the form. This method is called at any of the authentication failure points of the pipeline.

此时,如果凭据匹配,则用户将登录。 在这种情况下,将调用onAuthenticationSuccess()方法,在这里我们可以再次执行我们想要的操作。 在我们的例子中,重定向到首页似乎是一个很好的例子。 相反,如果身份验证失败,则将调用onAuthenticationFailure()方法。 我们要做的是重定向回到/login页面,但不是在设置会话中的最后一个身份验证异常之前,否则,因此我们可以在表格上方显示错误消息。 在管道的任何身份验证失败点都调用此方法。

The start() method is the entry point into the Guard system (and application). This method is called whenever a user is trying to access a page that requires authentication but no credentials are returned by getCredentials(). In our case this means that if somebody tries to access the homepage, there are no credentials in the request so getCredentials() returns NULL. What we want to happen then is to redirect to the /login page so that the user can log in to access the homepage.

start()方法是Guard系统(和应用程序)的入口点。 每当用户尝试访问需要身份验证的页面但getCredentials() 返回任何凭据时,都会调用此方法。 在我们的案例中,这意味着如果有人尝试访问主页,则请求中没有凭据,因此getCredentials()返回NULL。 然后我们想要发生的是重定向到/login页面,以便用户可以登录以访问主页。

Let’s imagine another example: token based authentication. In such a case, each request needs to contain a token that authenticates the user. This means that getCredentials() will always have to return the credentials. If it doesn’t, the start() method would return a response indicating that access is denied (or whatever you may think of).

让我们想象另一个示例:基于令牌的身份验证。 在这种情况下,每个请求都需要包含对用户进行身份验证的令牌。 这意味着getCredentials()将始终必须返回凭据。 如果不是,则start()方法将返回响应,指示访问被拒绝(或您可能想到的任何事情)。

The final method is responsible for marking the RememberMe functionality. In our case we don’t use it so we return false. For more information about Remember Me in Symfony check out the cookbook entry.

最后一种方法负责标记RememberMe功能。 在我们的情况下,我们不使用它,所以我们返回false。 有关Symfony中的“记住我”的更多信息, 查看食谱条目。

结论 (Conclusion)

We now have a fully functioning login system using the Guard component. Having mentioned above the example of the token authentication, we could implement one and have it work in tandem with the one we wrote in this article. Multiple authenticators can exist like so:

现在,我们有了使用Guard组件的功能齐全的登录系统。 上面已经提到了令牌身份验证的示例,我们可以实现一个令牌身份验证,并使它与本文中编写的令牌协同工作。 可以存在多个身份验证器,如下所示:

guard:
                authenticators:
                    - form_authenticator
                    - token_authenticator
                entry_point: form_authenticator

If we configure multiple authenticators, we also need to specify which one should be the entry point (whose start() method will be called when a user tries to access a resource without providing credentials).

如果我们配置多个身份验证者,我们还需要指定哪个身份验证者为入口点(当用户尝试在不提供凭据的情况下访问资源时,将调用其start()方法)。

Note that Guard does not replace anything that exists in Symfony but adds to it. So existing security set-ups are bound to continue working. For instance, the form_login or simple_form as we used it previously will continue to work.

请注意,Guard不会替换Symfony中存在的任何内容,而是会对其进行添加。 因此,现有的安全设置必然会继续起作用。 例如, 我们之前使用form_loginsimple_form将继续起作用。

Have you tried Guard out yet? How do you feel about it? Let us know!

您是否尝试过Guard? 你怎么看这件事? 让我们知道!

翻译自: https://www.sitepoint.com/easier-authentication-with-guard-in-symfony-3/

symfony3数据验证

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值