該文章轉載自我的部落格:我的部落格
在現在的情境下,前後端分離是越來越普遍了,而laravel本身就對API有良好的支援,所以我在這裡記錄一下我使用laravel的官方擴充套件包passport
去做API開發。
本文的laravel版本是laravel 6.*
,當然你也可以使用laravel的其他版本。
如果你使用 laravel 5.4
或者 更低版本
,你需要在 config/app.php
檔案中為Passport註冊服務。就這樣,在這個檔案中的providers陣列中新增註冊服務。
本文只是作為一個參考,告訴你如何使用passport。實際開發按照你自己的來。
通過Composer去安裝
composer require laravel/passport
php artisan migrate
注意:
這裡的遷移命令會把所有的遷移檔案都執行,所以如果你只想遷移passport
的檔案,在vendor/laravel/passport/database/migrations
目錄裡面,是與passport
有關的遷移檔案。
執行下面命令:
php artisan migrate --path=./vendor/laravel/passport/database/migrations/
如果遷移的時候報這種型別的錯
Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes
原因
Laravel 5.4對預設資料庫字符集進行了更改,現在utf8mb4它包含了對儲存表情符號的支援。這隻會影響新的應用程式,只要您執行MySQL v5.7.7及更高版本,就不需要做任何事情。
解決方法
第一種方法:把MySQL升級為MySQL8版本,如果是生產環境,就建議你升級到
MySQL8
第二種方法:就是在
app/Providers/AppServiceProvider.php
檔案中,修改boot方法:
public function boot()
{
Schema::defaultStringLength(191);//新增這行程式碼
}
此命令會建立祕鑰以用來生成安全的Access Token
。除此之外,它也會建立用來生成Access Token
的 personal access【私有授權】
和 password grant【密碼授權】
:
php artisan passport:install
我建議你直接使用上面的命令去生成
在 config/auth.php
配置檔案中,你應該設定 api
許可權認證守衛的 driver
選項為 passport
。當需要許可權認證的 API
請求進來時會告訴你的應用去使用 Passport's
的 TokenGuard
。
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
將 Laravel\Passport\HasApiTokens
trait 新增到你的 App\User
模型中。這個 trait 會為模型新增一系列助手函式用來驗證使用者的祕鑰和作用域:
<?php
namespace App\Models;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use Notifiable, HasApiTokens;
}
如果你的不是App\User
這個表,在config/auth.php
配置檔案中修改:
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class, //這裡修改成你的模型
],
],
接下來,你應該在 AuthServiceProvider
中的 boot 方法中呼叫 Passport::routes
方法。這個方法會註冊必要的路由去頒發訪問令牌,撤銷訪問令牌,客戶端和個人令牌:
<?php
namespace App\Providers;
use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
}
由於passport生成的令牌預設是長期有效的,所以我們把過期時間設定為24小時:
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\Passport;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::tokensExpireIn(Carbon::now()->addDays(1));//令牌過期時間
Passport::refreshTokensExpireIn(Carbon::now()->addDays(1));//重新整理令牌過期時間
Passport::personalAccessTokensExpireIn(Carbon::now()->addDays(1));//個人訪問令牌過期時間
}
}
然後Passport
的配置告一段落。接下來就是測試是否有用。
建立模型
php artisan make:model Models/User
模型程式碼:
<?php
namespace App\Models;
use App\Helper\Utils;
use Illuminate\Database\Eloquent\Model;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
//注意,這裡繼承的是Authenticatable而不是Model,否則會出現findForPassport,validateForPassportPasswordGrant不存在的報錯
class User extends Authenticatable
{
use HasApiTokens,Notifiable;//Notifiable這個可有可無
protected $table="users";
public function findForPassport($account)
{
//token驗證預設是email,我改成了account
return $this->where('account', $account)->first();
}
public function validateForPassportPasswordGrant($password)
{
//這裡使用的是自定義的密碼驗證
$md5_password=Utils::GetMd5Password($password,$this->salt);
return Utils::CheckHashPassword($md5_password,$this->password);
}
}
建立路由
在route/api.php
檔案下新增:
Route::prefix('v1')->namespace('Api')->group(function (){
Route::post("/register","UserController@register");
Route::post("/login","UserController@Login");
//這裡使用passport中介軟體驗證token
Route::middleware('auth:api')->group(function (){
Route::post("/user_info","UserController@UserInfo");
Route::post("/logout","UserController@Logout");
});
});
建立控制器
php artisan make:controller Api/UserController
控制器程式碼
<?php
namespace App\Http\Controllers\Api;
use App\Helper\Utils;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Validator;
class UserController extends Controller
{
//註冊
public function register(Request $request){
$post_data=$request->post();
$rule=[
"name"=>"required",
"account"=>"required",
"password"=>"required",
"phone"=>"required",
];
$message=[
"required"=>":attribute 不能為空"
];
$attr=[
"name"=>"使用者名稱稱",
"account"=>"賬號",
"password"=>"密碼",
"phone"=>"手機號",
];
$validator = Validator::make($post_data,$rule,$message,$attr);
if($validator->fails())
{
return Utils::JsonData(400,$validator->errors()->first());
}
//驗證使用者是否已存在
$is_user=User::query()->where("account",trim($post_data["account"]))->count();
if($is_user > 0){
return Utils::JsonData(400,"使用者已存在");
}
$salt=Utils::GetRandomString();
$password=Utils::GetHashPassword($post_data["password"],$salt);
$data_info=[
"name"=>trim($post_data["name"]),
"account"=>trim($post_data["account"]),
"password"=>$password,
"salt"=>$salt,
"phone"=>$post_data["phone"],
"create_time"=>date("Y-m-d H:s:i",time()),
"update_time"=>date("Y-m-d H:s:i",time()),
];
try{
User::query()->insert($data_info);
return Utils::JsonData(200,"註冊成功");
}catch (\Exception $e){
return Utils::JsonData(400,"註冊失敗");
}
}
//登入
public function Login(Request $request){
$post_data=$request->post();
//驗證使用者是否存在
$user=User::query()->where("account",$post_data["account"])->first();
if(!isset($user->id)){
return Utils::JsonData(400,"使用者不存在");
}
//驗證密碼
$md5_password=Utils::GetMd5Password($post_data["password"],$user->salt);
if(!Utils::CheckHashPassword($md5_password,$user->password)){
return Utils::JsonData(400,"密碼錯誤");
}
//生成token
$tokenResult = $user->createToken('LaravelApi');
$token = $tokenResult->token;
if ($post_data["remember_me"]) {
$token->expires_at = Carbon::now()->addWeeks(1);//設定令牌過期時間為一週
}
$token->save();//儲存令牌
$data_info=[
'access_token' => $tokenResult->accessToken,
'token_type' => 'Bearer',
'expires_at' => Carbon::parse(
$tokenResult->token->expires_at
)->toDateTimeString()
];
return Utils::JsonData(200,"請求成功",$data_info);
}
//退出
public function Logout(Request $request){
$request->user()->token()->revoke();
return Utils::JsonData(200,"退出成功");
}
//獲取使用者資訊
public function UserInfo(Request $request){
$user=$request->user();
return Utils::JsonData(200,"請求成功",$user);
}
}
建立輔助函式類 Utils
在app/
建立個Helper/Utils.php
,程式碼如下:
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2020/5/25
* Time: 10:24
*/
namespace App\Helper;
use Illuminate\Support\Facades\Hash;
class Utils
{
/**
* 請求返回資料封裝
* @param int $code 狀態碼 200:成功,400:錯誤,300:未授權
* @param string $msg
* @param null $data
*/
public static function JsonData($code=200,$msg="",$data=null,$is_return=true){
$return_data=array(
"code"=>$code,
"msg"=>$msg,
"data"=>$data
);
if($is_return){
return json_encode($return_data);
}else{
echo json_encode($return_data);
}
}
/**
* 生成雜湊密碼
* @param $password //密碼
* @param $salt //鹽值
*/
public static function GetHashPassword($password,$salt){
//加密規則,先生成MD5的加密密碼,然後再做雜湊處理
$md5_password=self::GetMd5Password($password,$salt);
//該方法還有一個引數,可以調整工作因子等配置,當然你也可以在config/hashing.php配置檔案中更改,參考官方文件
$hash_password=Hash::make($md5_password);
return $hash_password;
}
/**
* 生成MD5的加密密碼
* @param $password //密碼
* @param $salt
*/
public static function GetMd5Password($password,$salt){
$md5_password=md5(md5($password.$salt).$salt);
return $md5_password;
}
/**
* 驗證密碼是否正確
* @param $md5_password
* @param $hash_password
*/
public static function CheckHashPassword($md5_password,$hash_password){
if(Hash::check($md5_password,$hash_password)){
return true;
}else{
return false;
}
}
/**
* 檢查hash是否需要更新
* @param $hash_pass
*/
public static function InspectHashOverdue($hash_password){
if(Hash::needsRehash($hash_password)){
return true;
}else{
return false;
}
}
/**
* 生成隨機的字串
* @param int $len
* @param bool $special
*/
public static function GetRandomString($len=4,$special=false){
if($special){
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|";
}else{
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
}
$str="";
for ($i=0;$i < $len;$i++){
// 這裡提供兩種字元獲取方式
// 第一種是使用 substr 擷取$chars中的任意一位字元;
// 第二種是取字元陣列 $chars 的任意元素
// $str .= substr($chars, mt_rand(0, strlen($chars) – 1), 1);
$str .= $chars[ mt_rand(0, strlen($chars) - 1) ];
}
return $str;
}
}
使用postman測試介面
注意:請求的時候一定要把header
頭的Accept
設定為application/json
註冊:
登入:
獲取使用者資訊
上一步已經把這些方法完成,如果你有把上面的介面執行一遍,你就會發現,我token如果沒有驗證通過,會丟擲一個異常,而不是我們想要的json格式,所以我們要捕獲到異常,然後返回我們需要的json格式。
在app/Helper/
下建立異常捕獲類ExceptionReport.php
程式碼如下:
<?php
namespace App\Helper;
use Exception;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\QueryException;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
/**
* 自定義異常捕獲
* Class ExceptionReport
* @package App\Api\Helpers
*/
class ExceptionReport
{
/**
* @var Exception
*/
public $exception;
/**
* @var Request
*/
public $request;
/**
* @var
*/
protected $report;
/**
* ExceptionReport constructor.
* @param Request $request
* @param Exception $exception
*/
function __construct(Request $request, Exception $exception)
{
$this->request = $request;
$this->exception = $exception;
}
/**
* @var array
*/
public $doReport = [
AuthenticationException::class => ['未授權',401],
ModelNotFoundException::class => ['該模型未找到',404],
AuthorizationException::class => ['沒有此許可權',403],
ValidationException::class => [],
UnauthorizedHttpException::class=>['未登入或登入狀態失效',422],
NotFoundHttpException::class=>['沒有找到該頁面',404],
MethodNotAllowedHttpException::class=>['訪問方式不正確',405],
QueryException::class=>['引數錯誤',401],
];
/**
* @return bool
*/
public function shouldReturn(){
foreach (array_keys($this->doReport) as $report){
if ($this->exception instanceof $report){
$this->report = $report;
return true;
}
}
return false;
}
/**
* @param Exception $e
* @return static
*/
public static function make(Exception $e){
return new static(\request(),$e);
}
/**
* @return mixed
*/
public function report(){
//這個是表單驗證的異常
if ($this->exception instanceof ValidationException){
$error = current($this->exception->errors());
return Utils::JsonData(400,current($error));
}
$message = $this->doReport[$this->report];
return Utils::JsonData(400,$message[0]);
}
}
然後在app/Exceptions/Handler.php
中的render
方法攔截異常
public function render($request, Exception $exception)
{
// 將方法攔截到自己的ExceptionReport
$reporter = ExceptionReport::make($exception);
if ($reporter->shouldReturn()){
return $reporter->report();
}
return parent::render($request, $exception);
}
然後驗證不通過就不會丟擲一個異常了,而是返回json格式的錯誤。
本作品採用《CC 協議》,轉載必須註明作者和本文連結