如何从数据库(实体提供者)读取安全用户(转自http://wiki.jikexueyuan.com/project/symfony-cookbook/entity-provider.html)
Symfony 的安全系统可以通过活动目录或开放授权服务器像数据库一样从任何地方加载安全用户。这篇文章将告诉你如何通过一个 Doctrine entity 从数据库加载用户信息。
前言
在开始之前,您应该检查 FOSUserBundle。这个外部包允许您从数据库加载用户信息(就像你会在这里学到的)并为您提供内置路径和控制器用于一些事务,比如登录、注册和忘记密码。但是,如果您需要大量自定义用户系统或者如果你想了解工作原理,本教程是更好的。
通过 Doctrine entity 加载用户信息有两个基本步骤:
之后,您可以了解更多关于禁止非活动用户,使用自定义查询和序列化会话用户的相关信息。
1) 创建您的用户实体
此项,假设您在一个应用程序包内已经有一个用户对象包含下列字段:id,username,password,email 和 isActive。
// src/AppBundle/Entity/User.php
namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface; /**
* @ORM\Table(name="app_users")
* @ORM\Entity(repositoryClass="AppBundle\Entity\UserRepository")
*/
class User implements UserInterface, \Serializable
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id; /**
* @ORM\Column(type="string", length=25, unique=true)
*/
private $username; /**
* @ORM\Column(type="string", length=64)
*/
private $password; /**
* @ORM\Column(type="string", length=60, unique=true)
*/
private $email; /**
* @ORM\Column(name="is_active", type="boolean")
*/
private $isActive; public function __construct()
{
$this->isActive = true;
// may not be needed, see section on salt below
// $this->salt = md5(uniqid(null, true));
} public function getUsername()
{
return $this->username;
} public function getSalt()
{
// you *may* need a real salt depending on your encoder
// see section on salt below
return null;
} public function getPassword()
{
return $this->password;
} public function getRoles()
{
return array('ROLE_USER');
} public function eraseCredentials()
{
} /** @see \Serializable::serialize() */
public function serialize()
{
return serialize(array(
$this->id,
$this->username,
$this->password,
// see section on salt below
// $this->salt,
));
} /** @see \Serializable::unserialize() */
public function unserialize($serialized)
{
list (
$this->id,
$this->username,
$this->password,
// see section on salt below
// $this->salt
) = unserialize($serialized);
}
}
为了让代码简短,一些 getter 和 setter 方法没有显示。但你可以通过运行生成这些:
$ php app/console doctrine:generate:entities AppBundle/Entity/User
接下来,确保创建数据库表:
$ php app/console doctrine:schema:update --force
什么是用户接口?
目前为止,这只是一个普通的对象。 但是为了在安全系统中使用此类,它必须实现用户接口。这要求该类有以下五种方法:
- [getRoles()](http://api.symfony.com/2.7/Symfony/Component/Security/Core/User/UserInterface.html#getRoles())
- [getPassword()](http://api.symfony.com/2.7/Symfony/Component/Security/Core/User/UserInterface.html#getPassword())
- [getSalt()](http://api.symfony.com/2.7/Symfony/Component/Security/Core/User/UserInterface.html#getSalt())
- [getUsername()](http://api.symfony.com/2.7/Symfony/Component/Security/Core/User/UserInterface.html#getUsername())
- [eraseCredentials()](http://api.symfony.com/2.7/Symfony/Component/Security/Core/User/UserInterface.html#eraseCredentials())
要了解这五个方法的更多信息,请参见用户接口。
序列化和反序列化方法做什么?
在每个请求末,用户对象是序列化会话的。对下一个请求,它是非序列化的。要帮助 PHP 正确做到这一点,您需要实现可序列化。但你不必序列化任何东西:你只需要几个字段(上面所示的那些加上一些额外的字段,如果你决定实现 AdvancedUserInterface 的话)。对于每个请求,id 用于从数据库中查询一个新的用户对象。
想要了解更多吗?详见理解序列化和用户如何在会话中进行保存。
2) 从对象加载配置安全性
现在,您已经有了一个实现了用户接口的用户对象,你只需要在 security.yml 中把这些告诉 Symfony 的安全系统。
在这个例子中,用户将通过 HTTP 基本身份验证输入用户名和密码。Symfony 会查询一个和用户名匹配的用户对象,然后检查密码(通常检查密码的用时较短):
YAML:
# app/config/security.yml
security:
encoders:
AppBundle\Entity\User:
algorithm: bcrypt # ... providers:
our_db_provider:
entity:
class: AppBundle:User
property: username
# if you're using multiple entity managers
# manager_name: customer firewalls:
default:
pattern: ^/
http_basic: ~
provider: our_db_provider # ...
XML:
<!-- app/config/security.xml -->
<config>
<encoder class="AppBundle\Entity\User"
algorithm="bcrypt"
/> <!-- ... --> <provider name="our_db_provider">
<entity class="AppBundle:User" property="username" />
</provider> <firewall name="default" pattern="^/" provider="our_db_provider">
<http-basic />
</firewall> <!-- ... -->
</config>
PHP:
// app/config/security.php
$container->loadFromExtension('security', array(
'encoders' => array(
'AppBundle\Entity\User' => array(
'algorithm' => 'bcrypt',
),
),
// ...
'providers' => array(
'our_db_provider' => array(
'entity' => array(
'class' => 'AppBundle:User',
'property' => 'username',
),
),
),
'firewalls' => array(
'default' => array(
'pattern' => '^/',
'http_basic' => null,
'provider' => 'our_db_provider',
),
),
// ...
));
首先,encoder 部分告诉 Symfony 期望使用 bcrypt 对数据库中的密码进行编码。第二,providers 部分创建一个叫 our_db_provider 的 "user provider",它知道通过 username 属性在您的 AppBundle:User 对象中查询。其中,our_db_provider 这个名字并不重要:它只需要在你的防火墙下匹配 provider 的密钥的值。或者,如果您没有在防火墙下设置 provider 密钥,第一个 “user provider” 会被自动使用。
If you're using PHP 5.4 or lower, you'll need to install the ircmaxell/password-compat library via Composer in order to be able to use the bcrypt encoder:如果您使用的是PHP 5.4 或更低版本,为了能够使用 bcrypt 编码器,您需要通过 Composer 安装 ircmaxell/password-compat 库:
If you're using PHP 5.4 or lower, you'll need to install the ircmaxell/password-compat library via Composer in order to be able to use the bcrypt encoder:如果您使用的是PHP 5.4 或更低版本,为了能够使用 bcrypt 编码器,您需要通过 Composer 安装 ircmaxell/password-compat 库:
创建您的第一个用户
要添加用户,您可以实现一个注册表单或添加一些 fixtures。这只是一个普通的对象,所以没什么棘手,除了您需要对每个用户的密码进行加密。但别担心,Symfony 会给您一个用来实现此事的服务。有关详细信息,请参见动态编码密码。
下面是从 MySQL 中导出的 app_users 表,包含了用户 admin 和密码 admin (密码是加密过的)。
$ mysql> SELECT * FROM app_users;
+----+----------+--------------------------------------------------------------+--------------------+-----------+
| id | username | password | email | is_active |
+----+----------+--------------------------------------------------------------+--------------------+-----------+
| 1 | admin | $2a$08$jHZj/wJfcVKlIwr5AvR78euJxYK7Ku5kURNhNx.7.CSIJ3Pq6LEPC | admin@example.com | 1 |
+----+----------+--------------------------------------------------------------+--------------------+-----------+
您需要一个 salt 属性吗? 如果您使用 bcrypt,不需要。否则,是的。所有的密码必须用一个 salt 进行哈希处理,但是 bcrypt 内部做了这件事。由于本教程使用 bcrypt ,User 中的 getSalt() 方法只能返回空值(它没有被使用)。如果你使用了一个不同的算法,您需要在用户对象中取消对 salt 行的注释,并且添加一个持久的 salt 属性。
禁止非活动用户(AdvancedUserInterface)
如果用户 isActive 属性设置为 false (例如,is_active 在数据库中是 0),用户仍然可以正常登录到网站。这是很容易修正的。
排除非活动用户,更改您的用户类来实现 AdvancedUserInterface。这扩展了互动演示界面,所以你只需要新的界面:
// src/AppBundle/Entity/User.php use Symfony\Component\Security\Core\User\AdvancedUserInterface;
// ... class User implements AdvancedUserInterface, \Serializable
{
// ... public function isAccountNonExpired()
{
return true;
} public function isAccountNonLocked()
{
return true;
} public function isCredentialsNonExpired()
{
return true;
} public function isEnabled()
{
return $this->isActive;
} // serialize and unserialize must be updated - see below
public function serialize()
{
return serialize(array(
// ...
$this->isActive
));
}
public function unserialize($serialized)
{
list (
// ...
$this->isActive
) = unserialize($serialized);
}
}
该 AdvancedUserInterface 接口添加四个额外的方法来验证帐户状态:
- [isAccountNonExpired()](http://api.symfony.com/2.7/Symfony/Component/Security/Core/User/AdvancedUserInterface.html#isAccountNonExpired()) 检查用户帐户是否已过期;
- [isAccountNonLocked()](http://api.symfony.com/2.7/Symfony/Component/Security/Core/User/AdvancedUserInterface.html#isAccountNonLocked()) 检查用户是否已被锁定;
- [isCredentialsNonExpired()](http://api.symfony.com/2.7/Symfony/Component/Security/Core/User/AdvancedUserInterface.html#isCredentialsNonExpired()) 检查用户凭据是否(密码)已过期;
- [isEnabled()](http://api.symfony.com/2.7/Symfony/Component/Security/Core/User/AdvancedUserInterface.html#isEnabled()) 检查用户是否已启用.
如果任何这些返回 false,则用户不会被允许登录。你可以选择坚持所有这些的属性,或任何你需要的(在这个例子中,isActive 是从数据库中选出的唯一属性)。
那么方法之间的区别是什么?每个方法返回一个略有不同的错误消息(当你在登录模板提交它们到自定义模式时,这些可以被转换)。
如果您使用 AdvancedUserInterface,您还需要添加任何由这些方法使用的属性(如 isActive)到 serialize() 和 unserialize() 方法。如果你不这样做,您的用户可能无法从每个请求上的会话中正确反序列化。
恭喜您!您的数据库加载安全系统已完成所有设置!接下来,添加一个真正的登录表单代替 HTTP 基本身份验证或继续阅读其他主题。
使用自定义查询加载用户
如果用户可以用他们的用户名或电子邮件登录,这将是很好的,这在数据库中都是独特的。不幸的是,本机实体提供者只能通过单个用户的属性处理查询。
要做到这一点,使您的用户资料库执行一个特殊的 UserProviderInterface。此接口需要三个方法:loadUserByUsername($username),refreshUser(UserInterface $user) 和 supportsClass($class):
// src/AppBundle/Entity/UserRepository.php
namespace AppBundle\Entity; use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Doctrine\ORM\EntityRepository; class UserRepository extends EntityRepository implements UserProviderInterface
{
public function loadUserByUsername($username)
{
$user = $this->createQueryBuilder('u')
->where('u.username = :username OR u.email = :email')
->setParameter('username', $username)
->setParameter('email', $username)
->getQuery()
->getOneOrNullResult(); if (null === $user) {
$message = sprintf(
'Unable to find an active admin AppBundle:User object identified by "%s".',
$username
);
throw new UsernameNotFoundException($message);
} return $user;
} public function refreshUser(UserInterface $user)
{
$class = get_class($user);
if (!$this->supportsClass($class)) {
throw new UnsupportedUserException(
sprintf(
'Instances of "%s" are not supported.',
$class
)
);
} return $this->find($user->getId());
} public function supportsClass($class)
{
return $this->getEntityName() === $class
|| is_subclass_of($class, $this->getEntityName());
}
}
有关这些方法的详细信息,请参阅 UserProviderInterface。
别忘了将 repository 类添加到该实体的映射定义。
为了完成这一点,只需在 security.yml 中移除用户提供者的 property 键值。
YAML:
# app/config/security.yml
security:
# ...
providers:
our_db_provider:
entity:
class: AppBundle:User
# ...
XML:
<!-- app/config/security.xml -->
<config>
<!-- ... --> <provider name="our_db_provider">
<entity class="AppBundle:User" />
</provider> <!-- ... -->
</config>
PHP:
// app/config/security.php
$container->loadFromExtension('security', array(
...,
'providers' => array(
'our_db_provider' => array(
'entity' => array(
'class' => 'AppBundle:User',
),
),
),
...,
));
这告诉 Symfony 不要为用户自动查询。相反,当有人登录,用户资料库上的 loadUserByUsername() 方法将被调用。
了解序列化以及用户如何在会话中保存
如果你关心在用户类中 serialize() 方法的重要性或如何将用户对象序列化或反序列化,那么这一节适合于你。如果不是,可以跳过此节。
这告诉 Symfony 不要为用户自动查询。相反,当有人登录,用户资料库上的 loadUserByUsername() 方法将被调用。
了解序列化以及用户如何在会话中保存
如果你关心在用户类中 serialize() 方法的重要性或如何将用户对象序列化或反序列化,那么这一节适合于你。如果不是,可以跳过此节。
一旦用户登录,整个用户对象序列化到会话。对下一个请求,用户对象反序列化。然后,id 属性的值是用来从数据库中查询一个新的用户对象。最后,新的用户对象与反序列化的用户对象进行比较,以确保它们表示相同的用户。例如,如果由于某种原因,两个用户对象上的用户名不匹配,则出于安全原因,该用户将被注销。
尽管这一切会自动发生,但有一些重要的副作用。
首先,Serializable 接口和其序列化和反序列化方法被添加到允许用户类序列化的会话。这可能是也可能不是根据您的设置完成的,但它可能是个好主意。从理论上讲,只有 id 需要被序列化,因为 [refreshUser()](http://api.symfony.com/2.7/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.html#refreshUser()) 方法在每个使用该 id 的请求上刷新用户(如上所述)。这给我们一个 "fresh" 用户对象。
但 Symfony 也使用用户名、salt 和密码来验证用户请求之间没有改变(如果你执行它,它也会调用你的 AdvancedUserInterface 方法)。未能序列化这些会导致你在每个请求上被注销。如果您的用户实现 EquatableInterface,而不是检查这些属性,你的 isEqualTo 方法只是调用,那么您可以检查所需的任何属性。除非你理解这一点,您可能不需要实现该接口或担心这些。
更多翻译请查看(http://wiki.jikexueyuan.com/project/symfony-cookbook/)
如何从数据库(实体提供者)读取安全用户(转自http://wiki.jikexueyuan.com/project/symfony-cookbook/entity-provider.html)的更多相关文章
- Android基础笔记(十四)- 内容提供者读取联系人
利用内容提供者读取联系人 利用内容提供者插入联系人 内容观察者的原理 利用内容观察者监听系统应用数据库或者自己应用数据库的变化 利用内容提供者读取联系人 读取联系人相对于读取短信来说就复杂非常多了,我 ...
- ASP.NET Core搭建多层网站架构【5-网站数据库实体设计及映射配置】
2020/01/29, ASP.NET Core 3.1, VS2019, EntityFrameworkCore 3.1.1, Microsoft.Extensions.Logging.Consol ...
- T4教程2 T4模版引擎之生成数据库实体类
T4模版引擎之生成数据库实体类 在通过T4模版引擎之基础入门 对T4有了初步印象后,我们开始实战篇.T4模板引擎可以当做一个代码生成器,代码生成器的职责当然是用来生成代码(这不是废话吗).而这其中 ...
- 新浪SAE数据库信息wordpress设置(用户&密码&主地址)
新浪SAE数据库信息wordpress设置(用户&密码&主地址) 此账号仅能在SAE平台上使用,不能从外部连接我们建议开发者使用SaeMysql操作数据库如果您想自己实现数据库相关操作 ...
- [转]T4模版引擎之生成数据库实体类
本文转自:http://www.cnblogs.com/lzrabbit/archive/2012/07/18/2597953.html 在通过T4模版引擎之基础入门 对T4有了初步印象后,我们开始实 ...
- Oralce新建数据库、新建远程登录用户全过程
Oracle安装完后,其中有一个缺省的数据库,除了这个缺省的数据库外,我们还可以创建自己的数据库. 对于初学者来说,为了避免麻烦,可以用'Database Configuration Assi ...
- T4模版引擎之生成数据库实体类
在通过T4模版引擎之基础入门 对T4有了初步印象后,我们开始实战篇.T4模板引擎可以当做一个代码生成器,代码生成器的职责当然是用来生成代码(这不是废话吗).而这其中我们使用的最普遍的是根据数据库生成实 ...
- 在mysql数据库中创建oracle scott用户的四个表及插入初始化数据
在mysql数据库中创建oracle scott用户的四个表及插入初始化数据 /* 功能:创建 scott 数据库中的 dept 表 */ create table dept( deptno int ...
- C# T4 模板 数据库实体类生成模板(带注释,娱乐用)
说明:..,有些工具生成实体类没注释,不能和SqlServer的MS_Description属性一起使用,然后照着网上的资源,随便写了个生成模板,自娱自乐向,其实卵用都没有参考教程 1.htt ...
随机推荐
- Python 一路走来 Django
Web 框架 (本质:socket) Python web框架 自己实现socket - Tornado 基于wsgi ...
- cygwin编译SDL1.2
1.下载了一个SDL-1.2.14.tar.gz 2.下载一个cygwin64对SDL-1.2.14.tar.gz解压 tar -zxvf SDL-1.2.14.tar.gz 在网上找的大概是需要需要 ...
- Android 对话框简介
对话框(Dialog)是程序运行过程中弹出的窗口,Android中有好多种对话框,如警告对话框,进度对话框,列表对话框,单选对话框,日期选择对话框,时间选择对话框等: 下面用几个例子来演示一下各种对话 ...
- bzoj1143
题目:http://www.lydsy.com/JudgeOnline/problem.php?id=1143 首先用传递闭包,知道一个点是否可以到达另一个点,即mp[i][j]==1表示i可以到j: ...
- AS3排序
package { import flash.display.Sprite; public class Sort extends Sprite { private var arr:Vector.< ...
- cf492C Vanya and Exams
C. Vanya and Exams time limit per test 1 second memory limit per test 256 megabytes input standard i ...
- C++指针的操作和运算(转)
既然指针是一种数据类型,那么它也应该有对应的操作或运算,正如整数能做加减乘除一样.但是每一种操作或运算都应该对这种数据类型有意义.比如两个实数可以用关系运算得知哪个大哪个小,而两个虚数却不能使用关系运 ...
- first move advantage_百度搜索
first move advantage_百度搜索 先动优势
- cumber + selenium +java自动化测试
1.新建一个maven项目,pom文件为: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi=&quo ...
- Eclipse+Java+OpenCV246人脸识别
1.环境搭建:见上一篇博客 整个项目的结构图: 2.编写DetectFaceDemo.java,代码如下: package com.njupt.zhb.test; import org.opencv. ...