JSON WEB TOKEN 简称 JWT 是现在最常用的一样校验方式.
他的作用如下
- jwt是为了在网络应用环境传递声明而执行的一种基于json的开放标准。
- jwt被用来在身份提供者和服务提供者间传递被认证的用户身份信息,简单来说,就是用
- 来验证身份的手段,例如登录校验,像我们之前用的cookie。
- jwt可以使用HMAC算法或者是RSA的公私秘钥对来进行签名,来保证信息的可靠性。
应用场景
在例如身份验证场景中,用户一旦登录,接下来的每个请求都会包含jwt,用来验证身份信息。由于通信双方使用jwt对数据进行编码,它的信息是经过签名的,所以可以确保信息的安全性.
jwt对比cookie
cookie缺点
- 客户端发请求给服务器,服务器种植cookie后,每次请求都会带上cookie,浪费带宽
- cookie不能跨服务器访问,不支持跨域
- 服务器要对登录的用户对象进行存储,浪费服务器内存
jwt优点
- jwt是不基于状态的,不需要每次请求都带上token,节约流量
- 服务器不需要占用内存,信息相对于可靠些
- 可以跨服务端,可以共用
这都是业界对jwt的评价,可是jwt真的是这样的吗?
我们先来看看JWT的是怎么生成的?JWT是一种规范.他由下面的一种结构构造而成.
header.payload.signature
JWT 的 Header 部分包含有关如何计算 JWT 签名的信息,是一个以下形式的 JSON 对象:
{
"typ": "JWT",
"alg": "HS256"
}
在上面的 JSON 中,“typ”键的值指定对象是JWT,“alg”键的值指定用于创建 JWT 签名的算法。 在示例中,我们使用 HMAC-SHA256算法(一种使用密钥的散列算法)来计算签名(在步骤3中会更详细的介绍)。
创建 PAYLOAD
JWT 的 payload 部分时是存储在 JWT 内的数据。在我们的示例中,身份验证服务器创建一个JWT,其中存储有用户信息,特别是用户ID。
{
"userId": "b08f86af-35da-48f2-8fab-cef3904660bd"
}
在我们的示例中,我们只将一个声明放入 payload 中。 你可以根据需要添加任意数量的声明。JWT 规定了7个官方字段,供选用。
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
创建 SIGNATURE
// signature algorithm
data = base64urlEncode( header ) + “.” + base64urlEncode( payload )
hashedData = hash( data, secret )
signature = base64urlEncode( hashedData )
是的JWT是通过不断的计算来进行校验的.
这里的性能问题, 指的是该结构带来的请求数据变多的问题. 在jwt的使用场景中, 所有的请求都需要完整带上jwt的数据. 由于payload这部分的数据仅仅是base64后的数据, 并没有做任何处理. 所以, 如果在payload中增加过多的数据, 就会导致jwt的结构变大, 从而导致请求的效率降低.
如果把token存在redie或者mongodb中也是一个不错的选择. 我们该如何做呢?
下面我以Laravel
迁移文件如下
$table->bigIncrements('id');
$table->integer('user_id');
$table->string('token', 191)->default('')->comment('token');
$table->integer('expiry_time')->default(0)->comment('到期时间');
$table->string('ua')->default('')->comment('浏览器');
$table->string('ip')->default('')->comment('使用者ip');
$table->timestamps();
在通过登录校验成功的时候生成Token
$token = md5(uniqid(md5(microtime(true)), true));
$token = sha1($token);
UserTokens::create([
'user_id' => $user->id,
'token' => $token,
'ip' => request()->ip(),
'expiry_time' => time() + 2 * 60 * 60,
'ua' => request()->userAgent()
]);
return $token;
在需要的路由注入中间件拦截器
class Authenticate extends Controller
{
public function handle(Request $request, \Closure $next)
{
$Authorization = $request->header('Authorization');
if (!$Authorization) {
return response($this->error('请登录', 40001));
}
$token = explode(' ', $Authorization);
$token = $token[1];
if ($this->validateToken($token)) {
return $this->validateToken($token);
}
return $next($request);
}
protected function validateToken($token)
{
$dbToken = UserTokens::where('token', $token)->first();
if ($dbToken->token != $token) {
return response($this->error('您的信息有误!', 40010));
} else if ($dbToken->expiry_time < time()) {
return response($this->error('token有效期到期', 40020));
} else if ($dbToken->ua != \request()->userAgent()) {
return response($this->error('您的信息有误!', 40010));
} else if ($dbToken->ip != \request()->ip()) {
return response($this->error('您的信息有误!', 40010));
} else {
return false;
}
}
}
这种方式也是可以的.