一、什么是jwt,怎么运行的
1、jwt == Json Web Tokens
2、传统认证与jwt认证的区别与优势:
传统的方式:
主要是将认证后的用户信息储存在服务器上,比如Session。用户下次请求的时候带上Session Id,然后服务器以此查询用户是否认证过
传统认证方式的问题:
(1)、每次用户认证通过后,服务器需要创建一天记录保存用户信息,通常是在内存中,随着认证通过的用户越来越多,服务器在这里的开销就越来越大
(2)、session是在内存中,容易带来一些扩展性的问题
(3)、当我们想要扩展应用,需要多个服务器提供服务时,需要考虑session共享问题,不然可能会导致用户多次登录
(4)、用户容易收到csrf攻击,且要考虑客户端禁用cookie的问题
jwt认证方式:
工作流程
1.用户携带用户名和密码登入;
2.服务器校验用户信息
3.然后服务器提供一个token给客户端(jwt)
4.客户端会存储token,并且在随后的每一次请求中都带着它
5.服务器校验token,并获取信息。
注意的点:
1.每一次请求都需要token
2.token应该放在请求header中
3.我们还需要将服务器设置为接受来自所有域的请求,用Access-Control-Allow-Origin:*
JWT组成结构;头部、负载、签名
jwt =》这是一个字符串在字符串中通过 . 进行分割; 分为了三个部分;
第一部分;header
第二部分:payload
第三部分:signature
使用jwt的好处
1、无状态和可扩展性:token存储在客户端,完全无状态,可扩展。负载均衡器可以将用户传递到任意服务器,因为在任何地方都没有状态或回话信息
2、安全性:token不是cookie,每次请求token都会被发送,由于没有cookie,有助于防止csrf攻击
3、token在一段时间后会过期,这个时候需要重新登录,有助于保持安全,还有一个概念叫token撤销,它允许我们根据相同的授权许可使特定的token甚至一组token无效
jwt的问题
1、token失效问题:比如在浏览器端通过用户名、密码验证获得签名的token被木马窃取,即使用户登出系统,黑客还是可以利用窃取的token模拟正常请求,可用它访问服务器,而服务器端对此完全不知道,因为jwt机制是无状态的,直到过期,中间服务器无法控制它
2、app类token的有效时间:如果APP是新闻类、游戏类、聊天类等需要长时间用户粘性的,一般可设置1年的有效时间。如果是支付类、银行类的,一般token有效时间比较短,15分钟左右
所以:一般在jwt中不要放太多敏感性的词,如果需要的话那么就要加密。其次就是设置失效时间对应的得当
二、laravel中使用jwt进行认证
jwt-auth文档:https://jwt-auth.readthedocs.io/en/develop/laravel-installation/
1、jwt的安装
composer require tymon/jwt-auth
2、发布配置
然后执行
php artisan vendor:publish --provider=“Tymon\JWTAuth\Providers\LaravelServiceProvider”
这个指令就是把jwt的配置发布出,简单点就是在config目录下创建一个jwt的配置文件,同时也把jwt的组件服务加载到项目中,之后就可以通过在env的配置中修改
3、生成秘钥
然后可以再运行
php artisan jwt:secret
这个命令会在env中增加一个JWT_SECRET,同我们的APP_KEY这个secret是十分重要的,用于给Token签名,更换这个secret会导致之前生成的所有token无效,所以不要随意的替换这个secret
4、手动添加服务提供者信息(Laravel5.4及以下版本需要,5.5及以上版本无需手动添加)
将下面这行添加至 config/app.php 文件 providers 数组中:
#文件:config/app.php
'providers' => [
// other code
Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
]
5、配置Auth guard,让api的driver使用jwt
#文件:config/auth.php
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
6、更改 User Model,让User 支持 jwt-auth,并重写getJWTIdentifier()和getJWTCustomClaims()方法
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;
class Users extends Authenticatable implements JWTSubject
{
use Notifiable ;
protected $table = 'users';
public function getJWTCustomClaims()
{
// TODO: Implement getJWTCustomClaims() method.
return [];
}
public function getJWTIdentifier()
{
// TODO: Implement getJWTIdentifier() method.
return $this->getKey();
}
}
7、注册两个 Facade别名
这两个 Facade 不是必须的,但是使用它们会给你的代码编写带来一点便利。
config/app.php
#
'aliases' => [
...
// 添加以下两行
'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,
],
8、注册路由和创建控制器
路由:
Route::group(['namespace' => 'Api\V1'],function (){
Route::any('users/login','UsersController@login');
});
控制器:
<?php
namespace App\Http\Controllers\Api\V1;
use App\Models\UsersModel;
use Illuminate\Http\Request;
//use Tymon\JWTAuth\Facades\JWTAuth;
use JWTAuth;
class UsersController extends BaseController
{
/**
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function login(Request $request)
{
$data = $request->input();
$phone = $data['phone'];
//以手机号登录测试,具体根据自己的业务逻辑
$user = UsersModel::where(['phone' => $phone])->first();
if(!$user){
$user = new UsersModel();
$user->phone = $phone;
$user->save();
}
//方式一
$token = JWTAuth::fromUser($user);
//方式二
// $token = auth('api')->login($user);
//方式三、下面这种方式必须使用密码登录
// $token = auth('api')->attempt($user->toArray());
if(!$token ){
return response()->json(['error' => 'Unauthorized'],401);
}
return $this->respondWithToken($token,$user);
}
protected function respondWithToken($token, $data)
{
return response()->json([
'data' => $data,
'access_token' =>'bearer '.$token,
'token_type' => 'bearer'
]);
}
}
返回结果:
到此用户登录生成jwt认证token完成
用户登录生成token返回前端后,每次请求都需将token随着请求头一起传递到后端,故而需要验证请求是否携带token,以及检验token是否过期,通过创建一个中间件,来对需要验证的路由进行统一验证
创建路由中间件
Middleware文加下
<?php
namespace App\Http\Middleware;
use Closure;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Auth;
class RefreshToken extends BaseMiddleware
{
/**
* @param $request
* @param Closure $next
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response|mixed
* @throws JWTException
*/
public function handle($request , Closure $next)
{
//检查此次请求中,是否携带token,如果没有则抛出异常
$this->checkForToken($request);
try{
//检测用户登录状态,如果正常,则通过
if($this->auth->parseToken()->authenticate()){
return $next($request);
}
throw new UnauthorizedHttpException('jwt-auth',json_encode(['status' => 401,'msg' => '未登录']));
}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',json_encode(['status' => 401 , 'msg' => '未登录']));
}
}
//在响应头中返回新的token
return $this->setAuthenticationHeader($next($request),$token);
}
}
注册新增的中间件到Kernel.php文件中的$routeMiddleware数组中
'refresh.token' => \App\Http\Middleware\RefreshToken::class,
创建测试路由
Route::group(['namespace' => 'Api\V1','middleware'=>'refresh.token'],function(){
//测试是否携带token
Route::any('users/test','UsersController@test');
});
对于使用了token验证中间件的路由,在请求时未携带token或者已过期,则会提示401 Token not provided,此时需要重新登录获取新的token,并携带新的token再次请求接口