Maison  >  Article  >  développement back-end  >  Explication détaillée de la vérification de l'autorisation de l'API restful de yii2

Explication détaillée de la vérification de l'autorisation de l'API restful de yii2

*文
*文original
2018-01-03 11:42:023024parcourir

Cet article vous présente principalement les informations pertinentes sur la vérification réelle de l'autorisation de l'API restful du projet yii2. L'introduction dans l'article est très détaillée et a une certaine valeur de référence et d'apprentissage pour tous les amis qui en ont besoin peuvent y jeter un œil. ci-dessous. J'espère que cela aide tout le monde.

Préface

Cet article est principalement écrit pour le déploiement d'API dans des scénarios réels.

Aujourd'hui, nous allons parler des problèmes de vérification d'autorisation rencontrés par l'API dans ces années-là !

Analyse commerciale

Commençons par comprendre toute la logique

  • Les utilisateurs remplissent le client Formulaire de connexion

  • L'utilisateur soumet le formulaire et le client demande la connexion à l'interface de connexion

  • Le serveur vérifie le mot de passe du compte de l'utilisateur et renvoie un Jeton valide pour le client

  • Le client obtient le jeton de l'utilisateur et le stocke dans le client tel qu'un cookie

  • Le client porte le jeton pour accéder aux interfaces qui doivent être vérifiées, telles que l'interface permettant d'obtenir les informations personnelles de l'utilisateur

  • Le serveur vérifie la validité du jeton, et la vérification réussit de toute façon, les informations requises par le. le client est renvoyé. Si la vérification échoue, l'utilisateur doit se reconnecter

Dans cet article, nous prenons la connexion de l'utilisateur et obtenons les informations personnelles de l'utilisateur comme exemple pour donner un explication détaillée et complète.
Ce qui précède est au centre de cet article. Ne soyez pas encore excité ou nerveux. Après l'avoir analysé, nous procéderons étape par étape avec les détails.

Préparation

  • Vous devriez avoir une application API

  • Pour le client, nous allons utiliser postman pour la simulation. Si votre navigateur Google n'a pas installé postman, veuillez d'abord le télécharger vous-même

  • La table utilisateur à tester doit avoir un api_token S'il n'y a pas de champ, veuillez d'abord l'ajouter vous-même et assurez-vous que le champ est suffisamment long

  • L'application API a activé l'embellissement du routage et a d'abord configuré l'opération de connexion du type de publication et l'opération get type signup-test

  • Fermer la session du composant utilisateur

Concernant les 4ème et 5ème points du ci-dessus les préparatifs, nous publierons Le code suivant est facile à comprendre

'components' => [
 'user' => [ 
 'identityClass' => 'common\models\User',
 'enableAutoLogin' => true,
 'enableSession' => false,
 ],
 'urlManager' => [
 'enablePrettyUrl' => true,
 'showScriptName' => false,
 'enableStrictParsing' => true,
 'rules' => [
  [
  'class' => 'yii\rest\UrlRule',
  'controller' => ['v1/user'],
  'extraPatterns' => [
   'POST login' => 'login',
   'GET signup-test' => 'signup-test',
  ]
  ],
 ]
 ],
 // ......
],

opération de test d'inscription Nous ajouterons un utilisateur test plus tard pour faciliter l'opération de connexion. D'autres types d'opérations devront être ajoutés ultérieurement.

Sélection de la classe d'authentification

La classe modèle que nous avons définie dans apimodulesv1controllersUserController pointe vers la classe commonmodelsUser, à titre d'illustration Le point clé ici est que nous ne la réécrirons pas séparément. En fonction de vos besoins, si nécessaire, copiez une classe User séparément dans apimodels.

Pour vérifier les autorisations de l'utilisateur, nous prenons yiifiltersauthQueryParamAuth comme exemple

use yii\filters\auth\QueryParamAuth;

public function behaviors() 
{
 return ArrayHelper::merge (parent::behaviors(), [ 
  'authenticator' => [ 
  'class' => QueryParamAuth::className() 
  ] 
 ] );
}

Dans ce cas, toutes les opérations qui accèdent à l'utilisateur ne nécessiteraient-elles pas une authentification ? Cela ne fonctionne pas. D'où vient le jeton lorsque le client accède pour la première fois à l'opération de connexion ? yiifiltersauthQueryParamAuth fournit un attribut externe pour filtrer les actions qui ne nécessitent pas de vérification ? Nous modifions légèrement la méthode de comportement de UserController

public function behaviors() 
{
 return ArrayHelper::merge (parent::behaviors(), [ 
  'authenticator' => [ 
  'class' => QueryParamAuth::className(),
  'optional' => [
   'login',
   'signup-test'
  ],
  ] 
 ] );
}

afin que l'opération de connexion soit accessible sans vérification d'autorisation.

Ajouter un utilisateur test

Afin d'éviter l'échec de la connexion du client, nous écrivons d'abord une méthode simple pour l'ajouter à l'utilisateur tableau Insérez deux éléments de données pour faciliter la vérification ultérieure.

UserController ajoute l'opération signupTest. Notez que cette méthode n'entre pas dans le cadre de l'explication. Nous l'utilisons uniquement pour faciliter les tests.

use common\models\User;
/**
 * 添加测试用户
 */
public function actionSignupTest ()
{
 $user = new User();
 $user->generateAuthKey();
 $user->setPassword('123456');
 $user->username = '111';
 $user->email = '111@111.com';
 $user->save(false);

 return [
 'code' => 0
 ];
}

Comme ci-dessus, nous avons ajouté un utilisateur avec le nom d'utilisateur 111 et le mot de passe 123456

Opération de connexion

Supposons que l'utilisateur entre le nom d'utilisateur et le mot de passe sur le client pour se connecter. L'opération de connexion côté serveur est en fait très simple. La plupart du traitement de la logique métier est activé apimodelsloginForm.

use apimodelsLoginForm;

/**
 * 登录
 */
public function actionLogin ()
{
 $model = new LoginForm;
 $model->setAttributes(Yii::$app->request->post());
 if ($user = $model->login()) {
 if ($user instanceof IdentityInterface) {
  return $user->api_token;
 } else {
  return $user->errors;
 }
 } else {
 return $model->errors;
 }
}

Après une connexion réussie, le jeton de l'utilisateur est renvoyé au client. Jetons un coup d'œil à l'implémentation de la logique de connexion spécifique

<.>Créez un nouveau apimodelsLoginForm.PHP

<?php
namespace api\models;

use Yii;
use yii\base\Model;
use common\models\User;

/**
 * Login form
 */
class LoginForm extends Model
{
 public $username;
 public $password;

 private $_user;

 const GET_API_TOKEN = &#39;generate_api_token&#39;;

 public function init ()
 {
 parent::init();
 $this->on(self::GET_API_TOKEN, [$this, &#39;onGenerateApiToken&#39;]);
 }


 /**
 * @inheritdoc
 * 对客户端表单数据进行验证的rule
 */
 public function rules()
 {
 return [
  [[&#39;username&#39;, &#39;password&#39;], &#39;required&#39;],
  [&#39;password&#39;, &#39;validatePassword&#39;],
 ];
 }

 /**
 * 自定义的密码认证方法
 */
 public function validatePassword($attribute, $params)
 {
 if (!$this->hasErrors()) {
  $this->_user = $this->getUser();
  if (!$this->_user || !$this->_user->validatePassword($this->password)) {
  $this->addError($attribute, &#39;用户名或密码错误.&#39;);
  }
 }
 }
 /**
 * @inheritdoc
 */
 public function attributeLabels()
 {
 return [
  &#39;username&#39; => &#39;用户名&#39;,
  &#39;password&#39; => &#39;密码&#39;,
 ];
 }
 /**
 * Logs in a user using the provided username and password.
 *
 * @return boolean whether the user is logged in successfully
 */
 public function login()
 {
 if ($this->validate()) {
  $this->trigger(self::GET_API_TOKEN);
  return $this->_user;
 } else {
  return null;
 }
 }

 /**
 * 根据用户名获取用户的认证信息
 *
 * @return User|null
 */
 protected function getUser()
 {
 if ($this->_user === null) {
  $this->_user = User::findByUsername($this->username);
 }

 return $this->_user;
 }

 /**
 * 登录校验成功后,为用户生成新的token
 * 如果token失效,则重新生成token
 */
 public function onGenerateApiToken ()
 {
 if (!User::apiTokenIsValid($this->_user->api_token)) {
  $this->_user->generateApiToken();
  $this->_user->save(false);
 }
 }
}
Retournons et jetons un coup d'œil à ce qui s'est passé lorsque nous avons appelé l'opération de connexion de LoginForm dans l'opération de connexion de UserController


1. Appelez la méthode de connexion de LoginForm

2. Appelez la méthode validate, puis vérifiez les règles

3. Appelez la méthode validatePassword dans la vérification des règles pour vérifier le nom d'utilisateur et le mot de passe

4. Méthode ValidatePassword Pendant le processus, la méthode getUser de LoginForm est appelée et l'utilisateur est obtenu via findByUsername de la classe

si l'utilisateur est introuvable ou si le validatePassword de commonmodelsUser échoue. vérifiez le mot de passe, une erreur commonmodelsUser sera renvoyée.

      5、触发LoginForm::GENERATE_API_TOKEN事件,调用LoginForm的onGenerateApiToken方法,通过common\models\User的apiTokenIsValid校验token的有效性,如果无效,则调用User的generateApiToken方法重新生成

注意:common\models\User类必须是用户的认证类,如果不知道如何创建完善该类,请围观这里 用户管理之user组件的配置

下面补充本节增加的common\models\User的相关方法

/**
 * 生成 api_token
 */
public function generateApiToken()
{
 $this->api_token = Yii::$app->security->generateRandomString() . &#39;_&#39; . time();
}

/**
 * 校验api_token是否有效
 */
public static function apiTokenIsValid($token)
{
 if (empty($token)) {
 return false;
 }

 $timestamp = (int) substr($token, strrpos($token, &#39;_&#39;) + 1);
 $expire = Yii::$app->params[&#39;user.apiTokenExpire&#39;];
 return $timestamp + $expire >= time();
}

继续补充apiTokenIsValid方法中涉及到的token有效期,在api\config\params.php文件内增加即可

<?php
return [
 // ...
 // token 有效期默认1天
 &#39;user.apiTokenExpire&#39; => 1*24*3600,
];

到这里呢,客户端登录 服务端返回token给客户端就完成了。

按照文中一开始的分析,客户端应该把获取到的token存到本地,比如cookie中。以后再需要token校验的接口访问中,从本地读取比如从cookie中读取并访问接口即可。

根据token请求用户的认证操作

假设我们已经把获取到的token保存起来了,我们再以访问用户信息的接口为例。

yii\filters\auth\QueryParamAuth类认定的token参数是 access-token,我们可以在行为中修改下

public function behaviors() 
{
 return ArrayHelper::merge (parent::behaviors(), [ 
   &#39;authenticator&#39; => [ 
    &#39;class&#39; => QueryParamAuth::className(),
    &#39;tokenParam&#39; => &#39;token&#39;,
    &#39;optional&#39; => [
     &#39;login&#39;,
     &#39;signup-test&#39;
    ],
   ] 
 ] );
}

这里将默认的access-token修改为token。

我们在配置文件的urlManager组件中增加对userProfile操作

&#39;extraPatterns&#39; => [
 &#39;POST login&#39; => &#39;login&#39;,
 &#39;GET signup-test&#39; => &#39;signup-test&#39;,
 &#39;GET user-profile&#39; => &#39;user-profile&#39;,
]

我们用postman模拟请求访问下 /v1/users/user-profile?token=apeuT9dAgH072qbfrtihfzL6qDe_l4qz_1479626145发现,抛出了一个异常

\"findIdentityByAccessToken\" is not implemented.<br>

这是怎么回事呢?

我们找到 yii\filters\auth\QueryParamAuth 的authenticate方法,发现这里调用了 common\models\User类的loginByAccessToken方法,有同学疑惑了,common\models\User类没实现loginByAccessToken方法为啥说findIdentityByAccessToken方法没实现?如果你还记得common\models\User类实现了yii\web\user类的接口的话,你应该会打开yii\web\User类找答案。没错,loginByAccessToken方法在yii\web\User中实现了,该类中调用了common\models\User的findIdentityByAccessToken,但是我们看到,该方法中通过throw抛出了异常,也就是说这个方法要我们自己手动实现!

这好办了,我们就来实现下common\models\User类的findIdentityByAccessToken方法吧

public static function findIdentityByAccessToken($token, $type = null)
{
 // 如果token无效的话,
 if(!static::apiTokenIsValid($token)) {
  throw new \yii\web\UnauthorizedHttpException("token is invalid.");
 }

 return static::findOne([&#39;api_token&#39; => $token, &#39;status&#39; => self::STATUS_ACTIVE]);
 // throw new NotSupportedException(&#39;"findIdentityByAccessToken" is not implemented.&#39;);
}

验证完token的有效性,下面就要开始实现主要的业务逻辑部分了。

/**
 * 获取用户信息
 */
public function actionUserProfile ($token)
{
 // 到这一步,token都认为是有效的了
 // 下面只需要实现业务逻辑即可,下面仅仅作为案例,比如你可能需要关联其他表获取用户信息等等
 $user = User::findIdentityByAccessToken($token);
 return [
  &#39;id&#39; => $user->id,
  &#39;username&#39; => $user->username,
  &#39;email&#39; => $user->email,
 ];
}

服务端返回的数据类型定义

在postman中我们可以以何种数据类型输出的接口的数据,但是,有些人发现,当我们把postman模拟请求的地址copy到浏览器地址栏,返回的又却是xml格式了,而且我们明明在UserProfile操作中返回的是属组,怎么回事呢?

这其实是官方捣的鬼啦,我们一层层源码追下去,发现在yii\rest\Controller类中,有一个 contentNegotiator行为,该行为指定了允许返回的数据格式formats是json和xml,返回的最终的数据格式根据请求头中Accept包含的首先出现在formats中的为准,你可以在yii\filters\ContentNegotiatornegotiateContentType方法中找到答案。

你可以在浏览器的请求头中看到

Accept:

text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

即application/xml首先出现在formats中,所以返回的数据格式是xml类型,如果客户端获取到的数据格式想按照json进行解析,只需要设置请求头的Accept的值等于application/json即可

有同学可能要说,这样太麻烦了,啥年代了,谁还用xml,我就想服务端输出json格式的数据,怎么做?

办法就是用来解决问题滴,来看看怎么做。api\config\main.php文件中增加对response的配置

&#39;response&#39; => [
 &#39;class&#39; => &#39;yii\web\Response&#39;,
 &#39;on beforeSend&#39; => function ($event) {
  $response = $event->sender;
  $response->format = yii\web\Response::FORMAT_JSON;
 },
],

如此,不管你客户端传什么,服务端最终输出的都会是json格式的数据了。

自定义错误处理机制

再来看另外一个比较常见的问题:

你看我们上面几个方法哈,返回的结果是各式各样的,这样就给客户端解析增加了困扰,而且一旦有异常抛出,返回的代码还都是一堆一堆的,头疼,怎么办?

说到这个问题之前呢,我们先说一下yii中先关的异常处理类,当然,有很多哈。比如下面常见的一些,其他的自己去挖掘

yii\web\BadRequestHttpException
yii\web\ForbiddenHttpException
yii\web\NotFoundHttpException
yii\web\ServerErrorHttpException
yii\web\UnauthorizedHttpException
yii\web\TooManyRequestsHttpException

实际开发中各位要善于去利用这些类去捕获异常,抛出异常。说远了哈,我们回到重点,来说如何自定义接口异常响应或者叫自定义统一的数据格式,比如向下面这种配置,统一响应客户端的格式标准。

&#39;response&#39; => [
 &#39;class&#39; => &#39;yii\web\Response&#39;,
 &#39;on beforeSend&#39; => function ($event) {
  $response = $event->sender;
  $response->data = [
   &#39;code&#39; => $response->getStatusCode(),
   &#39;data&#39; => $response->data,
   &#39;message&#39; => $response->statusText
  ];
  $response->format = yii\web\Response::FORMAT_JSON;
 },
],

说道了那么多,本文就要结束了,刚开始接触的同学可能有一些蒙,不要蒙,慢慢消化,先知道这么个意思,了解下restful api接口在整个过程中是怎么用token授权的就好。这样真正实际用到的时候,你也能举一反三!

相关推荐:

Yii2整合迅搜实现高效中文分词检索

Yii如何过滤不良代码

Yii2实现rbac权限控制

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn