https://www.yiiframework.com/doc/guide/2.0/zh-cn/security-authentication
Http 认证
常见的 Http 认证如下图所示,在 yii2 中都有对应的实现。
1. 配置认证类
backend/config/main.php
'user' => [
'identityClass' => 'common\modules\account\domain\repository\UserPo' ,
'enableAutoLogin' => TRUE ,
'identityCookie' => ['name' => '_identity-backend' , 'httpOnly' => TRUE] ,
] ,
UserPo 就作为认证类了,它必须要实现IdentityInterface
接口。我们可以在它里面实现一些常用的方法。
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 里是这样的。
组合认证
可以自己实现,比如实现 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
- jwt Token 是社区的一个实现,需要安装:
composer require sizeg/yii2-jwt
- 安装完后,需要配置一下:
'jwt' => [
'class' => Jwt::class ,
'key' => 'talentkey-vms-Y3suZh73ZSBTIptc6bced8eYx1Pf5Wzg' ,
'jwtValidationData' => JwtValidationData::class ,
] ,
- 然后我们自己实现一下生成 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();
}
- 认证控制
public function behaviors(): array {
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => \sizeg\jwt\JwtHttpBearerAuth::class ,
'except' => ['login' ,'refresh-token' ,'options']
];
return $behaviors;
}