Laravel+Vue 问答平台项目实战API接口 - JWT认证

319 阅读4分钟

jwt-auth 官方文档地址

知识点

  • 安装配置jwt-auth
  • 请求验证类解耦控制器参数验证
  • API资源类应用

安装配置

配置composer阿里云镜像

全局配置,执行命令

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

仅修改当前工程配置,即进入到ant-qa-server项目根目录下,执行命令

composer config repo.packagist composer https://mirrors.aliyun.com/composer/
使用composer安装

进入到ant-qa-server项目根目录下

  1. 执行命令 composer require tymon/jwt-auth安装包
  2. 执行命令php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider",生成jwt配置文件config/jwt.php
  3. 执行命令php artisan jwt:secret,生成加密密钥,并且添加或更新.env 文件,JWT_SECRET=c9wWs2aiDSKNUGX6lyrocQctrq3ON8tc7bWQDY2oyLf6G0LH8py8vuHVi9m6jRP4 内容
更新User模型

修改app/User.php模型,内容如下

<?php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
{
    use Notifiable;

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

    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     *
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [];
    }
}
修改config/auth.php配置文件
'defaults' => [
		'guard' => 'api',
		'passwords' => 'users',
],

'guards' => [
    'web' => [
      'driver' => 'session',
      'provider' => 'users',
    ],

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

注册路由

编辑routes/api.php路由文件

<?php

Route::post('auth/login', 'AuthController@login');
Route::post('auth/register', 'AuthController@register');

// 需要认证,才可以访问
Route::group(['middleware' => 'auth:api'], function () {
    Route::post('user/logout', 'AuthController@logout');
	  Route::get('user/info', 'UserController@find');
});

此文件下的路由默认都有api前缀。这是由app/Providers/RouteServiceProvider服务提供者控制的

protected function mapApiRoutes()
{
    Route::prefix('api')
            ->middleware('api')
            ->namespace($this->namespace)
            ->group(base_path('routes/api.php'));
}

后续我们的api路由映射的控制器统一部署在控制下的Api目录下,所以这里修改下namespace,拼接上\Api目录即可

protected function mapApiRoutes()
{
    Route::prefix('api')
            ->middleware('api')
						->namespace($this->namespace.'\Api')
            ->group(base_path('routes/api.php'));
}
创建AuthController控制器

在项目根目录下执行命令php artisan make:controller Api/AuthController,此命令会创建app/Http/Controllers/Api/AuthController控制器,Api目录如果不存在,则会自动创建。接下来,在此控制器中实现注册、登录、退出登录接口功能。

注册接口

提交注册请求时,需要传用户名称,邮箱地址,密码以及确认密码参数。在处理注册请求时,需要对这些参数校验合法性,而这些参数验证相关的逻辑,编写在单独的表单请求类中。

创建注册请求类执行artisan命令

php artisan make:request RegisterRequest

执行完毕之后,会在项目中生成文件app/Http/Requests/RegisterRequest.php文件。如果之前没有Requests目录,则自动创建。初始代码如下

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class RegisterRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}

authorize() 方法用于检查用户权限,如果返回 false 则表示用户无权提交表单,会抛出权限异常中止请求,现在我们将其调整为返回 true 即可,然后我们在 rules() 方法中定义请求字段验证规则,修改后的内容如下

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class RegisterRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:6', 'confirmed'],
        ];
    }

    public function messages()
    {
        return [
            'name.required' => '名称字段不能为空',
            'name.string' => '名称字段仅支持字符串',
            'name.max' => '名称字段长度最大值为255',
            'email.required' => '邮箱字段不能为空',
            'email.email' => '邮箱字段必须是格式化的电子邮件地址',
            'email.max' => '邮箱字段长度最大值为255',
            'email.unique' => '该邮箱已存在',       // 自动查询users表,判断该邮箱是否已存在
            'password.required' => '密码字段不能为空',
            'password.string' => '密码字段仅支持字符串',
            'password.min' => '密码字段长度不得小于6',
            'password.confirmed' => '密码不一致',   // 表单参数必须包含password_confirmation字段,且与password内容一致
        ];
    }
}

接下来,在AuthController控制器中,引入RegisterRequest表单请求类

    public function register(RegisterRequest $request)
    {
        $data = $request->all();

        $user = User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);

        $token = auth()->login($user);

        return [
            'code' => 0,
            'msg' => 'success',
            'data' => [
                'token' => $token,
            ],
        ];
    }

此方法中,无需在编写表单参数验证相关代码,已经在RegisterRequest实现。这段逻辑仅需执行注册相关代码即可,最后返回用户token

测试注册接口

validate方法在验证参数时,会判断请求是否属于ajax请求,即判断请求头中是否指定接受数据类型,如果没有指定,则会自动重定向,如果指定了,比如accept => application/json接受json数据,那么会返回一段json数据

{
    "message": "The given data was invalid.",
    "errors": {
        "name": [
            "名称字段不能为空"
        ],
        "email": [
            "邮箱字段不能为空"
        ],
        "password": [
            "密码字段不能为空"
        ]
    }
}

但是这段json数据并不符合我们的API接口规范,可以通过修改app/Exception/Handler.php文件的render()方法,修改如下

    public function render($request, Exception $exception)
    {
        if ($exception instanceof ValidationException) {
            $errors = $exception->errors();
            $msg = '参数错误';
            foreach ($errors as $errorMsg) {
                $msg = $errorMsg[0] ?? '参数错误';
            }

            return response()->json([
                'code' => -1,
                'msg' => $msg,
                'data' => [],
            ]);
        }

        return response()->json([
            'code' => -1,
            'msg' => '系统异常',
            'data' => [],
        ]);

        // return parent::render($request, $exception);
    }

这里判断如果是参数规则异常,先获取且仅获取第一条错误提示,否则统一返回参数错误

目前,其他异常情况目前统一返回系统异常

如果我们希望,无需判断请求类型,统一返回json数据,而不是重定向页面,可以修改app/Http/Middleware/Authenticate.php中间件,修改redirectTo函数如下

use Illuminate\Auth\AuthenticationException;

protected function redirectTo($request)
{
    throw new AuthenticationException();
    if (!$request->expectsJson()) {
		    return route('login');
    }
}

正常返回结果示例

{
    "code": 0,
    "msg": "success",
    "data": {
        "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3RcL2FwaVwvYXV0aFwvcmVnaXN0ZXIiLCJpYXQiOjE2MDgwMDA0OTQsImV4cCI6MTYwODAwNDA5NCwibmJmIjoxNjA4MDAwNDk0LCJqdGkiOiI4OHBDeHNNV2x6R0hlRUlwIiwic3ViIjoxLCJwcnYiOiI4N2UwYWYxZWY5ZmQxNTgxMmZkZWM5NzE1M2ExNGUwYjA0NzU0NmFhIn0.PhJ50lStyG6jcVuP0uQHVSXn27uuPveL-AJZ7Lz8tL0"
    }
}
登录接口
定义登录接口的请求类

在项目根目录下执行artisan 命令

php artisan make:request LoginRequest

执行完毕之后,会在项目中生成文件app/Http/Requests/LoginRequest.php文件。修改此文件

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class LoginRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'email' => ['required', 'string', 'email', 'max:255'],
            'password' => ['required', 'string', 'min:6'],
        ];
    }
}

编辑app/Http/Controller/Api/AuthController.php控制器,添加login方法,如下

    public function login(LoginRequest $request)
    {
        if (!$token = auth()->attempt($request->all(['email', 'password']))) {
            return [
                'code' => -1,
                'msg' => '账号密码错误',
                'data' => [],
            ];
        }

        return [
            'code' => 0,
            'msg' => 'success',
            'data' => [
                'token' => $token,
            ],
        ];
    }
退出登录接口

退出登录接口。仅需要将该token置为失效即可。编辑app/Http/Controller/Api/AuthController.php控制器,添加logout方法,如下

    public function logout()
    {
        auth()->logout();

        return [
            'code' => 0,
            'msg' => 'success',
            'data' => [],
        ];
    }

如果我们直接访问http://localhost/api/user/logout接口,会返回如下结果

{
    "code": -1,
    "msg": "系统异常",
    "data": []
}

auth:api中间件的路由保护,抛出的AuthenticationException异常。针对这类异常,应该指定特殊的code值,如此前端可以用来判断,是否需要跳转至登录页面。修改文件app/Http/Exceptions/Handler.phprender()方法

    public function render($request, Exception $exception)
    {
        if ($exception instanceof ValidationException) {
            $errors = $exception->errors();
            $msg = '参数错误';
            foreach ($errors as $errorMsg) {
                $msg = $errorMsg[0] ?? '参数错误';
            }

            return response()->json([
                'code' => -1,
                'msg' => $msg,
                'data' => [],
            ]);
        }
        if ($exception instanceof AuthenticationException) {
            return response()->json([
                'code' => -10001,
                'msg' => '认证失败',
                'data' => [],
            ]);
        }

        dd($exception);

        return response()->json([
            'code' => -1,
            'msg' => '系统异常',
            'data' => [],
        ]);

        // return parent::render($request, $exception);
    }

添加if判断

if ($exception instanceof AuthenticationException) {
    return response()->json([
        'code' => -10001,
        'msg' => '认证失败',
        'data' => [],
    ]);
}

如果异常类型为AuthenticationException类型,则返回code值为-10001,表示认证失败。

访问受auth:api中间件保护的路有,需要在请求的header中添加内容

Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3RcL2FwaVwvYXV0aFwvbG9naW4iLCJpYXQiOjE2MDgwNDM1NDksImV4cCI6MTYwODA0NzE0OSwibmJmIjoxNjA4MDQzNTQ5LCJqdGkiOiJIMVYyVk15dFY0akowUEIzIiwic3ViIjoxLCJwcnYiOiI4N2UwYWYxZWY5ZmQxNTgxMmZkZWM5NzE1M2ExNGUwYjA0NzU0NmFhIn0.nG7Hk0gMV6F5x4wmR2241PZ2jx6fFMmWghSwHajn07Q

此处token可以通过登陆接口获取。

测试退出登录接口

第一次使用token调用退出登录接口时,返回成功

{
    "code": 0,
    "msg": "success",
    "data": []
}

如果,在发送一次调用退出登录接口请求,会发现返回了认证失败结果

{
    "code": -10001,
    "msg": "认证失败",
    "data": []
}

因为退出登录接口中auth()->logout();代码,已经将该token处理为失效,不可再次使用了。

获取用户信息接口

获取用户信息接口,编辑在一个新的UserController控制器中

php artisan make:controller Api/UserController

在生成的app/Http/Api/UserController.php控制器中,添加find方法

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Resources\User;

class UserController extends Controller
{
    public function find()
    {
        $user = auth()->user();

        return [
            'code' => 0,
            'msg' => 'success',
            'data' => [
                'user' => new User($user),
            ],
        ];
    }
}

$user = auth()->user();根据当前登录状获取当前用户模型

new User($user)实例化的User类是一个API资源类,API资源类是对获取到的数据和返回API接口数据之间做一层格式转换。也方便与后续需要同样数据结构的地方复用。

生成用户信息资源类

在项目根目录下执行命令 php artisan make:resource User,会生成app/Http/Resources/User.php文件,初始内容如下

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class User extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return parent::toArray($request);
    }
}

修改返回的用户信息结构,编辑后的内容如下

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class User extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
        ];
    }
}