最近在做一个公司的项目,前端使用 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

<?phpnamespace 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

<?phpreturn [/*|--------------------------------------------------------------------------| 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

<?phpnamespace 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());}}// 在响应头中返回新的 tokenreturn $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.authorizationif (token) {// 如果 header 中存在 token,那么触发 refreshToken 方法,替换本地的 tokenthis.$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 = truestate.token = tokenlocalStorage.token = token},// 用户刷新 token 成功,使用新的 token 替换掉本地的tokenrefreshToken(state, token) {state.token = tokenlocalStorage.token = tokenaxios.defaults.headers.common['Authorization'] = state.token},// 登录成功后拉取用户的信息存储到本地profile(state, data) {state.name = data.namestate.mobile = data.mobilestate.avatar = data.avatarstate.remark = data.remark},// 用户登出,清除本地数据logout(state){state.name = nullstate.mobile = nullstate.avatar = nullstate.remark = nullstate.auth = falsestate.token = nulllocalStorage.removeItem('token')}},actions: {// 登录成功后保存用户信息logined({dispatch,commit}, token) {return new Promise(function (resolve, reject) {commit('logined', token)axios.defaults.headers.common['Authorization'] = tokendispatch('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

<?phpnamespace 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

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

<?phpnamespace 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')]);

正确执行结果如下图:

然后打开 Postman 来进行 api 测试

正确的请求结果如下图:

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

如图可以看到我们已经拿到了新的 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,配置简单,使用方便。

文章结束,感谢阅读。

Laravel 5.5 使用 Jwt-Auth 实现 API 用户认证以及无痛刷新访问令牌相关推荐

  1. 使用 Jwt-Auth 实现 API 用户认证以及无痛刷新访问令牌

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

  2. Laravel使用Dingo API+JWT实现认证机制 无痛刷新Token

    Laravel使用Dingo API+JWT实现认证机制 无痛刷新Token 一.安装[Dingo API](https://github.com/dingo/api) 和 [JWT](https:/ ...

  3. Laravel 5.5 使用 Jwt-Auth 实现 API 用户认证、刷新令牌(一)

    需求: 新项目,采用前后端分离的模式,前端使用 Vue.js,后端使用 Laravel 5.5构建 Api 服务,用户认证的包使用 jwt-auth .本次博客会分4步完成, Laravel 5.5 ...

  4. php lumen auth,学习 Lumen 用户认证 (一)

    好久没写 PHP 代码了,尤其是 Lumen,我是 Lumen 的忠实用户,自从面世开始,我就将 Lumen 作为我 API 的主要框架使用. 但说到 API,不得不说的一个概念:「前后端分离」,现在 ...

  5. SpringtBoot+SpringSecurity+Jwt+MyBatis整合实现用户认证以及权限控制

    文章目录 前言 数据库表结构 项目结构图 核心配置类SecurityConfig 实体类 工具类 用户登录认证 Token令牌验证 获取用户权限 用户权限验证 Service层实现类 统一响应类 Co ...

  6. 基于 JWT + Refresh Token 的用户认证实践

    HTTP 是一个无状态的协议,一次请求结束后,下次在发送服务器就不知道这个请求是谁发来的了(同一个 IP 不代表同一个用户),在 Web 应用中,用户的认证和鉴权是非常重要的一环,实践中有多种可用方案 ...

  7. Rest API的认证模式

    Rest API的认证模式 Rest API的认证模式 AppKey AppKey + Secret JWT OAuth Rest API的认证模式 微服务系统中,很多团队采用了API驱动设计开发,服 ...

  8. springboot整合jwt_Spring Boot整合JWT实现用户认证(附源码)

    作者:LTLAYXhttps://dwz.cn/yv1Do6e3 推荐阅读 1. Java 性能优化:教你提高代码运行的效率 2. 基于token的多平台身份认证架构设计 3. select coun ...

  9. Spring Boot整合JWT实现用户认证(附源码)

    点击上方"程序IT圈",选择"置顶公众号" 每天早上8点50分进来看看,就是最大的支持 来源:https://dwz.cn/yv1Do6e3 什么是JWT JW ...

最新文章

  1. CodeForces 获得数据
  2. Python入门基础教程 Working with Python – Introductory Level
  3. 美国AI博士指出,自学Python到底能做什么
  4. 转 Java的各种打包方式(JAR/WAR/EAR/CAR)
  5. sql相同顺序法和一次封锁法_率土之滨追击战法攻略
  6. FluentNhibernate 组件component及应用
  7. linux系统安装xhprof,LNMP部署laravel与xhprof安装使用
  8. 你不知道的JavaScript--大白话讲解Promise
  9. 如何在工作中学习,让自己成为领域专家?
  10. eclipse jar打包 jar line too long 异常处理方法
  11. webRTC之[chromium-style] virtual methods with non-empty bodies shouldnt be declared inline(二十一)
  12. i2c-tools使用及调试
  13. ABB伺服驱动调试(四)
  14. siege 测试post_使用Siege进行Web App性能测试:计划,测试,学习
  15. canvas教程大纲
  16. vscode的常用插件
  17. 鸢尾花(iris)数据集保存到本地以及sklearn其他数据集下载保存
  18. 兄弟俩今年的年龄和是35岁,当哥哥像弟弟现在这样大时,弟弟的年龄恰好是哥哥年龄的一半,哥哥今年几岁?
  19. 1044 火星数字 ——c实现
  20. python3黑帽编程_Python3.7 黑帽编程

热门文章

  1. 给WIN7安装盘添加双PE3.0
  2. react前端显示图片_在react里怎么引用图片
  3. android singleinstance home,Android启动模式之singleinstance的坑
  4. 计算机组成原理基于mips结构pdf,计算机组成原理_L12-MIPS系统结构-V1.pdf
  5. html怎么防止修改数据,HTML防数据采集
  6. php warning date(),lnmp打开cacti时提示PHP Warning: date()
  7. 我要自学网python视频教程_人生苦短,请用Python!学习Python的四大理由
  8. 计算机考研:计算机网络五大考点解析
  9. Hibernate检索1
  10. 某聊天工具消息记录数据库文件解密逆向分析