laravel8+tymon/jwt-auth搭建api系统

一、服务器环境:

php:7.3.11
composer:2.0.8
mysql:5.7.27
redis:5.0.5
安装国内的 Composer 加速镜像

composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

composer升级到2.0

composer selfupdate -vvv
composer -V

在这里插入图片描述

二、安装

1.安装laravel

composer create-project --prefer-dist laravel/laravel /www/wwwroot/laravel-swoole.xxx.com -vvv

2.nginx开启伪静态

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

3.安装 predis、JWT

composer require predis/predis
composer require tymon/jwt-auth

#生成 JWT_SECRET 写入.env(自动写入)
php artisan jwt:secret

修改.env

REDIS_CLIENT=predis

4.配置文件 config/app.php

//'providers'数组中添加如下代码
'providers'=>[
    ...
    Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
],


//在'aliases'数组中给 JWT 以下两个类添加别名方便之后生成 token 时使用,(当然也可以使用 Auth 门面生成 token , 所以不添加也是可以的。) 
	
'aliases' => [
    ...
    'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
    'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,

5. JWT 的配置文件

1)发布配置文件

#生成 JWT 的 jwt.php 配置文件 实际是 vendor/tymon/jwt-auth/config/config.php 这个文件copy
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"  
2)修改配置文件
//把 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
3)修改 config/auth.php 配置文件
//把 'defaults' 数组中的默认守卫改为'api'
 'defaults' => [
//        'guard' => 'web',
        'guard' => 'api',
        'passwords' => 'users',
    ],


//把'guards' 数组中的 api 守卫的驱动改为'jwt'
'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
//            'driver' => 'token',
            'driver' => 'jwt',
            'provider' => 'users',
            'hash' => false,
        ],
    ],

6.新增phone字段

1)修改database/migrations/da2014_10_12_000000_create_users_table.php
public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name')->nullable();
            $table->char('phone', 11)->unique();
            $table->string('email')->nullable();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->text('last_token')->nullable();
            $table->rememberToken();
            $table->timestamps();
        });
    }
2)修改 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;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'phone',  //注意新增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 [];
    }
}

8.配置数据库执行数据迁移

php artisan migrate

9.注册、登录

1)新建统一响应数据文件
①新建app\Http\Helpers\ApiResponse.php

代码如下:

<?php

namespace App\Http\Helpers;

use Symfony\Component\HttpFoundation\Response as FoundationResponse;

trait ApiResponse
{
    /**
     * @var int
     */
    protected $statusCode = FoundationResponse::HTTP_OK;
    protected $token = '';

    /**
     * @return mixed
     */
    public function getStatusCode()
    {
        return $this->statusCode;
    }

    /**
     * @param $statusCode
     * @return $this
     */
    public function setStatusCode($statusCode)
    {
        $this->statusCode = $statusCode;
        return $this;
    }

    /**
     * @param $token
     * @return $this
     */
    public function setToken($token)
    {
        $this->token = $token;
        return $this;
    }

    /**
     * @param $data
     * @return \Illuminate\Http\JsonResponse
     */
    public function respond($data)
    {
        $response = response()->json($data, $this->getStatusCode());
        if ($this->token) {
            $response->headers->set('Authorization', 'Bearer ' . $this->token);
        }
        return $response;
    }

    /**
     * @param $status
     * @param array $data
     * @param null $code
     * @return mixed
     */
    public function status($status, array $data, $code = null)
    {
        if ($code) {
            $this->setStatusCode($code);
        }
        $status = [
            'status' => $status,
            'code' => $this->statusCode
        ];

        $data = array_merge($status, $data);
        return $this->respond($data);
    }

    /**
     * @param $message
     * @param int $code
     * @param string $status
     * @return mixed
     */
    /*
     * 格式
     * data:
     *  code:422
     *  message:xxx
     *  status:'error'
     */
    public function failed($message, $code = FoundationResponse::HTTP_BAD_REQUEST, $status = 'error')
    {
        return $this->setStatusCode($code)->message($message, $status);
    }

    /**
     * @param $message
     * @param string $status
     * @return mixed
     */
    public function message($message, $status = "success")
    {
        if(!is_array($message)) {
            $message = [$message];
        }
        return $this->status($status, [
            'message' => $message
        ]);
    }

    /**
     * @param string $message
     * @return mixed
     */
    public function internalError($message = "Internal Error!")
    {
        return $this->failed($message, FoundationResponse::HTTP_INTERNAL_SERVER_ERROR);
    }

    /**
     * @param string $message
     * @return mixed
     */
    public function created($message = "created")
    {
        return $this->setStatusCode(FoundationResponse::HTTP_CREATED)
            ->message($message);
    }

    /**
     * @param $data
     * @param string $status
     * @return mixed
     */
    public function success($data, $status = "success")
    {
        return $this->status($status, compact('data'));
    }

    /**
     * @param string $message
     * @return mixed
     */
    public function notFond($message = 'Not Fond!')
    {
        return $this->failed($message, Foundationresponse::HTTP_NOT_FOUND);
    }
}


②新建app\Http\Helpers\ExceptionReport.php

代码如下:

<?php

namespace App\Http\Helpers;

use ErrorException;
use Exception;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;

class ExceptionReport
{
    use ApiResponse;

    /**
     * @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
     */
    //当抛出这些异常时,可以使用我们定义的错误信息与HTTP状态码
    //可以把常见异常放在这里
    public $doReport = [
        AuthenticationException::class => ['未登录或登录状态失效', 401],
        ModelNotFoundException::class => ['该模型未找到', 404],
        AuthorizationException::class => ['没有此权限', 403],
        ValidationException::class => [],
        UnauthorizedHttpException::class => ['未登录或登录状态失效', 401],
        TokenInvalidException::class => ['未登录或登录状态失效', 401],
        NotFoundHttpException::class => ['没有找到该页面', 404],
        MethodNotAllowedHttpException::class => ['访问方式不正确', 405],
        ErrorException::class => ['服务器内部错误', 500],
        QueryException::class => ['参数错误', 400],
    ];

    public function register($className, callable $callback)
    {

        $this->doReport[$className] = $callback;
    }

    /**
     * @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) {
            return $this->failed(current($this->exception->errors()), $this->exception->status);
        }
        $message = $this->doReport[$this->report];
        return $this->failed($message[0], $message[1]);
    }

    public function prodReport()
    {
        return $this->failed('服务器错误', '500');
    }
}

③修改App\Exceptions\Handler.php
<?php

namespace App\Exceptions;

use App\Http\Helpers\ExceptionReport;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;

class Handler extends ExceptionHandler
{
    /**
     * A list of the exception types that are not reported.
     *
     * @var array
     */
    protected $dontReport = [
        //
    ];

    /**
     * A list of the inputs that are never flashed for validation exceptions.
     *
     * @var array
     */
    protected $dontFlash = [
        'password',
        'password_confirmation',
    ];

    /**
     * Register the exception handling callbacks for the application.
     *
     * @return void
     */
    public function register()
    {
        $this->reportable(function (Throwable $e) {
            //
        });
    }

    public function render($request, Throwable $exception)
    {
        if($request->is("api/*")){
            $reporter = ExceptionReport::make($exception);
            if ($reporter->shouldReturn()){
                return $reporter->report();
            }
            if(env('APP_DEBUG')){
                //开发环境,则显示详细错误信息
                return parent::render($request, $exception);
            }else{
                //线上环境,未知错误,则显示500
                return $reporter->prodReport();
            }
        }
        return parent::render($request, $exception);
    }
}

2)新建基本控制

新建app\Http\Controllers\Api\Controller.php

php artisan make:controller Api\Controller

代码如下

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller as BaseController;
use App\Http\Helpers\ApiResponse;

class Controller extends BaseController
{
    use ApiResponse;
}
3)新建控制器
①新建app\Http\Controllers\Api\AuthController.php
#创建控制器
php artisan make:controller Api\AuthController

代码如下

<?php
namespace App\Http\Controllers\Api;

use App\Http\Requests\Api\AuthRequest;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Facades\JWTAuth;

class AuthController extends Controller
{

    /**
     * @param AuthRequest $request
     * @return mixed
     */
    public function login(AuthRequest $request)
    {
        $user = User::query()->where(['phone' => $request->phone, 'password' => $request->password])->firstOrFail();
        $token = Auth::guard('api')->fromUser($user);
        if ($token) {
            if ($user->last_token) {
                try {
                    JWTAuth::setToken($user->last_token)->invalidate();
                } catch (TokenExpiredException $e) {
                    //因为让一个过期的token再失效,会抛出异常,所以我们捕捉异常,不需要做任何处理
                }
            }
            $user->last_token = $token;
            $user->save();
            return $this->setToken($token)->success('登录成功');
        }
        return $this->failed('账号或密码错误', 400);
    }

    /**
     * 返回当前登录用户信息
     * @return mixed
     */
    public function info()
    {
        $user = Auth::guard('api')->user();
        return $this->success($user);
    }

    /**
     * 注册接口
     * @param AuthRequest $request
     * @return mixed
     */
    public function register(AuthRequest $request)
    {
        $member = [
            'password' => $request->password,
            'phone' => $request->phone,

        ];
        if(User::query()->create($member)) {
            return $this->success('用户注册成功');
        } else {
            return $this->failed('用户注册失败');
        }
    }
}
②新建app\Http\Requests\Api\AuthRequest.php
#创建表单请求类
php artisan make:request AuthRequest

代码如下

<?php

namespace App\Http\Requests\Api;

class AuthRequest extends FormRequest
{
    
    public function rules()
    {
        switch ($this->method()) {
            case 'POST':
            {
                $return['password'] = ['bail', 'required', 'max:16', 'min:6'];
                if(strpos($this->route()->uri, 'login')) {
                    $return['phone'] = ['required', 'numeric', 'regex:/^1[3456789][0-9]{9}$/'];
                } else {
                    $return['phone'] = ['required', 'numeric', 'regex:/^1[3456789][0-9]{9}$/', 'unique:Users,phone'];
                }
                return $return;
            default:
            {
                return [

                ];
            }
        }
    }

    public function messages()
    {
        return [
            'phone.required' => '手机号不能为空',
            'phone.numeric' => '手机号错误',
            'phone.regex' => '手机号错误',
            'phone.unique' => '手机号已被注册',
            'password.required' => '密码不能为空',
            'password.max' => '密码长度不能超过16个字符',
            'password.min' => '密码长度不能小于6个字符'
        ];
    }
}

4)修改app\Http\Middleware\Authenticate.php

注释下面方法

<?php

namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;

class Authenticate extends Middleware
{
    /**
     * Get the path the user should be redirected to when they are not authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string|null
     */
//    protected function redirectTo($request)
//    {
//        if (! $request->expectsJson()) {
//            return route('login');
//        }
//    }
}

4)修改路由

修改routes/api.php

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\AuthController;

/*
|--------------------------------------------------------------------------
| 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();
// });

Route::namespace('Api')->group(function () {
    Route::prefix('auth/')->group(function () {
        //用户注册
        Route::post('register', [AuthController::class, 'register']);
        Route::post('login', [AuthController::class, 'login']);
        Route::middleware('auth:api')->group(function () {
            //当前用户信息
            Route::get('info', [AuthController::class, 'info']);
        });
    });
});

10.解决请求跨域问题

在最新版本的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 中间件:
Route::namespace('Api')->middleware('cors')->group(function () {
    Route::prefix('auth/')->group(function () {
        //用户注册
        Route::post('register', [AuthController::class, 'register']);
        Route::post('login', [AuthController::class, 'login']);
        Route::middleware('auth:api')->group(function () {
            //当前用户信息
            Route::get('info', [AuthController::class, 'info']);
        });
    });
});

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,
     
     ];
11.加入git
git init .
git add .
git commit -m "初始化"
git remote add origin git@gitee.com:***.git
git push -u origin master

git忽略文件权限

git config core.filemode false  // 当前版本库
git config --global core.fileMode false // 所有版本库
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值