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
项目根目录下
- 执行命令
composer require tymon/jwt-auth
安装包 - 执行命令
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
,生成jwt
配置文件config/jwt.php
- 执行命令
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.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' => [],
]);
}
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,
];
}
}