授权是验证用户是否有足够权限执行某个操作的过程。Yii 提供两种授权方法:访问控制过滤器 (ACF) 和基于角色的访问控制 (RBAC)。
访问控制过滤器 (ACF) 是一种简单的授权方法,它以 yii\filters\AccessControl 的形式实现,最适合那些只需要一些简单访问控制的应用程序。顾名思义,ACF 是一个操作 过滤器,可以在控制器或模块中使用。当用户请求执行操作时,ACF 会检查一个 访问规则 列表,以确定用户是否被允许访问所请求的操作。
以下代码展示了如何在 site
控制器中使用 ACF
use yii\web\Controller;
use yii\filters\AccessControl;
class SiteController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::class,
'only' => ['login', 'logout', 'signup'],
'rules' => [
[
'allow' => true,
'actions' => ['login', 'signup'],
'roles' => ['?'],
],
[
'allow' => true,
'actions' => ['logout'],
'roles' => ['@'],
],
],
],
];
}
// ...
}
在上面的代码中,ACF 作为行为附加到 site
控制器。这是使用操作过滤器的典型方法。only
选项指定 ACF 应该只应用于 login
、logout
和 signup
操作。site
控制器中的所有其他操作都不受访问控制的限制。rules
选项列出了 访问规则,它表示如下
login
和 signup
操作。roles
选项包含一个问号 ?
,它是一个特殊标记,代表“访客用户”。logout
操作。@
字符是另一个特殊标记,代表“已进行身份验证的用户”。ACF 通过从上到下逐一检查访问规则来执行授权检查,直到找到与当前执行上下文匹配的规则。然后使用匹配规则的 allow
值来判断用户是否被授权。如果没有任何规则匹配,则意味着用户没有被授权,ACF 会停止进一步的操作执行。
当 ACF 确定用户没有被授权访问当前操作时,它默认会采取以下措施
你可以通过配置 yii\filters\AccessControl::$denyCallback 属性来定制此行为,如下所示
[
'class' => AccessControl::class,
...
'denyCallback' => function ($rule, $action) {
throw new \Exception('You are not allowed to access this page');
}
]
访问规则 支持许多选项。以下是对支持选项的总结。你也可以扩展 yii\filters\AccessRule 来创建自己的自定义访问规则类。
allow:指定这是一个“允许”还是“拒绝”规则。
actions: 指定此规则匹配的操作。这应该是一个操作 ID 数组。比较区分大小写。如果此选项为空或未设置,则意味着此规则适用于所有操作。
controllers: 指定此规则匹配的控制器。这应该是一个控制器 ID 数组。每个控制器 ID 都会以模块 ID(如果有)作为前缀。比较区分大小写。如果此选项为空或未设置,则意味着此规则适用于所有控制器。
roles: 指定此规则匹配的用户角色。识别两种特殊角色,它们通过 yii\web\User::$isGuest 检查。
?
: 匹配访客用户(尚未进行身份验证)。@
: 匹配经过身份验证的用户。使用其他角色名称将触发对 yii\web\User::can() 的调用,这需要启用 RBAC(将在下一小节中介绍)。如果此选项为空或未设置,则意味着此规则适用于所有角色。
roleParams: 指定将传递给 yii\web\User::can() 的参数。请参阅下面介绍 RBAC 规则的部分,了解如何使用它。如果此选项为空或未设置,则不会传递任何参数。
ips: 指定此规则匹配的 客户端 IP 地址。IP 地址可以在末尾包含通配符 *
,以便它匹配具有相同前缀的 IP 地址。例如,'192.168.*' 匹配 '192.168.' 段中的所有 IP 地址。如果此选项为空或未设置,则意味着此规则适用于所有 IP 地址。
verbs: 指定此规则匹配的请求方法(例如 GET
、POST
)。比较不区分大小写。
matchCallback: 指定应调用的 PHP 可调用函数,以确定是否应应用此规则。
denyCallback: 指定当此规则拒绝访问时应调用的 PHP 可调用函数。
下面是一个示例,展示了如何使用 matchCallback
选项,它允许您编写任意访问检查逻辑。
use yii\filters\AccessControl;
class SiteController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::class,
'only' => ['special-callback'],
'rules' => [
[
'actions' => ['special-callback'],
'allow' => true,
'matchCallback' => function ($rule, $action) {
return date('d-m') === '31-10';
}
],
],
],
];
}
// Match callback called! This page can be accessed only each October 31st
public function actionSpecialCallback()
{
return $this->render('happy-halloween');
}
}
基于角色的访问控制 (RBAC) 提供了一种简单但功能强大的集中式访问控制。有关将 RBAC 与其他更传统的访问控制方案进行比较的详细信息,请参阅 维基百科。
Yii 实现了一个通用层次结构 RBAC,遵循 NIST RBAC 模型。它通过 authManager 应用程序组件 提供 RBAC 功能。
使用 RBAC 包括两部分工作。第一部分是建立 RBAC 授权数据,第二部分是在需要的地方使用授权数据执行访问检查。
为了便于我们接下来进行说明,我们将首先介绍一些基本的 RBAC 概念。
角色代表一组权限(例如创建帖子、更新帖子)。一个角色可以分配给一个或多个用户。要检查用户是否具有指定的权限,我们可以检查用户是否被分配了包含该权限的角色。
与每个角色或权限相关联的可能存在一个规则。规则代表在访问检查期间将执行的一段代码,以确定相应的角色或权限是否适用于当前用户。例如,“更新帖子”权限可能有一个规则,检查当前用户是否为帖子创建者。在访问检查期间,如果用户不是帖子创建者,则他/她将被视为没有“更新帖子”权限。
角色和权限都可以组织在层次结构中。特别是,一个角色可能包含其他角色或权限;一个权限可能包含其他权限。Yii 实现了一个偏序层次结构,其中包括更特殊的树层次结构。虽然一个角色可以包含一个权限,但反之则不为true。
在我们开始定义授权数据和执行访问检查之前,我们需要配置 authManager 应用程序组件。Yii 提供两种类型的授权管理器:yii\rbac\PhpManager 和 yii\rbac\DbManager。前者使用 PHP 脚本文件存储授权数据,而后者将授权数据存储在数据库中。如果您的应用程序不需要非常动态的角色和权限管理,您可能会考虑使用前者。
PhpManager
¶以下代码展示了如何在应用程序配置中使用 yii\rbac\PhpManager 类配置 authManager
return [
// ...
'components' => [
'authManager' => [
'class' => 'yii\rbac\PhpManager',
],
// ...
],
];
现在可以通过 \Yii::$app->authManager
访问 authManager
。
默认情况下,yii\rbac\PhpManager 将 RBAC 数据存储在 @app/rbac
目录下的文件中。如果需要在线更改权限层次结构,请确保该目录及其中的所有文件都可由 Web 服务器进程写入。
DbManager
¶以下代码展示了如何在应用程序配置中使用 yii\rbac\DbManager 类配置 authManager
return [
// ...
'components' => [
'authManager' => [
'class' => 'yii\rbac\DbManager',
// uncomment if you want to cache RBAC items hierarchy
// 'cache' => 'cache',
],
// ...
],
];
注意:如果您使用的是 yii2-basic-app 模板,则有一个
config/console.php
配置文件,其中需要在config/web.php
中额外声明authManager
。如果是 yii2-advanced-app,则应仅在common/config/main.php
中声明authManager
一次。
DbManager
使用四个数据库表来存储其数据
在您继续之前,您需要在数据库中创建这些表。为此,您可以使用存储在 @yii/rbac/migrations
中的迁移
yii migrate --migrationPath=@yii/rbac/migrations
在 分离的迁移 部分中阅读有关使用来自不同命名空间的迁移的更多信息。
现在可以通过 \Yii::$app->authManager
访问 authManager
。
构建授权数据就是关于以下任务
根据授权灵活性要求,以上任务可以以不同的方式完成。如果您的权限层次结构旨在仅由开发人员更改,则可以使用迁移或控制台命令。迁移的优点是它可以与其他迁移一起执行。控制台命令的优点是您可以很好地概述代码中的层次结构,而不是将其散布在多个迁移中。
无论哪种方式,您最终都会得到以下 RBAC 层次结构
如果您需要动态地形成权限层次结构,则需要一个 UI 或一个控制台命令。用于构建层次结构本身的 API 将不会有任何不同。
您可以使用 迁移 来通过 authManager
提供的 API 初始化和修改层次结构。
使用 ./yii migrate/create init_rbac
创建新的迁移,然后实现创建层次结构
<?php
use yii\db\Migration;
class m170124_084304_init_rbac extends Migration
{
public function up()
{
$auth = Yii::$app->authManager;
// add "createPost" permission
$createPost = $auth->createPermission('createPost');
$createPost->description = 'Create a post';
$auth->add($createPost);
// add "updatePost" permission
$updatePost = $auth->createPermission('updatePost');
$updatePost->description = 'Update post';
$auth->add($updatePost);
// add "author" role and give this role the "createPost" permission
$author = $auth->createRole('author');
$auth->add($author);
$auth->addChild($author, $createPost);
// add "admin" role and give this role the "updatePost" permission
// as well as the permissions of the "author" role
$admin = $auth->createRole('admin');
$auth->add($admin);
$auth->addChild($admin, $updatePost);
$auth->addChild($admin, $author);
// Assign roles to users. 1 and 2 are IDs returned by IdentityInterface::getId()
// usually implemented in your User model.
$auth->assign($author, 2);
$auth->assign($admin, 1);
}
public function down()
{
$auth = Yii::$app->authManager;
$auth->removeAll();
}
}
如果您不想硬编码哪些用户具有某些角色,请不要在迁移中放置
->assign()
调用。相反,请创建 UI 或控制台命令来管理分配。
可以通过使用 yii migrate
来应用迁移。
如果您的权限层次结构根本不更改,并且您有固定数量的用户,则可以创建一个 -控制台命令,该命令将通过 authManager
提供的 API 一次初始化授权数据
<?php
namespace app\commands;
use Yii;
use yii\console\Controller;
class RbacController extends Controller
{
public function actionInit()
{
$auth = Yii::$app->authManager;
$auth->removeAll();
// add "createPost" permission
$createPost = $auth->createPermission('createPost');
$createPost->description = 'Create a post';
$auth->add($createPost);
// add "updatePost" permission
$updatePost = $auth->createPermission('updatePost');
$updatePost->description = 'Update post';
$auth->add($updatePost);
// add "author" role and give this role the "createPost" permission
$author = $auth->createRole('author');
$auth->add($author);
$auth->addChild($author, $createPost);
// add "admin" role and give this role the "updatePost" permission
// as well as the permissions of the "author" role
$admin = $auth->createRole('admin');
$auth->add($admin);
$auth->addChild($admin, $updatePost);
$auth->addChild($admin, $author);
// Assign roles to users. 1 and 2 are IDs returned by IdentityInterface::getId()
// usually implemented in your User model.
$auth->assign($author, 2);
$auth->assign($admin, 1);
}
}
注意:如果您使用的是高级模板,则需要将
RbacController
放入console/controllers
目录中,并将命名空间更改为console\controllers
。
上面的命令可以通过以下方式从控制台执行
yii rbac/init
如果您不想硬编码哪些用户具有某些角色,请不要将
->assign()
调用放入命令中。相反,请创建 UI 或控制台命令来管理分配。
作者可以创建帖子,管理员可以更新帖子并执行作者可以执行的所有操作。
如果您的应用程序允许用户注册,则需要在注册新用户后将其分配角色。例如,为了让所有注册的用户在您的高级项目模板中成为作者,您需要修改 frontend\models\SignupForm::signup()
如下所示
public function signup()
{
if ($this->validate()) {
$user = new User();
$user->username = $this->username;
$user->email = $this->email;
$user->setPassword($this->password);
$user->generateAuthKey();
$user->save(false);
// the following three lines were added:
$auth = \Yii::$app->authManager;
$authorRole = $auth->getRole('author');
$auth->assign($authorRole, $user->getId());
return $user;
}
return null;
}
对于需要使用动态更新的授权数据的复杂访问控制的应用程序,可能需要使用 authManager
提供的 API 开发特殊的用户界面(即管理面板)。
如前所述,规则为角色和权限添加了额外的约束。规则是扩展自 yii\rbac\Rule 的类。它必须实现 execute() 方法。在我们之前创建的层次结构中,作者无法编辑自己的帖子。让我们修复它。首先,我们需要一个规则来验证用户是否是帖子作者
namespace app\rbac;
use yii\rbac\Rule;
use app\models\Post;
/**
* Checks if authorID matches user passed via params
*/
class AuthorRule extends Rule
{
public $name = 'isAuthor';
/**
* @param string|int $user the user ID.
* @param Item $item the role or permission that this rule is associated with
* @param array $params parameters passed to ManagerInterface::checkAccess().
* @return bool a value indicating whether the rule permits the role or permission it is associated with.
*/
public function execute($user, $item, $params)
{
return isset($params['post']) ? $params['post']->createdBy == $user : false;
}
}
上面的规则检查 post
是否由 $user
创建。我们将在之前使用的命令中创建一个特殊的权限 updateOwnPost
$auth = Yii::$app->authManager;
// add the rule
$rule = new \app\rbac\AuthorRule;
$auth->add($rule);
// add the "updateOwnPost" permission and associate the rule with it.
$updateOwnPost = $auth->createPermission('updateOwnPost');
$updateOwnPost->description = 'Update own post';
$updateOwnPost->ruleName = $rule->name;
$auth->add($updateOwnPost);
// "updateOwnPost" will be used from "updatePost"
$auth->addChild($updateOwnPost, $updatePost);
// allow "author" to update their own posts
$auth->addChild($author, $updateOwnPost);
现在我们有了以下层次结构
准备好授权数据后,访问检查就像调用 yii\rbac\ManagerInterface::checkAccess() 方法一样简单。由于大多数访问检查都是关于当前用户的,为了方便起见,Yii 提供了一个快捷方法 yii\web\User::can(),它可以像以下这样使用
if (\Yii::$app->user->can('createPost')) {
// create post
}
如果当前用户是 Jane,ID=1
,我们从 createPost
开始,并尝试到达 Jane
为了检查用户是否可以更新帖子,我们需要传递一个额外的参数,该参数是之前描述的 AuthorRule
所需的
if (\Yii::$app->user->can('updatePost', ['post' => $post])) {
// update post
}
以下是如果当前用户是 John 的情况
我们从 updatePost
开始,并通过 updateOwnPost
。为了通过访问检查,AuthorRule
应该从其 execute()
方法中返回 true
。该方法从 can()
方法调用中接收其 $params
,因此其值为 ['post' => $post]
。如果一切正常,我们将到达分配给 John 的 author
。
Jane 的情况比较简单,因为她是管理员。
在你的控制器中,有几种方法可以实现授权。如果你想要细粒度的权限来区分添加和删除的访问权限,那么你需要检查每个操作的访问权限。你可以在每个操作方法中使用上面的条件,也可以使用 yii\filters\AccessControl
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::class,
'rules' => [
[
'allow' => true,
'actions' => ['index'],
'roles' => ['managePost'],
],
[
'allow' => true,
'actions' => ['view'],
'roles' => ['viewPost'],
],
[
'allow' => true,
'actions' => ['create'],
'roles' => ['createPost'],
],
[
'allow' => true,
'actions' => ['update'],
'roles' => ['updatePost'],
],
[
'allow' => true,
'actions' => ['delete'],
'roles' => ['deletePost'],
],
],
],
];
}
如果所有 CRUD 操作都一起管理,那么使用一个单独的权限,例如 managePost
,并在 yii\web\Controller::beforeAction() 中检查它是一个好主意。
在上面的例子中,没有为访问操作指定的角色传递参数,但对于 updatePost
权限,我们需要传递一个 post
参数才能正常工作。你可以通过在访问规则上指定 roleParams 来将参数传递给 yii\web\User::can()
[
'allow' => true,
'actions' => ['update'],
'roles' => ['updatePost'],
'roleParams' => function() {
return ['post' => Post::findOne(['id' => Yii::$app->request->get('id')])];
},
],
在上面的例子中,roleParams 是一个闭包,它会在检查访问规则时被评估,所以模型只有在需要时才会被加载。如果创建角色参数是一个简单的操作,你也可以直接指定一个数组,如下所示
[
'allow' => true,
'actions' => ['update'],
'roles' => ['updatePost'],
'roleParams' => ['postId' => Yii::$app->request->get('id')],
],
默认角色是隐式分配给所有用户的角色。不需要调用 yii\rbac\ManagerInterface::assign(),授权数据也不包含其分配信息。
默认角色通常与一个规则相关联,该规则决定角色是否适用于正在检查的用户。
默认角色通常用于已经有一些角色分配的应用程序。例如,一个应用程序可能在其用户表中有一个 "group" 列来表示每个用户属于哪个权限组。如果每个权限组可以映射到一个 RBAC 角色,你可以使用默认角色功能自动将每个用户分配到一个 RBAC 角色。让我们用一个例子来说明如何做到这一点。
假设在用户表中,你有一个 group
列,使用 1 代表管理员组,2 代表作者组。你计划使用两个 RBAC 角色 admin
和 author
来分别代表这两个组的权限。你可以按照如下方式设置 RBAC 数据,首先创建一个类
namespace app\rbac;
use Yii;
use yii\rbac\Rule;
/**
* Checks if user group matches
*/
class UserGroupRule extends Rule
{
public $name = 'userGroup';
public function execute($user, $item, $params)
{
if (!Yii::$app->user->isGuest) {
$group = Yii::$app->user->identity->group;
if ($item->name === 'admin') {
return $group == 1;
} elseif ($item->name === 'author') {
return $group == 1 || $group == 2;
}
}
return false;
}
}
然后,创建你自己的命令/迁移,如 上一节所述
$auth = Yii::$app->authManager;
$rule = new \app\rbac\UserGroupRule;
$auth->add($rule);
$author = $auth->createRole('author');
$author->ruleName = $rule->name;
$auth->add($author);
// ... add permissions as children of $author ...
$admin = $auth->createRole('admin');
$admin->ruleName = $rule->name;
$auth->add($admin);
$auth->addChild($admin, $author);
// ... add permissions as children of $admin ...
请注意,在上面,因为 "author" 被添加为 "admin" 的子级,当你实现规则类的 execute()
方法时,也需要遵守这种层次结构。这就是为什么当角色名称为 "author" 时,execute()
方法将在用户组为 1 或 2(表示用户在 "admin" 组或 "author" 组中)时返回 true
。
接下来,通过在 yii\rbac\BaseManager::$defaultRoles 中列出两个角色来配置 authManager
return [
// ...
'components' => [
'authManager' => [
'class' => 'yii\rbac\PhpManager',
'defaultRoles' => ['admin', 'author'],
],
// ...
],
];
现在,如果你执行访问检查,admin
和 author
两个角色都将通过评估与它们关联的规则来进行检查。如果规则返回 true
,则表示该角色适用于当前用户。根据上面的规则实现,这意味着如果用户的 group
值为 1,则 admin
角色将适用于该用户;如果 group
值为 2,则 author
角色将适用于该用户。
发现错别字或认为此页面需要改进?
在 github 上编辑它 !
注册 或 登录 以评论。