跳转到主要内容
云客 提交于 25 November 2016

在drupal8中如何判定请求来源于已认证用户?不带认证信息的请求视作匿名用户,带认证信息的所有请求都需要认证,不能通过认证的请求显示质询界面,会话ID就是一种认证信息。那么是何时又是如何认证的呢?这就是本篇的主题。

认证系统是在何时开始运作的呢?它是在核心派发kernel.request事件时触发的,在认证订阅器AuthenticationSubscriber中完成 这个时机是比较早的,在得到控制器之前就已完成。

通过认证的请求会建立账户对象(不带认证信息的请求也会建立匿名账户对象)后续程序通过这个账户对象就可以方便的知道账户ID、用户名、昵称、邮件、角色、权限情况、语言偏好、时区设置、最后访问时间等等,此对象代表登录状态。

关于认证系统的主要代码存放在:\core\lib\Drupal\Core\Authentication文件夹下,我们来看一看:

首先需要明白的是真正的认证工作是被叫做“认证提供器 Authentication Provider”的对象来完成的,这个文件夹下的内容围绕它展开,但此处并无用于工作的认证提供器,它由核心模块或用户定义。

为规范使用,定义了几个接口:

AuthenticationProviderInterface:所有的认证提供器必须要实现的接口,里面仅定义了两个方法:

public function applies(Request $request); 检查请求是否携带用于认证的信息 public function authenticate(Request $request); 用请求携带的认证信息去检查能否通过认证

 AuthenticationProviderFilterInterface:认证提供器过滤接口,检查认证方法能否被用于某个路由上面: public function appliesToRoutedRequest(Request $request, $authenticated);

AuthenticationProviderChallengeInterface:认证提供器质询接口,当认证不通过时产生一个质询 public function challengeException(Request $request, \Exception $previous);

系统提供了一个认证管理器AuthenticationManager,同时实现以上三个接口

 

AuthenticationCollectorInterface:认证提供器聚集器接口,用于统一管理认证提供器,并区分它们的优先级 AuthenticationCollector是其默认实现

 

以上是文件用途,我们看看具体运作:

在drupal8中认证账户对象需要由认证提供器来建立,否则默认是匿名用户对象,要想得到已登录的用户账户对象,就需要先收集认证提供器,怎么收集呢?看认证提供器聚集器的服务定义:

 

  authentication_collector:
    class: Drupal\Core\Authentication\AuthenticationCollector
    tags:
      - { name: service_collector, tag: authentication_provider, call: addProvider }

它的标签表示它实例化后将所有带有标签名authentication_provider的服务传递给addProvider方法调用一次,这种标签用法见编译器:\core\lib\Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass.php

drupal8还提供了一个编译器:\core\lib\Drupal\Core\DependencyInjection\Compiler\AuthenticationProviderPass.php它用于给容器添加一个参数authentication_providers,此参数数组包含了系统当前存在的服务提供器。

 

如果标签为authentication_provider的服务带有标签属性,会将标签属性一并传入 系统默认只定义了一个认证提供器,它的服务id是user.authentication.cookie,在user模块中实现,来看看它的定义:

 

  user.authentication.cookie:
    class: Drupal\user\Authentication\Provider\Cookie
    arguments: ['@session_configuration', '@database']
    tags:
      - { name: authentication_provider, provider_id: 'cookie', priority: 0, global: TRUE }

以上就是服务提供器的定义和收集工作

AuthenticationCollector包含着系统提供的认证提供器,它作为参数传递给AuthenticationManager来进行认证管理

并在AuthenticationSubscriber中得以运行,服务提供器是有优先级的,从高到低依次检查对某一请求是否可用,一旦可用即停止检查并返回作为默认认证提供器,低优先级的提供器没有机会运行,系统提供的默认提供器优先级为0,提供器在进行认证操作后,如果通过则返回账户对象,否则返回空,下面我们看一看系统默认提供的认证提供器:

它位于:\core\modules\user\src\Authentication\Provider\Cookie.php 此认证提供器检测请求是否包含正确的会话ID,如果有,则从会话中取回用户ID并产生账户信息,核心代码如下:

 

/**
   * Returns the UserSession object for the given session.
   *
   * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session
   *   The session.
   *
   * @return \Drupal\Core\Session\AccountInterface|null
   *   The UserSession object for the current user, or NULL if this is an
   *   anonymous session.
   */
  protected function getUserFromSession(SessionInterface $session) {
    if ($uid = $session->get('uid')) {
      // @todo Load the User entity in SessionHandler so we don't need queries.
      // @see https://www.drupal.org/node/2345611
      $values = $this->connection
        ->query('SELECT * FROM {users_field_data} u WHERE u.uid = :uid AND u.default_langcode = 1', [':uid' => $uid])
        ->fetchAssoc();

      // Check if the user data was found and the user is active.
      if (!empty($values) && $values['status'] == 1) {
        // Add the user's roles.
        $rids = $this->connection
          ->query('SELECT roles_target_id FROM {user__roles} WHERE entity_id = :uid', [':uid' => $values['uid']])
          ->fetchCol();
        $values['roles'] = array_merge([AccountInterface::AUTHENTICATED_ROLE], $rids);

        return new UserSession($values);
      }
    }

    // This is an anonymous session.
    return NULL;
  }

 

从这个方法中你可以清楚的看出用户账户对象是如何形成的,在全局使用中会用一个账户代理对象AccountProxy封装它。

如果是一个已经登录的用户,此时(核心派发kernel.request事件时)系统中将存在一个用户账户对象。

 

 

 

以上就是认证及账户对象形成过程。

我是云客,【云游天下,做客四方】欢迎转载,但须著名出处,讨论请加qq群203286137

 

 

Drupal 版本