最近和群里网友(就是评论区厚颜无耻要冠名的那个子不语啦)聊天,发现他在数据接口中校验登录状态用的还是session,在我及时劝说和科普之后,他最终决定改用JWT。那么接下来我们就聊一聊数据接口应该怎么管理登录状态以及什么是JWT
混合开发的时候是怎么做的
前后端混合开发的时候,用户登录状态的管理一般都是通过session来实现的,原理很简单:用户登录后,服务端将登录用户信息存储到服务器上的特定位置,并生成对应的session id存储到浏览器的cookie中。需要校验的时候先读取cookie中的session id,找到服务器中对应的存储内容,完成校验。
很显然,这个机制是建立在cookie基础上的,cookie又依赖于浏览器,而且有域名限制。是不适合app、小程序、以及前后端时数据接口采用其他域名等情况的。
app、小程序、前后端分离的时候要怎么做
app、小程序、前后端分离时的数据接口一般采用token来做登录信息校验。原理是用户登录后,服务端生成对应用户的一个token(一般都是一段无意义的唯一字符串)后返回,app、小程序、前端(以下统称为前端)拿到token后保存,在需要校验用户登录的接口请求中加入token(可以是get、post参数或者http header的形式),服务端拿到token后校验真实性、有效性等信息后完成登录校验。一般为了防止盗用,还会设置一套签名校验的过程。
其实token和session的原理是差不多的,都是服务端将对应用户的一个key(session的时候是session id,token的时候就是token)交给前端,前端通过token请求服务端,服务端再去反查用户,获取用户登录状态。
现在一般微信、微博等接口都是采用的这种方式。但是这种方式也有弊端,主要是:
- 服务端必须保存token,以及有效期,校验的时候必须要有数据读取的过程;
- 校验签名的时候一般需要一个secret做为加密签名的附加字符,前端必须也要同时保存这个secret,这样显然不适合代码会暴露的网页前端。
这时候,就轮到我们这次的主角JWT出场了。
什么是JWT
JWT是JSON Web Token的简称,有官网详细介绍,大家可以看一看,这里简单说一下。
JWT其实就是一种特殊的token,原理和使用方法自然和token一样。
JWT是由三部分组成的字符串,结构是:头部+主体内容(官方称之为Payload)+签名,三部分用“.”连接。头部和主体内容都是json格式的字符串再经过base64编码,为了方便放在get请求中,还需要把类似“=”、“/”等特殊字符替换掉。
头部内容是固定的,原始json就是下面这样
{
"alg": "HS256",
"typ": "JWT"
}
主要是说明了最后签名部分的加密算法。
重点是中间的主体内容,原始json一般是类似下面这样的
{
"user": "John Doe",
"exp": "2020-01-01 12:24:30"
}
主体内容一个是当前登录的用户,可以是用户id,也可以是用户名等可以检索定位到用户的信息;还有一个就是过期时间。还可以加入一些其他不私密的信息。
服务端拿到JWT之后可以在不读取数据的情况下,仅通过解码这部分信息就可以完成获取登录用户以及判断是否过期等初期工作。
最后的签名一般是把头部、主体内容再加上secret拼接成字符串再加密,这一步在用户登录生成JWT的时候就完成了。服务端拿到JWT之后只需要把前两部分加上secret再计算一次签名加以比对就可以完成校验签名,前端不需要同时保存secret。
JWT官网提供了各种服务端语言的生成代码,这里我提供一个我自己用PHP写的相对简化的方法,供大家参考
private function _jwt($payload){
$header['alg']='HS256';
$header['typ']='JWT';
$jwt_header=$this->_base64url($header);
$jwt_payload=$this->_base64url($payload);
$jwt_sign=hash_hmac('sha256', $jwt_header.'.'.$jwt_payload, $this->secret);
$jwt['token']=$jwt_header.'.'.$jwt_payload.'.'.$jwt_sign;
$jwt['sign']=$jwt_sign;
return $jwt;
}
private function _base64url($a){
$c=base64_encode(json_encode($a));
$c=str_replace('=', '', $c);
$c=str_replace('+', '-', $c);
$c=str_replace('/', '_', $c);
return $c;
}
我这个方法里需要把主题内容以数组形式的参数传入,最终返回了生成的JWT和签名,方便接收时校验签名。
最后再说一下缺点
JWT在实际使用中也是存在问题的,目前想到以下几点:
- 安全性:签名包含在token中,一旦token整体被盗用,将没有办法区分,所以有网友称之为“裸奔”;
- 过期时间放在token中而不是服务端保存处理,一旦token生成并签发出去,将无法灵活的控制有效期;
- 最后一个是用户体验,其实可以算是token方式的通病。这个问题我也和群友讨论过,大家在访问社区的时候应该会遇到过,还在访问过程中突然变成未登录。我觉得这主要时因为服务端在token过期后就即时判断为用户登录失败,不管你在网页上处于什么状态。这个问题在session方式中是不存在的,前面说过session依赖于cookie,而存储session id的cookie是会保存在整个浏览器进程,就是说只要浏览器不关闭,用户就可以一直保持登录状态。