什么是JWT
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
传统的session认证
我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来.
基于session认证所显露的问题
Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
基于token的鉴权机制
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
流程上是这样的:
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息
- 服务器通过验证发送给用户一个token
- 客户端存储token,并在每次请求时附送上这个token值
- 服务端验证token值,并返回数据
这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)
策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *
。
tp6安装JWT
composer require firebase/php-jwt
tp6重新封装JWT
- 在
app
文件夹下新建一个services
文件夹 - 在
services
文件夹下新建JwtService.php
内容如下
<?php
namespace app\services;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Symfony\Component\VarDumper\VarDumper;
class JwtService{
public function getToken($uid){
$key="xuanransoftware";//自定义key值
$payload=array(
"iss"=>'http://api.xuanransoftware.com',//签发者
"aud"=>"http://api.xuanransoftware.com",//接收者
"iat"=>time(),//初始时间
"nbf"=>time()+60*60*24*7,//过期时间为7天
"uid"=>$uid,//前端页面传uid
);
$jwt=JWT::encode($payload,$key,'HS256');
return $jwt;
}
public function checkToken($token){
$key="xuanransoftware";//自定义key值
$decoded=JWT::decode($token,new Key($key,'HS256'));
return $decoded;
}
}
- 新建Login控制器
php think make:controller admins/Login
Login控制器内容如下
<?php
declare (strict_types = 1);
namespace app\controller\admins;
use think\Request;
use app\BaseController;
// 引入admin模型
use app\model\Admin;
//引入JWT进行token验证
use app\services\JwtService;
class Login extends BaseController
{
/**
* 显示资源列表
*
* @return \think\Response
*/
public function login(Request $request)
{
//判断是否为post请求
if($request->isPost()){
//获取post参数
$params = $request->param() ;
$userName =trim($params['username']);
$password =trim(sha1($params['password']));
//使用Admin模型查找用户名和密码
$db = new Admin();
$index = $db->find('username',$userName);
//判断用户是否存在
if (isset($index)){
//判断密码是否正确
if($password==$index['password']){
//更新登录时间
$index->lastlogin = time();
$index->save();
//根据用户id生成token
$jwt = new JwtService();
$token = $jwt->getToken($index['id']);
return $this->msg(1,'登录成功',$token);
}
else{
return $this->msg(0,'密码错误');
}
}else{
return $this->msg(0,'用户名不存在,请注册后重新登录');
}
}else{
return $this->msg(0,'请求类型码错误');
}
}
//返回状态码
private function msg($code,$msg,$token='')
{
return json(['code'=>$code,'msg'=>$msg,'token'=>$token]);
}
}
- 新建CheckToken中间件
php think make:middleware CheckToken
中间件内容如下
<?php
declare (strict_types = 1);
namespace app\middleware;
use app\services\JwtService;
class CheckToken
{
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
$token=$request->param('token');
//没有token
if (!$token){
return response("token是空的");
}
//没有找到jwt类
$jwt=new JwtService();
try {
$decode=$jwt->checkToken($token);
}catch (\Exception $e){
echo $e;
return response("位置错误");
}
//解析token
$decode=json_decode(json_encode($decode),true);
var_dump($decode);
if (!$decode){
return response("token 不合法");
}
if (time() > $decode['nbf']){
return response("token 已过期");
}
return $next($request);
}
}
- 添加路由中间件组
- 因为时区不同,所以需要注释JWT中的这段