Detailed description of restful api authorization verification in yii2 project practice, yii2restful
Preface
What is a restful api? We have written a large article to introduce its concepts and basic operations.
Now that I have written it, what do I want to say today?
This article focuses on api deployment in actual scenarios.
Today, we are talking about the authorization verification problems encountered by api in those years! If I have benefited from my work exclusively, do not forget to give me a thumbs up.
Business Analysis
Let's take a look at the entire logic.
- Enter the logon form on the client.
- The user submits a form and the client requests the login interface login.
- The server verifies the user's account password and returns a valid token to the client.
- The client obtains the user's token and stores it in the client, such as cookie.
- The client carries a token to access the interface to be verified, such as the interface for Obtaining user personal information
- The server verifies the token validity and passes the verification. The information required by the client is returned anyway. The verification fails and the user needs to log on again.
This document describes how to use user logon to obtain personal information of a user.
The above is the focus of this article. Don't be excited or nervous. After analyzing the details, let's continue step by step.
Preparations
- You should have an api application. If you do not have one, please first move here → _ → Restful api Basics
- For the client, we are going to use postman for simulation. If your google browser has not installed postman, please download it first
- The User table to be tested must have an api_token field. If you do not have one, add it first and ensure that the field is long enough.
- The api application enables route beautification, and first configures the post-type login operation and the get-type signup-test operation.
- The session of the user component is disabled.
For the 4th and 5th points of the above preparations, we can paste the Code to facilitate understanding.
'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', ] ], ] ], // ......],
The signup-test operation is followed by a test user to facilitate the login operation. You need to add other types of operations later.
Authentication class selection
Inapi\modules\v1\controllers\UserController
The model class specified incommon\models\User
Class, in order to illustrate the important point here we will not separately rewrite it, depending on your needs, if necessary, then copy a User classapi\models
.
Verify User Permissionsyii\filters\auth\QueryParamAuth
Example
use yii\filters\auth\QueryParamAuth;public function behaviors() { return ArrayHelper::merge (parent::behaviors(), [ 'authenticator' => [ 'class' => QueryParamAuth::className() ] ] );}
In this way, wouldn't all access user operations require authentication? That doesn't work. When the client first accesses the login operation,yii\filters\auth\QueryParamAuth
An external attribute is provided to filter actions that do not require verification. We will slightly modify the behaviors method of UserController.
public function behaviors() { return ArrayHelper::merge (parent::behaviors(), [ 'authenticator' => [ 'class' => QueryParamAuth::className(), 'optional' => [ 'login', 'signup-test' ], ] ] );}
In this way, the login operation can be accessed without permission verification.
Add test user
To avoid client logon failure, we first write a simple method to insert two pieces of data into the user table for subsequent verification.
Added the signupTest operation for UserController. Note that this method is not within the scope of the description. It is only used for testing convenience.
Use common \ models \ User;/*** add test user */public function actionSignupTest () {$ User = new user (); $ User-> generateAuthKey (); $ user-> setPassword ('000000'); $ user-> username = '000000'; $ user-> email = '2017 @ 111.com '; $ user-> save (false); return ['code' => 0];}
As shown above, we have added a user whose username is 111 and whose password is 123456.
Logon operation
Assume that the user enters the user name and password on the client to log on, the login operation on the server is actually very simple, and most of the business logic processing isapi\models\loginForm
First, let's look at the implementation of login.
Use api \ models \ LoginForm;
/*** Log on to */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 ;}}
After successful login, the client returns the user's token here. Then let's take a look at the implementation of the specific login logic.
Create api \ models \ LoginForm. PHP
<? Phpnamespace 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 * indicates the rule/public function rules () {return [[['Username', 'Password'], 'requestred'], ['Password', 'validatepassword'],];} /*** custom password authentication method */public function validatePassword ($ attribute, $ params) {if (! $ This-> hasErrors () {$ this-> _ user = $ this-> getUser (); if (! $ This-> _ user |! $ This-> _ user-> validatePassword ($ this-> password) {$ this-> addError ($ attribute, 'user name or password error. ') ;}}/ *** @ inheritdoc */public function attributeLabels () {return ['username' => 'username ', 'Password' => '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-> tri Gger (self: GET_API_TOKEN); return $ this-> _ user;} else {return null ;}} /*** obtain User authentication information based on user name ** @ return User | null */protected function getUser () {if ($ this-> _ user = null) {$ this-> _ user = User: findByUsername ($ this-> username);} return $ this-> _ user;}/*** after successful login verification, generate a new token * for the user. if the token is invalid, generate the token */public function onGenerateApiToken () {if (! User: apiTokenIsValid ($ this-> _ user-> api_token) {$ this-> _ user-> generateApiToken (); $ this->_user-> save (false );}}}
Let's look back at what happened after we call the login operation of LoginForm In the login operation of UserController.
1. Call the login method of LoginForm
2. Call the validate method and then verify the rules.
3. In rules verification, the validatePassword method is called to verify the user name and password.
4. The getUser method of LoginForm is called during the validatePassword method verification.common\models\User
Class findByUsername to get the user. the user cannot be found orcommon\models\User
ValidatePassword of Returns error if password verification fails.
5. Trigger the LoginForm: GENERATE_API_TOKEN event and call the onGenerateApiToken method of LoginForm.common\models\User
ApiTokenIsValid of to verify the validity of the token. If the token is invalid, the generateApiToken method of the User is called to regenerate the token.
Note:common\models\User
The class must be the user's authentication class. If you do not know how to create and improve the class, please view the configuration of the user component managed by the user here.
Added in this sectioncommon\models\User
Related Methods
/*** Generate api_token */public function generateApiToken () {$ this-> api_token = Yii ::$ app-> security-> generateRandomString (). '_'. time () ;}/ *** verify whether api_token is valid */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 ();}
Continue to supplement the token validity period involved in the apiTokenIsValid method.api\config\params.php
Add files
<? Phpreturn [//... // token validity period: 1 day by default. 'user. apiTokenExpire '=> 1*24*3600,];
Here, the client logs on to the server and returns the token to the client.
According to the analysis at the beginning of this article, the client should store the obtained tokens locally, such as cookies. Later, you can access the interface that requires token verification to read from the local computer, for example, from the cookie and access the interface.
Request User Authentication Based on the token
Let's assume that we have saved the obtained tokens. Let's take the interface for accessing user information as an example.
yii\filters\auth\QueryParamAuth
The token parameter recognized by the class is access-token, which can be modified in the row
public function behaviors() { return ArrayHelper::merge (parent::behaviors(), [ 'authenticator' => [ 'class' => QueryParamAuth::className(), 'tokenParam' => 'token', 'optional' => [ 'login', 'signup-test' ], ] ] );}
Change the default access-token to token.
We added the userProfile operation to the urlManager component of the configuration file.
'extraPatterns' => [ 'POST login' => 'login', 'GET signup-test' => 'signup-test', 'GET user-profile' => 'user-profile',]
We use postman to simulate request access./V1/users/user-profile? Token = apeuT9dAgH072qbfrtihfzL6qDe_l4qz_1479626145 found that an exception was thrown.
\"findIdentityByAccessToken\" is not implemented.
What's going on?
We foundAuthenticate of yii \ filters \ auth \ QueryParamAuth
Method.common\models\User
Some people are confused about the loginByAccessToken method of the class,common\models\User
Class does not implement the loginByAccessToken method. Why does the findIdentityByAccessToken method not? If you still remembercommon\models\User
Classyii\web\user
Class interface, you should openyii\web\User
Class to find the answer. The loginByAccessToken method isyii\web\User
This class is calledcommon\models\User
The findIdentityByAccessToken, but we can see that the throw throws an exception in this method, that is, we need to manually implement this method!
This is easy to implement.common\models\User
ClassfindIdentityByAccessToken
Method
Public static function findIdentityByAccessToken ($ token, $ type = null) {// if the token is invalid, 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. ');}
After verifying the validity of the token, we will begin to implement the main business logic section.
/*** Get user information */public function actionUserProfile ($ token) {// at this step, token is regarded as valid. // you only need to implement the business logic below, the following is a case study. For example, you may need to associate other tables to obtain user information. $ User = user: findidentitybyaccesen en ($ token ); return ['id' => $ user-> id, 'username' => $ user-> username, 'email '=> $ user-> email,];}
Definitions of Data Types returned by the server
In postman, We can output the data of the Interface Based on the Data Type. However, some people find that when we copy the address of the postman simulated request to the browser address bar, the returned result is in xml format, and the returned result is a group in the UserProfile operation. What is going on?
This is actually an official ghost. We can trace the source code layer by layer and find thatyii\rest\Controller
Class, there is a contentNegotiator behavior, this behavior specifies that the data formats allowed to return formats are json and xml, the final returned data format is subject to the information contained in the Accept in the Request Header first in formats. You canyii\filters\ContentNegotiator
OfnegotiateContentType
Method.
You can see in the browser's Request Header
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
That is, application/xml first appears in formats, so the returned data format is xml. If the data format obtained by the client is parsed in json format, you only need to set the Accept value of the request header to be equal to application/json.
Some people may say that this is too troublesome. In what age, who still uses xml? I want the server to output data in json format. How can this problem be solved?
The solution is to solve the problem and see how to do it. Added the response configuration in the api \ config \ main. php file.
'response' => [ 'class' => 'yii\web\Response', 'on beforeSend' => function ($event) { $response = $event->sender; $response->format = yii\web\Response::FORMAT_JSON; },],
In this way, no matter what your client uploads, the server will eventually output data in json format.
Custom error handling mechanism
Let's take a look at another common problem:
You can see the above several methods. The returned results are various, which adds a lot of trouble to the client's parsing. In addition, once an exception is thrown, The Returned Code is a bunch of code, what should I do?
Before talking about this problem, let's first talk about the exception handling class mentioned in yii. Of course, there are many. For example, some of the following are common examples.
yii\web\BadRequestHttpExceptionyii\web\ForbiddenHttpExceptionyii\web\NotFoundHttpExceptionyii\web\ServerErrorHttpExceptionyii\web\UnauthorizedHttpExceptionyii\web\TooManyRequestsHttpException
In actual development, you should be good at using these classes to capture and throw exceptions. To put it far away, let's go back to the point and talk about how to customize the interface exception response or customize a unified data format. For example, to the following configuration, We Will uniformly respond to the client format standards.
'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; },],
After talking so much about it, this article is about to end. The first contact may be a bit blind, don't be blind, and digest it slowly. The first prophet said this, let's take a look at how restful api uses token authorization throughout the process. In this way, you can also put it together!
Summary
The above is all the content of this article. I hope the content of this article will help you in your study or work. If you have any questions, please leave a message, thank you for your support.