Yii2 访问控制之AccessControl 详解

技巧库 · wy1272086709 · 于 1年前 发布 · 2982 次阅读

先看yii2中的yii\base\Controller 类里面的beforeAction方法代码.

 	public function beforeAction($action)
    {
        $event = new ActionEvent($action);
        $this->trigger(self::EVENT_BEFORE_ACTION, $event);
        return $event->isValid;
    }

可以看出,beforeAction方法,触发了EVENT_BEFORE_ACTION事件,然后,返回了ActionEvent事件的isValid属性值(这个属性默认为true)

在控制器中,一般都会调用 if(parent::beforeAction($action)) 来调用yii\base\Controller 里面的beforeAction ,一般都会先执行上面的这段,触发EVNET_BEFORE_ACTION事件。在控制器中的beforeAction返回true后,再执行真正要访问的Action路由页面。

这样做的原因,可以通过yii\base\Controller 里面的runAction代码看出。

        if ($runAction && $this->beforeAction($action)) {
            // run the action
            $result = $action->runWithParams($params);

            $result = $this->afterAction($action, $result);

            // call afterAction on modules
            foreach ($modules as $module) {
                /* @var $module Module */
                $result = $module->afterAction($action, $result);
            }
        }

这里判断beforeAction($action) 是否返回true,返回TRUE的情况,才会执行真正要执行的action.

下面来看一看AccessControl 类和这个beforeAction的关系。 yii\filters\AccessControl类继承自yii\filters\ActionFilter,ActionFilter 类继承自yii\base\Behavior类。ActionFilter 类重写了attach和detach 两个方法,使这个行为附加到一个component 的时候,必定会注册和删除Controller::EVENT_BEFORE_ACTION事件。同时,可以看到,

在yii\filters\AccessFilter类里面有如下代码:

  	public function attach($owner)
    {
        $this->owner = $owner;
        $owner->on(Controller::EVENT_BEFORE_ACTION, [$this, 'beforeFilter']);
    }

    /**
     * @inheritdoc
     */
    public function detach()
    {
        if ($this->owner) {
            $this->owner->off(Controller::EVENT_BEFORE_ACTION, [$this, 'beforeFilter']);
            $this->owner->off(Controller::EVENT_AFTER_ACTION, [$this, 'afterFilter']);
            $this->owner = null;
        }
    }
	
	public function beforeFilter($event)
    {
		//这里判断对应的actionID 是否符合$except 和$only 属性变量设定的规则。
        if (!$this->isActive($event->action)) {
            return;
        }

        $event->isValid = $this->beforeAction($event->action);
        if ($event->isValid) {
            // call afterFilter only if beforeFilter succeeds
            // beforeFilter and afterFilter should be properly nested
            $this->owner->on(Controller::EVENT_AFTER_ACTION, [$this, 'afterFilter'], null, false);
        } else {
            $event->handled = true;
        }
    }

在yii\filter\AccessControl 类里面,有四个属性,分别是user,rules,only,except,denyCallback.user 表示具体涉及到的用户控制组件ID,在控制登录的过程中,这个组件发挥着很重要的作用,only表示这些规则,适用于哪些actionId,except表示这些规则,不适用哪些的actionId.rules 表示访问规则的数组,可以有多组访问规则,只要其中有一个规则完全符合要求,就会跳出。denyCallback,用于指定,当不允许的访问的规则匹配到,同时对应的规则,没有设置denyCallback 属性时的回调策略。如果不允许访问的规则和AccessControl类都没有设置denyCallback,则会调用user组件,跳转到user组件对应的loginUrl对应的地址。

 	public function beforeAction($action)
    {
        $user = $this->user;
        $request = Yii::$app->getRequest();
        /* @var $rule AccessRule */
        foreach ($this->rules as $rule) {
            if ($allow = $rule->allows($action, $user, $request)) {
                return true;
            } elseif ($allow === false) {
                if (isset($rule->denyCallback)) {
                    call_user_func($rule->denyCallback, $rule, $action);
                } elseif ($this->denyCallback !== null) {
                    call_user_func($this->denyCallback, $rule, $action);
                } else {
                    $this->denyAccess($user);
                }
                return false;
            }
        }
        if ($this->denyCallback !== null) {
            call_user_func($this->denyCallback, null, $action);
        } else {
            $this->denyAccess($user);
        }
        return false;
    }
	protected function denyAccess($user)
    {
        if ($user->getIsGuest()) {
            $user->loginRequired();
        } else {
            throw new ForbiddenHttpException(Yii::t('yii', 'You are not allowed to perform this action.'));
        }
    }
	 public function loginRequired($checkAjax = true, $checkAcceptHeader = true)
    {
        $request = Yii::$app->getRequest();
        $canRedirect = !$checkAcceptHeader || $this->checkRedirectAcceptable();
        if ($this->enableSession
            && $request->getIsGet()
            && (!$checkAjax || !$request->getIsAjax())
            && $canRedirect
        ) {
            $this->setReturnUrl($request->getUrl());
        }
        if ($this->loginUrl !== null && $canRedirect) {
            $loginUrl = (array) $this->loginUrl;
            if ($loginUrl[0] !== Yii::$app->requestedRoute) {
                return Yii::$app->getResponse()->redirect($this->loginUrl);
            }
        }
        throw new ForbiddenHttpException(Yii::t('yii', 'Login Required'));
    }

在yii\filters\AccessRule 类里面,有如下代码可以好好看看.

 	protected function matchRole($user)
    {
        if (empty($this->roles)) {
            return true;
        }
        foreach ($this->roles as $role) {
            if ($role === '?') {
                if ($user->getIsGuest()) {
                    return true;
                }
            } elseif ($role === '@') {
                if (!$user->getIsGuest()) {
                    return true;
                }
            } elseif ($user->can($role)) {
                return true;
            }
        }

        return false;
    }

	protected function matchIP($ip)
    {
        if (empty($this->ips)) {
            return true;
        }
        foreach ($this->ips as $rule) {
            if ($rule === '*' || $rule === $ip || (($pos = strpos($rule, '*')) !== false && !strncmp($ip, $rule, $pos))) {
                return true;
            }
        }

        return false;
    }

    protected function matchCustom($action)
    {
        return empty($this->matchCallback) || call_user_func($this->matchCallback, $this, $action);
    }

由以上代码大致可以推断:

  • 在对应的rules 数组里面的元素,对应的roles 除了使用?(未认证用户),@(已认证用户)外,还可以是具体的权限名称)。
  • 在rules数组里面的元素,对应的ips 属性,对应的访问限制中,可以判断是否在某一个网段(如192.168..),或者具体某一个IP。
  • 在对应的rules 数组里面的元素,可以设置matchCallback属性,来自定义对应的过滤规则。
共收到 0 条回复
没有找到数据。
添加回复 (需要登录)
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册