Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来.
Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
流程上是这样的:
CORS(跨来源资源共享)
策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *
。 composer require firebase/php-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;
}
}
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]);
}
}
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);
}
}