首页
Yii2 认证与授权

https://www.yiiframework.com/doc/guide/2.0/zh-cn/security-authentication

Http 认证

常见的 Http 认证如下图所示,在 yii2 中都有对应的实现。

http://image.maplejoyous.cn/post/2022/05/02/202205020853011.png

1. 配置认证类

backend/config/main.php

'user' => [
    'identityClass' => 'common\modules\account\domain\repository\UserPo' ,
    'enableAutoLogin' => TRUE ,
    'identityCookie' => ['name' => '_identity-backend' , 'httpOnly' => TRUE] ,
] ,

UserPo 就作为认证类了,它必须要实现IdentityInterface接口。我们可以在它里面实现一些常用的方法。

http://image.maplejoyous.cn/post/2022/05/02/2022050208573434.png

2. 使用用户组件

// 当前用户的身份实例。未认证用户则为 Null 。
$identity = Yii::$app->user->identity;

// 当前用户的ID。 未认证用户则为 Null 。
$id = Yii::$app->user->id;

// 判断当前用户是否是游客(未认证的)
$isGuest = Yii::$app->user->isGuest;

可以使用下面的代码进行登录:

// 使用指定用户名获取用户身份实例。
// 请注意,如果需要的话您可能要检验密码
$identity = User::findOne(['username' => $username]);

// 登录用户
Yii::$app->user->login($identity);

// 注销用户
Yii::$app->user->logout();

3. Http 认证方式

Session 认证

http 请求的认证是通过过滤器来实现的,yii2 提供了很多 http 认证相关的过滤器。默认的认证方式是基于 session 认证的,不需要任何配置,但是需要配置 RBAC 授权才能实现请求控制,如下所示:

public function behaviors(): array {
	return [
		'access' => [
			'class' => AccessControl::class ,
			'rules' => [
				['actions' => ['signup' , 'login' , 'test-email'] , 'allow' => TRUE] ,
				['actions' => ['logout' , 'index' , 'test'] , 'allow' => TRUE , 'roles' => ['@']]
			] ,
		] ,
	];
}

roles:指定该规则用于匹配哪些用户角色。 系统自带两个特殊的角色,通过 yii\web\User::$isGuest 来判断:

  • ?: 用于匹配访客用户 (未经认证)

  • @: 用于匹配已认证用户

在使用 REST 风格的时候,我们可以配置behaviors实现认证行为,比如下面认证失败抛出一个异常,注意要与父类的行为配置合并,而不是重写

public function behaviors(): array {
	return array_merge(parent::behaviors() , [
		'access' => [
			'class' => AccessControl::class ,
			'denyCallback' => function ($rule , $action) {
				throw new UnauthorizedHttpException("未登录");
			} ,
			'rules' => [
				['actions' => [] , 'allow' => TRUE , 'roles' => ['@']]
			] ,
		] ,
	]);
}

Http Basic Auth

yii2 也提供了其他了一些认证方式。
https://www.yiiframework.com/doc/api/2.0/yii-filters-auth-httpbasicauth

public function behaviors()
{
    return [
        'basicAuth' => [
            'class' => \yii\filters\auth\HttpBasicAuth::class,
            'auth' => function ($username, $password) {
                $user = User::find()->where(['username' => $username])->one();
                if ($user && $user->validatePassword($password)) {
                    return $user;
                }
                return null;
            },
        ],
    ];
}

请求时带上$_SERVER['PHP_AUTH_USER']$_SERVER['PHP_AUTH_PW']就可以了,postman 里是这样的。

http://image.maplejoyous.cn/post/2022/05/02/2022050209164444.png

组合认证

可以自己实现,比如实现 Session 与 BasicAuth 的组合。如果用户没有传递 session,就使用 basicAuth,可以很方便比如 UnitTest 的场景。

public function behaviors(): array {
	// 同时使用 cookie 和 httpBasic 认证
	$behaviors = parent::behaviors();
    $isGuest = Yii::$app->user->isGuest;
	if ($isGuest) {
		$behaviors['basicAuth'] = [
            'class' => HttpBasicAuth::class ,
            'auth' => function ($username , $password) {
            $user = Admin::find()->where(['username' => $username])->one();
                if ($user && $user->validatePassword($password)) {
                    return $user;
                }
                return NULL;
            } ,
        ];
	}
	return array_merge($behaviors , [
		'access' => [
			'class' => AccessControl::class ,
			'denyCallback' => function ($rule , $action) {
				throw new UnauthorizedHttpException("未登录");
			} ,
			'rules' => [
				['actions' => [] , 'allow' => TRUE , 'roles' => ['@']]
			] ,
		] ,
	]);
}

官方也提供了自己的组合认证,可以尝试一下:

https://www.yiiframework.com/doc/api/2.0/yii-filters-auth-compositeauth

jwt Token 认证

https://www.yiiframework.com/wiki/2568/jwt-authentication-tutorial

  1. jwt Token 是社区的一个实现,需要安装:
composer require sizeg/yii2-jwt
  1. 安装完后,需要配置一下:
'jwt' => [
    'class' => Jwt::class ,
    'key' => 'talentkey-vms-Y3suZh73ZSBTIptc6bced8eYx1Pf5Wzg' ,
    'jwtValidationData' => JwtValidationData::class ,
] ,
  1. 然后我们自己实现一下生成 token 和 刷新 token 的方法。

刷新 token 的标准做法是通过一个 refresh token 来进行刷新,需要把这个 refresh token 保存到 db,简单的一点做法就是根据 token 重新生成一个,但是这样安全性就没有标准做法的那么高了。

/**
* 生成 jwt token
* @param array $tokenData 需要额外追加的数据
* @return Token
* @example $token = (string)$user->generateJwt()
*/
public function generateJwt(array $tokenData = []): Token {
	$jwt = Yii::$app->jwt;
	$signer = $jwt->getSigner('HS256');
	$key = $jwt->getKey();
	$time = time();

	$jwt = $jwt->getBuilder()
		->issuedAt($time)           //发行时间
        ->expiresAt($time + 3600)   //过期时间
        // 把一些数据加密到 token 中
		->withClaim('uid' , $this->user_id)
        ->withClaim('username' , $this->name)
		->withClaim('email' , $this->email);

	if ( ! empty($tokenData)) {
        $jwt->withClaim('role_id' , $tokenData['role_id'])
            ->withClaim('role_id' , $tokenData['role_id'])
            ->withClaim('role_name' , $tokenData['role_name'])
            ->withClaim('role_name' , $tokenData['role_name'])
            ->withClaim('groups' , $tokenData['groups'])
            ->withClaim('privileges' , $tokenData['privileges'])
            ->withClaim('ent_id' , $tokenData['ent']['ent_id'])
            ->withClaim('ent_name' , $tokenData['ent']['name']);
	}

	return $jwt->getToken($signer , $key);
}

/**
* 刷新 jwt token,直接生成而不是使用 refresh_token
* @param string $token
* @return Token|null
* @example  $token = (string)$user->generateRefreshToken()
*/
public function generateRefreshToken(string $token): ?Token {
	$jwt = Yii::$app->jwt;
	$token = $jwt->loadToken($token);
    $exp = $token->getClaim('exp');
	//如果过期时间小于10分钟,重新获取失败
	if (($exp - time()) > (60 * 10)) {
		return NULL;
	}
	return $this->generateJwt();
}
  1. 认证控制
public function behaviors(): array {
    $behaviors = parent::behaviors();
    $behaviors['authenticator'] = [
        'class' => \sizeg\jwt\JwtHttpBearerAuth::class ,
        'except' => ['login' ,'refresh-token' ,'options']
    ];
    return $behaviors;
}