1.什么是JWT
在介绍JWT之前,我们先来回顾一下利用token进行用户身份验证的流程:
客户端使用用户名和密码请求登录
服务端收到请求,验证用户名和密码
验证成功后,服务端会签发一个token,再把这个token返回给客户端
客户端收到token后可以把它存储起来,比如放到cookie中
客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带
服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据
这种基于token的认证方式相比传统的session认证方式更节约服务器资源,并且对移动端和分布式更加友好。其优点如下:
2.相比传统token验证的优点
支持跨域访问:cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题
无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力
更适用CDN:可以通过内容分发网络请求服务端的所有资料
更适用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会简单很多
无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御
3.废话不多说,直接上代码。
第一步,安装thinkphp6
composer create-project topthink/think mythink(项目名字)
第二步,安装多应用,迟早要用上的,索性安装上
composer require topthink/think-multi-app
第三步,安装JWT扩展。
composer require firebase/php-jwt
4.测试上代码。
php think build admin(建后台应用)
php think build api (建立前台应用)
在应用控制器下面新建公用Controller.php
<?php
declare (strict_types = 1);
namespace app\admin\controller;
use app\BaseController;
use think\response\Json;
class Controller extends BaseController
{
/**
* 初始化
*/
public function initialize()
{
//拦截验证
//do something
}
/**
* 返回封装后的 API 数据到客户端
*/
protected function renderData(int $status = null, string $message = '', array $data = []): Json
{
is_null($status) && $status = config('status.success');
return json(compact('status', 'message', 'data'));
}
/**
* 返回操作成功json
* @param array|string $data
* @param string $message
* @return Json
*/
protected function renderSuccess($data = [], string $message = 'success'): Json
{
if (is_string($data)) {
$message = $data;
$data = [];
}
return $this->renderData(200, $message, $data);
}
/**
* 返回操作失败json
* @param string $message
* @param array $data
* @return Json
*/
protected function renderError(string $message = 'error', array $data = []): Json
{
return $this->renderData(500, $message, $data);
}
/**
* 获取post数据 (数组)
* @param $key
* @return mixed
*/
protected function postData($key = null)
{
return $this->request->post(empty($key) ? '' : "{$key}/a");
}
/**
* 获取post数据 (数组)
* @param string $key
* @return mixed
*/
protected function postForm(string $key = 'form')
{
return $this->postData($key);
}
/**
* 获取post数据 (数组)
* @param $key
* @return mixed
*/
protected function getData($key = null)
{
return $this->request->get(is_null($key) ? '' : $key);
}
}
然后再加上 jwt 的公用方法,在commen.php里面写
<?php
// 应用公共文件
use Firebase\JWT\BeforeValidException;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\SignatureInvalidException;
const JWT_TTL = 7200;
/**生成验签
* @param $data
* @return string
*/
function signToken($data) :string
{
$key='LAL@lc!'; //这里是自定义的一个随机字串,应该写在config文件中的,解密时也会用,相当于加密中常用的 盐-salt
$token=array(
"iss" => $key, //签发者 可以为空
"aud" => '', //面象的用户,可以为空
"iat" => time(), //签发时间
"nbf" => time() + 3, //在什么时候jwt开始生效 (这里表示生成100秒后才生效)
"exp"=> time() + JWT_TTL, //token 过期时间
"data"=> $data //记录的userid的信息,这里是自已添加上去的,如果有其它信息,可以再添加数组的键值对
);
return JWT::encode($token, $key, "HS384"); //根据参数生成了token,可选:HS256、HS384、HS512、RS256、ES256等
}
/**验证token
* @param $token
* @return array|int[]
*/
function checkToken($token) :array
{
$key = 'LAL@lc!';
$status = ['code' => 500];
try {
JWT::$leeway = 60;//当前时间减去60,把时间留点余地
$decoded = JWT::decode($token, new Key($key, 'HS384')); //同上的方式,这里要和签发的时候对应
$arr = (array)$decoded;
$res['code'] = 200;
$res['data'] = $arr['data'];
$res['data'] = json_decode(json_encode($res['data']), true);//将stdObj类型转换为array
return $res;
} catch (SignatureInvalidException $e) { //签名不正确
$status['msg'] = "签名不正确";
return $status;
} catch (BeforeValidException $e) { // 签名在某个时间点之后才能用
$status['msg'] = "token失效";
return $status;
} catch (ExpiredException $e) { // token过期
$status['msg'] = "token失效";
return $status;
} catch (Exception $e) { //其他错误
$status['msg'] = "未知错误";
return $status;
}
}
准备工作做完了,我们按照平时的习惯写接口,比如我们在admin应用的controller 下面新建Index.php,然后开始测试JWT。
<?php
declare (strict_types = 1);
namespace app\admin\controller;
use think\response\Json;
class Index extends Controller
{
public function index():Json
{
$data = [
'id' => 1001,
'name' => "吴俊峰"
];
$data['token'] = signToken($data);
return $this->renderSuccess($data);
}
public function check():Json
{
$token = $this->request->header('token');
$user_info = checkToken($token);
if($user_info['code'] != 200){
return $this->renderError('token验证失败');
}
return $this->renderSuccess($user_info,'验证成功');
}
}
我们先访问 index方法接口,如下,
我们再用得到的token去访问其他接口,
怎么样,是不是很简单?有错误之处,请大家斧正!
龙卷风之殇