安装
Composer require tymon/jwt-auth 1.0.0-rc.1
配置
添加服务提供者
- 'providers' => [
- Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
- ]
发布配置文件
PHP artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
生成密钥
PHP artisan jwt:secret
配置 Auth guard
- 'guards' => [
- 'api' => [
- 'driver' => 'jwt',
- 'provider' => 'users',
- ],
- ],
更改 Model
实现 Tymon\JWTAuth\Contracts\JWTSubject 接口, 编写 getJWTIdentifier 和 getJWTCustomClaims 方法.
- <?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);
- }
- }
更新异常处理的 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);
- }
- }
测试
- // 使用 Auth 登录用户, 如果登录成功, 则返回 201 的 code 和 token, 如果登录失败则返回
- return ($token = Auth::guard('api')->attempt($request->only('email','password')))
- ? response(['token' => 'bearer' . JWTAuth::fromUser(Auth::guard('api')->user())], 201)
- : response(['error' => '账号或密码错误'], 400);
- }
注意: 在认证成功后其他经过 RefreshToken 中间件的 API 路由在请求的时候需要在 header 上加上 Authorization 字段, 其值为登录返回的 token
参考文档
- https://laravelacademy.org/post/9794.html
- https://www.jianshu.com/p/9e95a5f8ac4a
来源: http://www.bubuko.com/infodetail-2945821.html