laravel+dingo+jwt搭建api系统
一: 安装 laravel ( https://learnku.com/docs/laravel/8.x/installation/9354 )
//可能需要先安装一下国内的 Composer 加速镜像:
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
//安装 laravel 项目(最新版本):
composer create-project --prefer-dist laravel/laravel la8(项目名称)
二: 安装 Dingo ( https://learnku.com/docs/dingo-api/2.0.0/Installation/1443 )
composer require dingo/api
三: 安装 JWT
composer require tymon/jwt-auth
生成 JWT_SECRET 写入.env(自动写入)
php artisan jwt:secret
四: 配置文件 config/app.php
//在 providers 数组中添加以下两个服务提供者:
"providers"=>[
...
Dingo\Api\Provider\LaravelServiceProvider::class,
Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
],
//在 aliases 数组中给 JWT 以下两个类添加别名方便之后生成 token 时使用,(当然也可以使用 Auth 门面生成 token , 所以不添加也是可以的。)
'aliases' => [
...
'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,
]
五: 生成 Dingo 和 JWT 的配置文件
php artisan vendor:publish --provider="Dingo\Api\Provider\LaravelServiceProvider" //生成 Dingo 的 api.php 文件
【 实际上是 vendor/dingo/api/config/api.php 这个配置文件 】
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider" //生成 JWT 的 jwt.php 文件
【 实际上是 vendor/tymon/jwt-auth/config/config.php 这个配置文件 】
六:修改 api.php 和 jwt.php 配置文件
//在 api.php 的 auth 数组中添加 api 权限验证类
'auth' => [
'jwt' => Dingo\Api\Auth\Provider\JWT::class, // api 权限验证类
],
//把 jwt.php providers 数组中的 token 生成类(Lcobucci)修改为 Namshi 如下:
'providers' => [
//'jwt' => Tymon\JWTAuth\Providers\JWT\Lcobucci::class, // 使用 attempt() 方法生成 token (本人不推荐使用这方法)
'jwt' => Tymon\JWTAuth\Providers\JWT\Namshi::class, //使用 formUser() 方法生成 token
.
.
.
七:修改 config/auth.php 配置文件
//把 defaults 数组中的默认守卫(网关)改为 api 如下:
'defaults' => [
'guard' => 'api',//'web',
'passwords' => 'users',
],
//把 guards 数组中的 api 守卫的驱动改为 jwt 如下:
'guards' => [
...
'api' => [
'driver' => 'jwt', //'token',
'provider' => 'users',
'hash' => false,
],
],
八: 在 .env 中写入 Dingo 配置 ( 具体配置见: https://learnku.com/docs/dingo-api/2.0.0/Configuration/1444 )
#以下是我的配置:
API_STANDARDS_TREE=vnd
API_SUBTYPE=laravel
API_PREFIX=api
#子域名 (前缀和子域名只能存在一个)可选
#API_DOMAIN=api.myapp.com
API_VERSION=v1
API_NAME=api-demo
#API_CONDITIONAL_REQUEST=false
#API_STRICT=false
API_DEFAULT_FORMAT=json
API_DEBUG=true
九: 在 .env 中配置数据库
#以下是我的配置:
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=la8
DB_USERNAME=root
DB_PASSWORD=123
十: 执行数据迁移
php artisan migrate
主要是生成 users 数据表
十一: 修改 Models/User.php 如下:
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject; //
class User extends Authenticatable implements JWTSubject
{
use HasFactory, Notifiable;
public $table = 'users';
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'phone', //注意我在数据表 users 中添加了 phone 字段
'email',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
//实现 JWTSubject 以下两个接口函数
public function getJWTIdentifier(){`在这里插入代码片`
return $this->getKey();
}
public function getJWTCustomClaims(){
return [];
}
}
十二: 注册 BaseController.php 控制器目的是调整一下请求响应的数据格式。(目录 :/app/Http/Controllers/Api/V1/BaseController.php)
<?php
namespace App\Http\Controllers\Api\V1;
use Dingo\Api\Routing\Helpers;
use App\Http\Controllers\Controller;
use Dingo\Api\Exception\ValidationHttpException;
class BaseController extends Controller{
// 接口帮助调用
use Helpers;
// 工具函数
// 返回错误的请求
protected function errorBadRequest($validator)
{
// github like error messages
// if you don't like this you can use code bellow
//
//throw new ValidationHttpException($validator->errors());
$result = [];
$messages = $validator->errors()->toArray();
if ($messages) {
foreach ($messages as $field => $errors) {
foreach ($errors as $error) {
$result[] = [
'field' => $field,
'code' => $error,
];
}
}
}
//throw new ValidationHttpException($result);
$this->responseValidationError($result);
}
// 请求成功时对数据进行格式处理
public function responseSuccess($data) {
return response()->json([
'code' => '200',
'data' => $data
]);
}
// 响应失败时返回自定义错误信息
public function responseError($msg) {
return response()->json([
'code' => '400',
'msg' => $msg
]);
}
// 响应校验失败时返回自定义的信息(基本用不上)
public function responseValidationError($msgs) {
return response()->json([
'code' => '401',
'msgs' => $msgs
]);
}
// 能用错误提示方法
public function onError($msgs){
return response()->json([
'code' => 'error',
'msgs' => $msgs
]);
}
}
十三: 在 Models 中创建 Authorization.php 目的是为了获取格式化的 token 相关信息
<?php
namespace App\Models;
use Carbon\Carbon;
class Authorization
{
protected $token;
protected $payload;
public function __construct($token = null)
{
$this->token = $token;
}
public function setToken($token)
{
$this->token = $token;
return $this;
}
public function getToken()
{
if (! $this->token) {
throw new \Exception('请设置token');
}
return $this->token;
}
public function getPayload()
{
if (! $this->payload) {
$this->payload = \Auth::setToken($this->getToken())->getPayload();
}
return $this->payload;
}
public function getExpiredAt()
{
return Carbon::createFromTimestamp($this->getPayload()->get('exp'))
->toDateTimeString();
}
public function getRefreshExpiredAt()
{
return Carbon::createFromTimestamp($this->getPayload()->get('iat'))
->addMinutes(config('jwt.refresh_ttl'))
->toDateTimeString();
}
public function user()
{
return \Auth::authenticate($this->getToken());
}
public function toArray()
{
return [
'token' => $this->getToken(),
'token_type' => 'Bearer',
'expired_at' => $this->getExpiredAt(),
// 'refresh_expired_at' => $this->getRefreshExpiredAt(),
];
}
}
十四: 注册 AuthController.php 实现注册、登录、刷新token 和登出功能(目录: app/Http/Controllers/Api/V1/Auth/AuthController.php)
<?php
namespace App\Http\Controllers\Api\V1\Auth;
use App\Http\Controllers\Api\V1\BaseController;
use Illuminate\Http\Request;
use App\Models\Authorization;
use App\Transformers\AuthorizationTransformer;
use Illuminate\Support\Facades\Hash;
use Dingo\Api\Exception\StoreResourceFailedException;
use App\Models\User;
use Auth;
class AuthController extends BaseController
{
// 注册接口
public function register(Request $request) {
$isPhone = preg_match("/^1[3456789]\d{9}$/", $request->phone);
$validator = $this->validator($request->all());
if($isPhone != 1){
return $this->responseError('请正确输入手机号');
}
if ($validator->fails()) {
return $this->errorBadRequest($validator);
}
$user = $this->create($request->all());
if ($user->save() == 1) {
$token = \Auth::fromUser($user);
if($token !="" || $token !=null) {
$authorization = new Authorization($token);
return $this->responseSuccess(['token' => $authorization->toArray(), 'user' => $user]);
}
User::find($user->id)->delete();
return $this->responseError("用户注册失败,请重试!");
} else {
return $this->responseError("用户注册失败,请重试!");
}
}
protected function validator(array $data) {
return \Validator::make($data, [
'name' => 'required',
'phone' => 'required|unique:users',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|min:6',
]);
}
protected function create(array $data) {
return User::create([
'name' => $data['name'],
'phone' => $data['phone'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
// 登录接口
public function login(Request $request)
{
$validator = \Validator::make($request->all(), [
'phone' => 'required',
'password' => 'required',
]);
if ($validator->fails()) {
return $this->errorBadRequest($validator);
}
$user = User::where('phone', $request->phone)->first();
if ($user && Hash::check($request->get('password'), $user->password)){
$token = \Auth::fromUser($user);
if($token !="" || $token !=null) {
$authorization = new Authorization($token);
return $this->responseSuccess(['token' => $authorization->toArray(), 'user' => $user]);
}
return $this->responseError(trans('auth.incorrect'));
}
return $this->responseError("登录失败,请重试!");
}
//刷新token接口 (一个 token 只能刷新一次 ,并且需要在 token 的过期时间内进行刷新)
public function update()
{
$authorization = new Authorization(\Auth::refresh());
return $this->responseSuccess(['token' => $authorization->toArray(), 'user' => $authorization->user()]);
}
// 注销接口
public function destroy()
{
\Auth::logout();
return $this->responseSuccess("logout");
}
}
十五: 在 routes/api.php 中管理路由
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
// Route::middleware('auth:api')->get('/user', function (Request $request) {
// return $request->user();
// });
//管理路由
$api = app('Dingo\Api\Routing\Router');
$api->version('v1',[
'namespace' => 'App\Http\Controllers\Api\V1'
], function ($api) {
$api->group(['namespace' => 'Auth', "prefix" => 'auth'], function ($api) {
$api->post('/register', 'AuthController@register');
$api->post('/login', 'AuthController@login');
$api->group(['middleware' => 'api.auth'], function ($api) {
$api->post('/refresh', 'AuthController@update');
$api->post('/logout', 'AuthController@destroy');
});
});
});
其中 : 刷新token 和 注销接口 请求的时候需要以下请求头:
Authorization : Bearer + "your token"
十六: 关于解决请求跨域问题
在最新版本的laravel(8.*)框架中已经提供了解决请求跨域的CORS扩展包( 我这里使用: "fruitcake/laravel-cors": "^2.0" )
1、 在 config/app.php 的 providers 中添加 CORS 服务提供者
'providers' => [
...
Fruitcake\Cors\CorsServiceProvider::class,
]
2、 在 app/Http/Kernel.php 添加 cors 路由中间件:
检查一下是否存在以下记录再去添加 cors 路由中间件
protected $middleware = [
...
\Fruitcake\Cors\HandleCors::class,
]
protected $routeMiddleware = [
...
'cors' => \Fruitcake\Cors\HandleCors::class,
];
3、 在 routes/api.php 中添加路由 cors 中间件:
$api = app('Dingo\Api\Routing\Router');
$api->version('v1',[
'namespace' => 'App\Http\Controllers\Api\V1',
'middleware' => [
'cors'
],
// each route have a limit of 1000 of 1 minutes
'limit' => 1000, 'expires' => 1,
], function ($api) {
$api->group(['namespace' => 'Auth', "prefix" => 'auth'], function ($api) {
$api->post('/register', 'AuthController@register');
$api->post('/login', 'AuthController@login');
$api->group(['middleware' => 'api.auth'], function ($api) {
$api->post('/refresh', 'AuthController@update');
$api->post('/logout', 'AuthController@destroy');
});
});
});
4、 在 config/cors.php 配置跨域相关信息:
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
];
十七: 请求注册用户接口