最近在做一个公司的项目,前端使用 Vue.js,后端使用 Laravel 构建 Api 服务,用户认证的包本来是想用 Laravel Passport 的,但是感觉有点麻烦,于是使用了 jwt-auth

安装

jwt-auth 最新版本是 1.0.0 rc.1 版本,已经支持了 Laravel 5.5。如果你使用的是 Laravel 5.5 版本,可以使用如下命令安装。根据评论区 @tradzero 兄弟的建议,如果你是 Laravel 5.5 以下版本,也推荐使用最新版本,RC.1 前的版本都存在多用户token认证的安全问题。

$ composer require tymon/jwt-auth 1.0.0-rc.1

配置

添加服务提供商

将下面这行添加至 config/app.php 文件 providers 数组中:

app.php

'providers' => [

    ...

    Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
]

发布配置文件

在你的 shell 中运行如下命令发布 jwt-auth 的配置文件:

shell

$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

此命令会在 config 目录下生成一个 jwt.php 配置文件,你可以在此进行自定义配置。

生成密钥

jwt-auth 已经预先定义好了一个 Artisan 命令方便你生成 Secret,你只需要在你的 shell 中运行如下命令即可:

shell

$ php artisan jwt:secret

此命令会在你的 .env 文件中新增一行 JWT_SECRET=secret

配置 Auth guard

config/auth.php 文件中,你需要将 guards/driver 更新为 jwt

auth.php

'defaults' => [
'guard' => 'api',
'passwords' => 'users',
], ... 'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],

只有在使用 Laravel 5.2 及以上版本的情况下才能使用。

更改 Model

如果需要使用 jwt-auth 作为用户认证,我们需要对我们的 User 模型进行一点小小的改变,实现一个接口,变更后的 User 模型如下:

User.php

<?php

namespace App;

use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable implements JWTSubject
{
use Notifiable; // Rest omitted for brevity /**
* 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 [];
}
}

配置项详解

jwt.php

<?php

return [

    /*
|--------------------------------------------------------------------------
| JWT Authentication Secret
|--------------------------------------------------------------------------
|
| 用于加密生成 token 的 secret
|
*/ 'secret' => env('JWT_SECRET'), /*
|--------------------------------------------------------------------------
| JWT Authentication Keys
|--------------------------------------------------------------------------
|
| 如果你在 .env 文件中定义了 JWT_SECRET 的随机字符串
| 那么 jwt 将会使用 对称算法 来生成 token
| 如果你没有定有,那么jwt 将会使用如下配置的公钥和私钥来生成 token
|
*/ 'keys' => [ /*
|--------------------------------------------------------------------------
| Public Key
|--------------------------------------------------------------------------
|
| 公钥
|
*/ 'public' => env('JWT_PUBLIC_KEY'), /*
|--------------------------------------------------------------------------
| Private Key
|--------------------------------------------------------------------------
|
| 私钥
|
*/ 'private' => env('JWT_PRIVATE_KEY'), /*
|--------------------------------------------------------------------------
| Passphrase
|--------------------------------------------------------------------------
|
| 私钥的密码。 如果没有设置,可以为 null。
|
*/ 'passphrase' => env('JWT_PASSPHRASE'), ], /*
|--------------------------------------------------------------------------
| JWT time to live
|--------------------------------------------------------------------------
|
| 指定 access_token 有效的时间长度(以分钟为单位),默认为1小时,您也可以将其设置为空,以产生永不过期的标记
|
*/ 'ttl' => env('JWT_TTL', 60), /*
|--------------------------------------------------------------------------
| Refresh time to live
|--------------------------------------------------------------------------
|
| 指定 access_token 可刷新的时间长度(以分钟为单位)。默认的时间为 2 周。
| 大概意思就是如果用户有一个 access_token,那么他可以带着他的 access_token
| 过来领取新的 access_token,直到 2 周的时间后,他便无法继续刷新了,需要重新登录。
|
*/ 'refresh_ttl' => env('JWT_REFRESH_TTL', 20160), /*
|--------------------------------------------------------------------------
| JWT hashing algorithm
|--------------------------------------------------------------------------
|
| 指定将用于对令牌进行签名的散列算法。
|
*/ 'algo' => env('JWT_ALGO', 'HS256'), /*
|--------------------------------------------------------------------------
| Required Claims
|--------------------------------------------------------------------------
|
| 指定必须存在于任何令牌中的声明。
|
|
*/ 'required_claims' => [
'iss',
'iat',
'exp',
'nbf',
'sub',
'jti',
], /*
|--------------------------------------------------------------------------
| Persistent Claims
|--------------------------------------------------------------------------
|
| 指定在刷新令牌时要保留的声明密钥。
|
*/ 'persistent_claims' => [
// 'foo',
// 'bar',
], /*
|--------------------------------------------------------------------------
| Blacklist Enabled
|--------------------------------------------------------------------------
|
| 为了使令牌无效,您必须启用黑名单。
| 如果您不想或不需要此功能,请将其设置为 false。
|
*/ 'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true), /*
| -------------------------------------------------------------------------
| Blacklist Grace Period
| -------------------------------------------------------------------------
|
| 当多个并发请求使用相同的JWT进行时,
| 由于 access_token 的刷新 ,其中一些可能会失败
| 以秒为单位设置请求时间以防止并发的请求失败。
|
*/ 'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0), /*
|--------------------------------------------------------------------------
| Providers
|--------------------------------------------------------------------------
|
| 指定整个包中使用的各种提供程序。
|
*/ 'providers' => [ /*
|--------------------------------------------------------------------------
| JWT Provider
|--------------------------------------------------------------------------
|
| 指定用于创建和解码令牌的提供程序。
|
*/ 'jwt' => Tymon\JWTAuth\Providers\JWT\Namshi::class, /*
|--------------------------------------------------------------------------
| Authentication Provider
|--------------------------------------------------------------------------
|
| 指定用于对用户进行身份验证的提供程序。
|
*/ 'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class, /*
|--------------------------------------------------------------------------
| Storage Provider
|--------------------------------------------------------------------------
|
| 指定用于在黑名单中存储标记的提供程序。
|
*/ 'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class, ], ];

自定义认证中间件

先来说明一下我想要达成的效果,我希望用户提供账号密码前来登录。如果登录成功,那么我会给前端颁发一个 access _token ,设置在 header 中以请求需要用户认证的路由。

同时我希望如果用户的令牌如果过期了,可以暂时通过此次请求,并在此次请求中刷新该用户的 access _token,最后在响应头中将新的 access _token 返回给前端,这样子可以无痛的刷新 access _token ,用户可以获得一个很良好的体验,所以开始动手写代码。

执行如下命令以新建一个中间件:

php artisan make:middleware RefreshToken

中间件代码如下:

RefreshToken.php

<?php

namespace App\Http\Middleware;

use Auth;
use Closure;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; // 注意,我们要继承的是 jwt 的 BaseMiddleware
class RefreshToken extends BaseMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
*
* @return mixed
*/
public function handle($request, Closure $next)
{
// 检查此次请求中是否带有 token,如果没有则抛出异常。
$this->checkForToken($request); // 使用 try 包裹,以捕捉 token 过期所抛出的 TokenExpiredException 异常
try {
// 检测用户的登录状态,如果正常则通过
if ($this->auth->parseToken()->authenticate()) {
return $next($request);
}
throw new UnauthorizedHttpException('jwt-auth', '未登录');
} catch (TokenExpiredException $exception) {
// 此处捕获到了 token 过期所抛出的 TokenExpiredException 异常,我们在这里需要做的是刷新该用户的 token 并将它添加到响应头中
try {
// 刷新用户的 token
$token = $this->auth->refresh();
// 使用一次性登录以保证此次请求的成功
Auth::guard('api')->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']);
} catch (JWTException $exception) {
// 如果捕获到此异常,即代表 refresh 也过期了,用户无法刷新令牌,需要重新登录。
throw new UnauthorizedHttpException('jwt-auth', $exception->getMessage());
}
} // 在响应头中返回新的 token
return $this->setAuthenticationHeader($next($request), $token);
}
}

设置 Axios 拦截器

我选用的 HTTP 请求套件是 axios。为了达到无痛刷新 token 的效果,我们需要对 axios 定义一个拦截器,用以接收我们刷新的 Token,代码如下:

app.js

import Vue from 'vue'
import router from './router'
import store from './store'
import iView from 'iview'
import 'iview/dist/styles/iview.css' Vue.use(iView) new Vue({
el: '#app',
router,
store,
created() {
// 自定义的 axios 响应拦截器
this.$axios.interceptors.response.use((response) => {
// 判断一下响应中是否有 token,如果有就直接使用此 token 替换掉本地的 token。你可以根据你的业务需求自己编写更新 token 的逻辑
var token = response.headers.authorization
if (token) {
// 如果 header 中存在 token,那么触发 refreshToken 方法,替换本地的 token
this.$store.dispatch('refreshToken', token)
}
return response
}, (error) => {
switch (error.response.status) { // 如果响应中的 http code 为 401,那么则此用户可能 token 失效了之类的,我会触发 logout 方法,清除本地的数据并将用户重定向至登录页面
case 401:
return this.$store.dispatch('logout')
break
// 如果响应中的 http code 为 400,那么就弹出一条错误提示给用户
case 400:
return this.$Message.error(error.response.data.error)
break
}
return Promise.reject(error)
})
}
})

Vuex 内的代码如下:

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios' Vue.use(Vuex) export default new Vuex.Store({
state: {
name: null,
avatar: null,
mobile: null,
token: null,
remark: null,
auth: false,
},
mutations: {
// 用户登录成功,存储 token 并设置 header 头
logined(state, token) {
state.auth = true
state.token = token
localStorage.token = token
},
// 用户刷新 token 成功,使用新的 token 替换掉本地的token
refreshToken(state, token) {
state.token = token
localStorage.token = token
axios.defaults.headers.common['Authorization'] = state.token
},
// 登录成功后拉取用户的信息存储到本地
profile(state, data) {
state.name = data.name
state.mobile = data.mobile
state.avatar = data.avatar
state.remark = data.remark
},
// 用户登出,清除本地数据
logout(state){
state.name = null
state.mobile = null
state.avatar = null
state.remark = null
state.auth = false
state.token = null localStorage.removeItem('token')
}
},
actions: {
// 登录成功后保存用户信息
logined({dispatch,commit}, token) {
return new Promise(function (resolve, reject) {
commit('logined', token)
axios.defaults.headers.common['Authorization'] = token
dispatch('profile').then(() => {
resolve()
}).catch(() => {
reject()
})
})
},
// 登录成功后使用 token 拉取用户的信息
profile({commit}) {
return new Promise(function (resolve, reject) {
axios.get('profile', {}).then(respond => {
if (respond.status == 200) {
commit('profile', respond.data)
resolve()
} else {
reject()
}
})
})
},
// 用户登出,清除本地数据并重定向至登录页面
logout({commit}) {
return new Promise(function (resolve, reject) {
commit('logout')
axios.post('auth/logout', {}).then(respond => {
Vue.$router.push({name:'login'})
})
})
},
// 将刷新的 token 保存至本地
refreshToken({commit},token) {
return new Promise(function (resolve, reject) {
commit('refreshToken', token)
})
},
}
})

更新异常处理的 Handler

由于我们构建的是 api 服务,所以我们需要更新一下 app/Exceptions/Handler.php 中的 render

方法,自定义处理一些异常。

Handler.php

<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; class Handler extends ExceptionHandler
{
... /**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
// 参数验证错误的异常,我们需要返回 400 的 http code 和一句错误信息
if ($exception instanceof ValidationException) {
return response(['error' => array_first(array_collapse($exception->errors()))], 400);
}
// 用户认证的异常,我们需要返回 401 的 http code 和错误信息
if ($exception instanceof UnauthorizedHttpException) {
return response($exception->getMessage(), 401);
} return parent::render($request, $exception);
}
}

更新完此方法后,我们上面自定义的中间件里抛出的异常和我们下面参数验证错误抛出的异常都会被转为指定的格式抛出。

使用

现在,我们可以在我们的 routes/api.php 路由文件中新增几条路由来测试一下了:

api.php

Route::prefix('auth')->group(function($router) {
$router->post('login', 'AuthController@login');
$router->post('logout', 'AuthController@logout'); }); Route::middleware('refresh.token')->group(function($router) {
$router->get('profile','UserController@profile');
});

在你的 shell 中运行如下命令以新增一个控制器:

$ php artisan make:controller AuthController

打开此控制器,写入如下内容

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Transformers\UserTransformer; class AuthController extends Controller
{ /**
* Get a JWT token via given credentials.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function login(Request $request)
{
// 验证规则,由于业务需求,这里我更改了一下登录的用户名,使用手机号码登录
$rules = [
'mobile' => [
'required',
'exists:users',
],
'password' => 'required|string|min:6|max:20',
]; // 验证参数,如果验证失败,则会抛出 ValidationException 的异常
$params = $this->validate($request, $rules); // 使用 Auth 登录用户,如果登录成功,则返回 201 的 code 和 token,如果登录失败则返回
return ($token = Auth::guard('api')->attempt($params))
? response(['token' => 'bearer ' . $token], 201)
: response(['error' => '账号或密码错误'], 400);
} /**
* 处理用户登出逻辑
*
* @return \Illuminate\Http\JsonResponse
*/
public function logout()
{
Auth::guard('api')->logout(); return response(['message' => '退出成功']);
}
}

然后我们进入 tinker

$ php artisan tinker

执行以下命令来创建一个测试用户,我这里的用户名是用的是手机号码,你可以自行替换为邮箱。别忘了设置命名空间哟:

>>> namespace App\Models;
>>> User::create(['name' => 'Test','mobile' => 17623239881,'password' => bcrypt('123456')]);

正确执行结果如下图:

 
1.png

然后打开 Postman 来进行 api 测试

 
2.png

正确的请求结果如下图:

 
3.png

可以看到我们已经成功的拿到了 token,接下来我们就去验证一下刷新 token 吧

 
4.png

如图可以看到我们已经拿到了新的 token,接下来的事情便会交由我们前面设置的 axios 拦截器处理,它会将本地的 token 替换为此 token。

版本科普

感觉蛮多人对版本没什么概念,所以在这里科普下常见的版本。

  • α(Alpha)版

    ​ 这个版本表示该 Package 仅仅是一个初步完成品,通常只在开发者内部交流,也有很少一部分发布给专业测试人员。一般而言,该版本软件的 Bug 较多,普通用户最好不要安装。

  • β(Beta)版

    该版本相对于 α(Alpha)版已有了很大的改进,修复了严重的错误,但还是存在着一些缺陷,需要经过大规模的发布测试来进一步消除。通过一些专业爱好者的测试,将结果反馈给开发者,开发者们再进行有针对性的修改。该版本也不适合一般用户安装。

  • RC/ Preview版

    RC 即 Release Candidate 的缩写,作为一个固定术语,意味着最终版本准备就绪。一般来说 RC 版本已经完成全部功能并清除大部分的 BUG。一般到了这个阶段 Package 的作者只会修复 Bug,不会对软件做任何大的更改。

  • 普通发行版本

    一般在经历了上面三个版本后,作者会推出此版本。此版本修复了绝大部分的 Bug,并且会维护一定的时间。(时间根据作者的意愿而决定,例如 Laravel 的一般发行版本会提供为期一年的维护支持。)

  • LTS(Long Term Support) 版

    该版本是一个特殊的版本,和普通版本旨在支持比正常时间更长的时间。(例如 Laravel 的 LTS 版本会提供为期三年的 维护支持。)

结语

jwt-auth 确实是一个很棒的用户认证 Package,配置简单,使用方便。


原文:https://segmentfault.com/a/1190000012606246

作者:琯琯
链接:https://www.jianshu.com/p/9e95a5f8ac4a
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Laravel 5.5 使用 Jwt-Auth 实现 API 用户认证以及刷新访问令牌的更多相关文章

  1. Django之auth模块(用户认证)

    auth模块简介 auth模块是对登录认证方法的一种封装,之前我们获取用户输入的用户名及密码后需要自己从user表里查询有没有用户名和密码符合的对象, 而有了auth模块之后就可以很轻松的去验证用户的 ...

  2. Django之auth模块(用户认证)登陆组件

    auth模块简介 auth模块是对登录认证方法的一种封装,之前我们获取用户输入的用户名及密码后需要自己从user表里查询有没有用户名和密码符合的对象, 而有了auth模块之后就可以很轻松的去验证用户的 ...

  3. Django——Auth模块(用户认证模块)

    1.Auth模块简介 auth模块是对登录认证方法的一种封装,之前我们获取用户输入的用户名及密码后需要自己从user表里查询有没有用户名和密码符合的对象. 而有了auth模块之后就可以很轻松的去验证用 ...

  4. laravel JWT Auth - JSON Web令牌认证API

    https://github.com/tymondesigns/jwt-auth/wiki

  5. laravel Passport - 创建 REST API 用户认证以及Dingo/Api v2.0+Passport实现api认证

    第一部分: 安装passport 使⽤ Composer 依赖包管理器安装 Passport : composer require laravel/passport 接下来,将 Passport 的服 ...

  6. Laravel 5 中使用 JWT(Json Web Token) 实现基于API的用户认证

    在JavaScript前端技术大行其道的今天,我们通常只需在后台构建API提供给前端调用,并且后端仅仅设计为给前端移动App调用.用户认证是Web应用的重要组成部分,基于API的用户认证有两个最佳解决 ...

  7. JWT 实现基于API的用户认证

    基于 JWT-Auth 实现 API 验证 如果想要了解其生成Token的算法原理,请自行查阅相关资料 需要提及的几点: 使用session存在的问题: session和cookie是为了解决http ...

  8. Laravel 5.2 使用 JWT 完成多用户认证 | Laravel China 社区 - 高品质的 Laravel 开发者社区 - Powered by PHPHub

    Json Web Token# JWT代表Json Web Token.JWT能有效地进行身份验证并连接前后端. 降地耦合性,取代session,进一步实现前后端分离 减少服务器的压力 可以很简单的实 ...

  9. 【laravel】基于jwt实现用户认证

    安装及基础配置 使用 composer 安装 # 建议使用1.0以上版本 composer require tymon/jwt-auth .*@rc 进行一些配置 有些文档会说要添加 Tymon\JW ...

随机推荐

  1. QThreadPool线程池的使用,线程与Widget通过信号与槽的方式通信。

    因为QRunnable类并非继承自QObject,不能使用信号和槽,为了能够使用信号与槽和Widget通信,需要对QRunnable进行封装. 定义一个类QMyRunnable,该类首先继承自QObj ...

  2. spring-boot parent变更为依赖方式

    原parent继承方式 <parent> <groupId>org.springframework.boot</groupId> <artifactId> ...

  3. FireDAC 下的 Sqlite [12] - 备忘录(草草结束这个话题了)

    该话题的继续延伸主要就是 SQL 的语法了, 草草收场的原因是现在的脑筋已经进入了 IntraWeb 的世界. 相关备忘会随时补充在下面: //连接多个数据库的参考代码: FDConnection1. ...

  4. oracle 删除字段中空格

    update  sales_report set region =  REGEXP_REPLACE(region,  '( ){1,}', '')

  5. 对一个前端AngularJS,后端OData,ASP.NET Web API案例的理解

    依然chsakell,他写了一篇前端AngularJS,后端OData,ASP.NET Web API的Demo,关于OData在ASP.NET Web API中的正删改查没有什么特别之处,但在前端调 ...

  6. 在ASP.NET Web API中使用OData的Action和Function

    本篇体验OData的Action和Function功能.上下文信息参考"ASP.NET Web API基于OData的增删改查,以及处理实体间关系".在本文之前,我存在的疑惑包括: ...

  7. 解决ASP.NET MVC4中使用Html.DropDownListFor显示枚举值默认项问题

    从ASP.NET MVC 5开始,Html.DropDownListFor已经提供了对Enum的支持,但在这以前,需要通过帮助方法或扩展方法来让Html.DropDownListFor显示枚举值. 本 ...

  8. 从普通函数到对象方法 ------Windows窗口过程的面向对象封装

    原文地址:http://blog.csdn.net/linzhengqun/article/details/1451088 从普通函数到对象方法 ------Windows窗口过程的面向对象封装 开始 ...

  9. Maven 构建

    最近在工作中越来越经常的用到了Maven作为项目管理和Jar包管理和构建的工具,感觉Maven的确是很好用的.而且要将Maven的功能最大发挥出来,多模块是一个很好的集成例子. 一个Maven项目包括 ...

  10. SharePoint 2013 安装.NET Framework 3.5 报错

    环境描述 操作系统:Windows Server 2012 R2 Datacenter版本 安装报错 中途接手安装SharePoint Server 2013 with sp1,配置向导报错如下: A ...