目录
jwt概述:
1、安装jwt
2.注册服务提供者和别名
3、发布配置文件
4.在发布的配置中生成key:
5.配置 api的验证模式
6、修改Model
7、 配置项详解
8、自定义RefreshToken 中间件
9、注册RefreshToken 中间件
10、更新异常处理的handler
11、添加新的控制器
12、添加路由
13、在数据库中添加数据
14、请求接口
15、请求结果
jwt概述:
JWT(JSON Web Token)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
1、安装jwt
安装地址:https://packagist.org/packages/tymon/jwt-auth
composer require tymon/jwt-auth
2.注册服务提供者和别名
安装完成后,在配置文件 config/app.php 中注册服务提供者和别名:
'providers' => [Tymon\JWTAuth\Providers\LaravelServiceProvider::class
]'aliases' => ['JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
]
3、发布配置文件
在你项目根目录运行如下命令,生成config\ jwt.php 的配置文件:
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
4.在发布的配置中生成key:
此命令会在你的 .env 文件中新增一行 JWT_SECRET=secret。
php artisan jwt:secret
5.配置 api的验证模式
在 config/auth.php 文件中,你需要将 guards中api['driver ']的值更新为 jwt:
'guards' => ['web' => ['driver' => 'session','provider' => 'users',],'api' => ['driver' => 'jwt','provider' => 'users',],],
6、修改Model
如果需要使用 jwt-auth 作为用户认证,我们需要对app/User模型进行修改,修改后的代码如下:
<?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;/*** The attributes that are mass assignable.*要验证的字段* @var array*/protected $fillable = ['openid', 'uuid',];/*** The attributes that should be hidden for arrays.** @var array*/protected $hidden = ['password', 'remember_token',];/*** 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 [];}
}
注:此处有一个需要注意的地方如下图
7、 配置项详解
文件位置:config/jwt.php,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,],];
8、自定义RefreshToken 中间件
实现效果:提供账号密码前来登录。如果登录成功,那么我会给前端返回一个 token ,设置在 header 中以请求需要用户认证的路由。
如果用户的令牌如果过期了,可以暂时通过此次请求,并在此次请求中刷新该用户的 token,最后在响应头中将新的 token 返回给前端,这样子可以无痛的刷新 token ,用户可以获得一个很良好的体验,所以开始动手写代码。无限刷新默认时间是两周。
php artisan make:middleware RefreshToken
<?phpnamespace App\Http\Middleware;use Closure;
use Auth;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;class RefreshToken extends BaseMiddleware
{/*** Handle an incoming request.** @param \Illuminate\Http\Request $request* @param \Closure $next* @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);}
}
9、注册RefreshToken 中间件
在app/Http/Kernel.php 添加如下代码:
protected $routeMiddleware = ['refresh' => \App\Http\Middleware\RefreshToken::class,
]
10、更新异常处理的handler
由于我们构建的是 api 服务,所以我们需要更新一下 app/Exceptions/Handler.php 中的 render方法,自定义处理一些异常。
<?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
{/*** A list of the exception types that are not reported.** @var array*/protected $dontReport = [//];/*** A list of the inputs that are never flashed for validation exceptions.** @var array*/protected $dontFlash = ['password','password_confirmation',];/*** Report or log an exception.** This is a great spot to send exceptions to Sentry, Bugsnag, etc.** @param \Exception $exception* @return void*/public function report(Exception $exception){parent::report($exception);}/*** 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);}
}
11、添加新的控制器
创建控制器:
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 = ['email' => ['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' => '退出成功']);}
}
12、添加路由
在 routes/api.php 文件中,添加路由:
Route::group(['prefix'=>'auth'],function($router) {$router->match(['post','get'],'login', 'AuthController@login');$router->post('logout', 'AuthController@logout');});Route::middleware('refresh')->group(function($router) {$router->get('profile','UserController@profile');});
13、在数据库中添加数据
DB::table('users')->insert(['email' => 'john@example.com', 'password' => bcrypt('123456')]
);
注:此处密码需用函数处理,因为Auth::attempt($params)是使用bcrypt()加密验证的,否则会一直返回false
14、请求接口
15、请求结果
注:若邮箱在数据库中不存在,则也会返回false,因为验证规则里面要求的是必须在数据表中存在,自己也可以修改