Dieser Artikel stellt Ihnen hauptsächlich die relevanten Informationen zur tatsächlichen Restful-API-Autorisierungsüberprüfung des yii2-Projekts vor. Die Einführung im Artikel ist sehr detailliert und bietet einen gewissen Referenz- und Lernwert für alle Freunde, die sie benötigen unten. Ich hoffe, es hilft allen.
Vorwort
Dieser Artikel ist hauptsächlich für die Bereitstellung von APIs in tatsächlichen Szenarien geschrieben.
Heute werden wir über die Probleme bei der Autorisierungsüberprüfung sprechen, auf die die API in diesen Jahren gestoßen ist!
Geschäftsanalyse
Lassen Sie uns zunächst die gesamte Logik verstehen
Benutzer füllen den Client aus Anmeldeformular
Der Benutzer sendet das Formular und der Client fordert die Anmeldung an der Anmeldeschnittstelle an
Der Server überprüft das Kontokennwort des Benutzers und gibt a zurück gültiges Token für den Client
Der Client erhält das Token des Benutzers und speichert es im Client, beispielsweise als Cookie
Der Client trägt das Token für Zugriff auf Schnittstellen, die überprüft werden müssen, z. B. die Schnittstelle zum Abrufen persönlicher Benutzerinformationen
Der Server überprüft die Gültigkeit des Tokens und die Überprüfung erfolgt. Wie dem auch sei Der Client wird zurückgegeben. Wenn die Überprüfung fehlschlägt, muss sich der Benutzer erneut anmelden.
In diesem Artikel nehmen wir die Benutzeranmeldung und erhalten die persönlichen Daten des Benutzers als Beispiel ausführliche und vollständige Erklärung.
Das Obige steht im Mittelpunkt dieses Artikels. Seien Sie noch nicht aufgeregt oder nervös. Nach der Analyse gehen wir Schritt für Schritt mit den Details fort.
Vorbereitung
Sie sollten eine API-Anwendung haben
Für den Client verwenden wir Postman zur Simulation. Wenn Ihr Google-Browser Postman nicht installiert hat, laden Sie es bitte zuerst selbst herunter
Die zu testende Benutzertabelle muss vorhanden sein ein api_token Wenn kein Feld vorhanden ist, fügen Sie es bitte zuerst selbst hinzu und stellen Sie sicher, dass das Feld lang genug ist
Die API-Anwendung hat die Routing-Verschönerung aktiviert und zuerst den Anmeldevorgang für den Beitragstyp konfiguriert und die Get-Type-Signup-Test-Operation
Schließen Sie die Sitzung der Benutzerkomponente
In Bezug auf den 4. und 5. Punkt des Die oben genannten Vorbereitungen werden wir veröffentlichen Der folgende Code ist leicht zu verstehen
'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', ] ], ] ], // ...... ],
Anmeldetestvorgang Wir werden später einen Testbenutzer hinzufügen, um den Anmeldevorgang zu erleichtern. Andere Arten von Operationen müssen später hinzugefügt werden.
Auswahl der Authentifizierungsklasse
Die Modellklasse, die wir in apimodulesv1controllersUserController
festgelegt haben, verweist zur Veranschaulichung auf die Klasse commonmodelsUser
Der entscheidende Punkt hierbei ist, dass wir es je nach Bedarf nicht separat umschreiben. Kopieren Sie ggf. eine Benutzerklasse separat nach apimodels
.
Um Benutzerberechtigungen zu überprüfen, nehmen wir yiifiltersauthQueryParamAuth
als Beispiel:
use yii\filters\auth\QueryParamAuth; public function behaviors() { return ArrayHelper::merge (parent::behaviors(), [ 'authenticator' => [ 'class' => QueryParamAuth::className() ] ] ); }
Würden in diesem Fall nicht alle Vorgänge, die auf den Benutzer zugreifen, eine Authentifizierung erfordern? Das funktioniert nicht. Woher kommt das Token, wenn der Client zum ersten Mal auf den Anmeldevorgang zugreift? yiifiltersauthQueryParamAuth
stellt ein externes Attribut zum Filtern von Aktionen bereit, die keiner Überprüfung bedürfen. Wir ändern die Verhaltensmethode von UserController
public function behaviors() { return ArrayHelper::merge (parent::behaviors(), [ 'authenticator' => [ 'class' => QueryParamAuth::className(), 'optional' => [ 'login', 'signup-test' ], ] ] ); }
geringfügig, sodass auf den Anmeldevorgang ohne Berechtigungsüberprüfung zugegriffen werden kann.
Testbenutzer hinzufügen
Um Client-Anmeldefehler zu vermeiden, schreiben wir zunächst eine einfache Methode, um ihn dem Benutzer hinzuzufügen Tabelle Fügen Sie zwei Daten ein, um die spätere Überprüfung zu erleichtern.
UserController fügt den SignupTest-Vorgang hinzu. Beachten Sie, dass diese Methode nicht im Rahmen der Erklärung liegt. Wir verwenden sie nur, um das Testen zu erleichtern.
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 ]; }
Wie oben haben wir einen Benutzer mit Benutzername 111 und Passwort 123456 hinzugefügt
Anmeldevorgang
Angenommen, der Benutzer gibt den Benutzernamen und das Passwort auf dem Client ein, um sich anzumelden. Der serverseitige Anmeldevorgang ist eigentlich sehr einfach. Schauen wir uns zunächst die Implementierung der Anmeldung an 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; } }
<?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 = 'generate_api_token'; public function init () { parent::init(); $this->on(self::GET_API_TOKEN, [$this, 'onGenerateApiToken']); } /** * @inheritdoc * 对客户端表单数据进行验证的rule */ public function rules() { return [ [['username', 'password'], 'required'], ['password', 'validatePassword'], ]; } /** * 自定义的密码认证方法 */ public function validatePassword($attribute, $params) { if (!$this->hasErrors()) { $this->_user = $this->getUser(); if (!$this->_user || !$this->_user->validatePassword($this->password)) { $this->addError($attribute, '用户名或密码错误.'); } } } /** * @inheritdoc */ public function attributeLabels() { return [ 'username' => '用户名', 'password' => '密码', ]; } /** * 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); } } }
abgerufen, wenn der Benutzer nicht gefunden werden kann, oder über das validatePassword von commonmodelsUser
Wenn das Passwort nicht überprüft werden kann, wird ein Fehler commonmodelsUser
zurückgegeben.
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() . '_' . time(); } /** * 校验api_token是否有效 */ public static function apiTokenIsValid($token) { if (empty($token)) { return false; } $timestamp = (int) substr($token, strrpos($token, '_') + 1); $expire = Yii::$app->params['user.apiTokenExpire']; return $timestamp + $expire >= time(); }
继续补充apiTokenIsValid方法中涉及到的token有效期,在api\config\params.php
文件内增加即可
<?php return [ // ... // token 有效期默认1天 'user.apiTokenExpire' => 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(), [ 'authenticator' => [ 'class' => QueryParamAuth::className(), 'tokenParam' => 'token', 'optional' => [ 'login', 'signup-test' ], ] ] ); }
这里将默认的access-token修改为token。
我们在配置文件的urlManager组件中增加对userProfile操作
'extraPatterns' => [ 'POST login' => 'login', 'GET signup-test' => 'signup-test', 'GET user-profile' => 'user-profile', ]
我们用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(['api_token' => $token, 'status' => self::STATUS_ACTIVE]); // throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.'); }
验证完token的有效性,下面就要开始实现主要的业务逻辑部分了。
/** * 获取用户信息 */ public function actionUserProfile ($token) { // 到这一步,token都认为是有效的了 // 下面只需要实现业务逻辑即可,下面仅仅作为案例,比如你可能需要关联其他表获取用户信息等等 $user = User::findIdentityByAccessToken($token); return [ 'id' => $user->id, 'username' => $user->username, 'email' => $user->email, ]; }
服务端返回的数据类型定义
在postman中我们可以以何种数据类型输出的接口的数据,但是,有些人发现,当我们把postman模拟请求的地址copy到浏览器地址栏,返回的又却是xml格式了,而且我们明明在UserProfile操作中返回的是属组,怎么回事呢?
这其实是官方捣的鬼啦,我们一层层源码追下去,发现在yii\rest\Controller
类中,有一个 contentNegotiator行为,该行为指定了允许返回的数据格式formats是json和xml,返回的最终的数据格式根据请求头中Accept包含的首先出现在formats中的为准,你可以在yii\filters\ContentNegotiator
的negotiateContentType
方法中找到答案。
你可以在浏览器的请求头中看到
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的配置
'response' => [ 'class' => 'yii\web\Response', 'on beforeSend' => 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
实际开发中各位要善于去利用这些类去捕获异常,抛出异常。说远了哈,我们回到重点,来说如何自定义接口异常响应或者叫自定义统一的数据格式,比如向下面这种配置,统一响应客户端的格式标准。
'response' => [ 'class' => 'yii\web\Response', 'on beforeSend' => function ($event) { $response = $event->sender; $response->data = [ 'code' => $response->getStatusCode(), 'data' => $response->data, 'message' => $response->statusText ]; $response->format = yii\web\Response::FORMAT_JSON; }, ],
说道了那么多,本文就要结束了,刚开始接触的同学可能有一些蒙,不要蒙,慢慢消化,先知道这么个意思,了解下restful api接口在整个过程中是怎么用token授权的就好。这样真正实际用到的时候,你也能举一反三!
相关推荐:
Das obige ist der detaillierte Inhalt vonAusführliche Erläuterung der Restful-API-Autorisierungsüberprüfung von yii2. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!