基础使用
首先在应用的配置文件中进行如下配置
'components' => [
'user' => [
//用于获取验证状态的系统类
'class' => 'yii\web\User',
//用于获取用户信息的自定义类,继承了model类,并且实现了接口 yii\web\IdentityInterface
'identityClass' => 'common\models\UserModel',
//设置cookie名称
'identityCookie' => ['name' => 'cookiename'],
//设置cookie有效时长
'authTimeout' => 72000,
//登录后的跳转地址
'loginUrl' => 'login',
//登录地址。用于access授权过滤下,游客访问需登录的方法是,自动跳转至该地址
'returnUrl' => '/WLN100/index/index',
],
在控制器中使用认证类获取登录的用户信息
class BaseController extends \common\component\Controller{
public $user_id;
public function beforeAction($action){
.....
//判断当前页面不是登录页面,且未登录时,调整到登录页面
if($action->getUniqueId() != 'user/login'){
Yii::$app->getResponse()->redirect('user/login.html')
return false;
}
}
public function actionLogin(){
$this->user_id = Yii::$app->user->id();
//未登录时,返回的id为NULL
if(!$user_id){
//使用账号密码获取用户信息
$identityClass = UserModel::findOne(['user_name'=>'zhangsan','password'=>'123321']);
//使用yii\web\User组件获取授权状态
Yii::$app->user->login(identityClass );
//调用login后,会把用户的信息写入到组件中,同时也会设置cookie等信息
//之后的请求,可以直接从组件中获取用户的相关信息
$this->user_id = Yii::$app->user->id();
}else(
Yii::$app->getResponse()->redirect('index/index.html');
)
}
}
以上为基础使用,适用于可以使用cookie的场景。
接口使用
当服务为app提供接口时,无法使用cookie,需要使用token,因为无法使用cookie,所以不需要配置相关信息
'components' => [
'user' => [
//用于获取验证状态的系统类
'class' => 'yii\web\User',
//用于获取用户信息的自定义类,继承了model类,并且实现了接口 yii\web\IdentityInterface
'identityClass' => 'common\models\UserModel'
],
在基础使用中,我们调用认证组件user获取用户信息后,需要根据用户信息自行验证用户的登录状态
在接口使用中,因为不能使用cookie,需要通过传入的token获取用户信息,而且验证用户登录状态的方式也发生了改变。框架提供了QueryParamAuth行为,来为我们自动验证当前用户的登录状态
class ApiController extends ActiveController{
public function behaviors() {
return array_merge(parent::behaviors(), [
//权限验证
'authenticator' => yii\filters\auth\QueryParamAuth::class
]);
}
}
行为authenticator会对所有的请求都进行认证,获取用户信息,当不通过时会返回401。但我们需要部分请求可以不经过验证,如登录、首页等公共页面。我们可以在QueryParamAuth中重写父类的isOptional方法,如下
class QueryParamAuth extends AuthMethod{
.....................
protected function isOptional($action){
$url = $action->getUniqueId();// 模块/控制前/方法
$no_need_url = ['user/index/login','user/index/index'];
if(in_array($url,$no_need_url)){
return true;
}else{
return false;
}
}
}
这样,当我们通过login方法输入账号密码成功后,后台会返回token。前台拿到token后记录到本地,在之后的请求中带上token,接口就可以通过token自动验证用户的登录状态了
获取user_id的注意事项
在控制器中,我们配置了认证行为,并传入相关认证参数后,在访问具体的action方法时,已经是登陆状态。这时我们可能需要使用用户id执行相关操作,特别是当应用是后端应用,基本每个action中都会用到用户id,此时我们根据以往经验,希望把用户id作为一个基础属性,在控制器初始化时就获取用户id,在action中就可以直接使用,如下
class ApiController extends ActiveController{
public $uesr_id;
public function init(){
parent::init();
$this->user_id = Yii::$app->user->getId();
}
public function behaviors() {
return array_merge(parent::behaviors(), [
//权限验证
'authenticator' => yii\filters\auth\QueryParamAuth::class
]);
}
public function actionList(){
$user_id = Yii::$app->user->getId();
var_dump($user_id);//打印在控制器中获取的用户id
var_dump($this->user_id);//打印初始化时获取的用户id
}
}
以上代码执行actionList方法后,我们会发现初始化时获取的用户id为NULL,没有获取到,但控制器中可以正常获取用户id。这是因为在控制器初始化时,控制器的行为尚未执行。
我们借此可以梳理下Yii2框架的代码执行顺序,点击查看yii框架的执行逻辑
所以如果我们想要在初始化时获取用户认证信息,可以把自动认证放在bootstrap组件中,而不是在控制器中使用认证行为。
oauth2 使用
当我们想实现单点登录,多个平台通过一个服务获取登录信息时,需要对yii2的认证进行一些改造
首先修改配置文件
$config = [
'id' => 'app',
'basePath' => dirname(dirname(__DIR__)),
'defaultRoute' => 'v1/index/index', //默认路由
//模块化开发
'modules' => ['v1'=>'app/modules/v1/Module'],
//配置oauth2登录为启动组件
'bootstrap' => [
'auth2' => [
'class' => 'common\auth2',
]
]
]
OAuth2 .php
class OAuth2 implements BootstrapInterface{
//cookie名称
const AUTHORIZATION_COOKIE_NAME = 'authorization';
public function bootstrap($app)
{
parent::bootstrap($app);
//设置用户组件用户对象
Yii::$app->set('user',[
'class' => yii\web\User::class,
'identityClass' => 'common\auth2\user',
/**
auth2公用登录页面url和相关参数
callbackUrl 用于oauth2登录成功后,请求本网站地址,处理验证信息
redirectUrl 当前请求页面地址,用于等callbackUrl 处理成功后,跳转到原地址
**/
'loginUrl' =>$oauth_url.'&redirectUrl='.urlencode(Yii::$app->request->getAbsoluteUrl()).'&callbackUrl='.urlencode(Url::to(['/auth2/callback'],true)),
]);
//清除缓存数据
Yii::$app->user->setIdentity(null);
//退出登录时调用用户管理服务器接口删除对应的token使此用户所有登录状态失效
Yii::$app->user->on(User::EVENT_BEFORE_LOGOUT,function(){
Yii::$app->response->getCookies()->remove(self::AUTHORIZATION_COOKIE_NAME);
//删除autho2端用户登录数据
return true;
});
//从cookie中获取auth2回调后,存储的token信息,token是auth2返回的经过jwt加密的字符串
$authorization = Yii::$app->request->cookies->get(self::AUTHORIZATION_COOKIE_NAME);
//存储token不为空,且能解析出正确信息时,把解析的信息放入用户验证组件中
if(!empty($authorization) && $token = JWTHelper::validateToken($authorization->value)){
$userIdentityData = $token->getClaim('identity');
/* @var $userIdentity IdentityInterface|Model*/
//创建一个common\auth2\user对象 ,此处的user类,是根据oauth2返回的用户信息构建的一个model类,继承IdentityInterface,无对应的table
$userIdentity = Yii::createObject(Yii::$app->user->identityClass);
//设置userIdentity 类的属性
$userIdentity->setAttributes((array)$userIdentityData,false);
//把userIdentity放入user组件,类似Yii::$app->user->login($user);
Yii::$app->user->setIdentity($userIdentity);
$authorization->value = (string)$token;
$authorization->expire = $token->getClaim('exp');
//设置用户cookie信息,这样就下次请求就可以通过cooke获取用户认证状态
Yii::$app->response->cookies->add($authorization);
return;
}
}
}
用户首次请求时,通过user组件获取用户信息,若未登录,跳转到oauth2公共登录地址,如下
class BaseController extends Controller {
public function beforeAction($action) {
if(!parent::beforeAction($action)){
return parent::beforeAction($action);
}
$id = Yii::$app->user->id;
if(!$id){
Yii::$app->getResponse()->redirect(Yii::$app->user->loginUrl);
}
}
}
输入账号密码登录后,公共登录平台会请求callback地址,同时携带token等信息,具体代码如下
class CallBackController extends Controller {
$request = Yii::$app->request;
//获取登录成功后要跳转的地址,在前面的loginUrl中携带,此时传回来并接收
$redirectUrl = $request->get('redirectUrl');
//验证oauth2平台返回的token是否合法,具体代码根据自己使用的平台自行处理
$token = JWTHelper::validateToken($request->get('authorization'));
if(empty($token)){
throw new BadRequestHttpException('无效的authorization');
}
//验证成功,把token相关信息存入cookie中
Yii::$app->response->getCookies()->add(new Cookie([
'name' => AccountBootstrap::AUTHORIZATION_COOKIE_NAME,
'httpOnly' => true,
'value' => $request->get('authorization'),
'expire' => $token->getClaim('exp') ,
]));
//跳转到登录成功后的页面
if(empty($redirectUrl)){
$redirectUrl = '/';
}
return $this->redirect($redirectUrl);
}
需要确保callback不需要登录
callback跳转前,token信息已经存入了cookie,跳转到成功页时,框架先执行了启动组件oauth2。组件会解析cookie中的信息,并更新user组件的信息。此时BaseController 中的验证用户登录通过,不会跳转到登录页面,可以执行之后的代码
以上方法适用于cookie可用的场景中,在app接口相关应用中,流程和代码需要进行一些改造