前些天看到 “Token 认证的来龙去脉”这篇文章,对使用token认证已经讲得很清楚了。
我也回答过不少关于身份认证的问题,在这里也简单总结一下,其实理解其中的核心原理,无论用什么方案都是相通的。
用户认证过程
先用一个例子来说明一下常规的基于session+cookie的认证过程:
1.
客户端: 我要查看用户资料 (请求API)
服务端: 你没有凭证(session_id),请先获得凭证再来,获得凭证需要你提供用户名密码。
客户端: 好的,这是我的用户名和密码
服务端: 验证通过,现在为你生成凭证,请保管好,以后只认凭证,就算阿猫阿狗拿着你的凭证来查资料,我也会给他。
服务端生成凭证的同时,会将凭证记录在案,以便比对。
。。。
2.
客户端: 我要查用户资料,这是我的凭证(通过cookie传递session_id).
服务端: 好的,请稍等,我确认一下你的凭证是否真的(根椐session_id确认session存在且有效) ,OK,是真的,资料拿去。
。。。
3.
客户端: 我要查用户资料,这是我的凭证.
服务端: 对不起,你的凭证已经过期失效了,请重新提供用户名密码获得新的凭证。
常规处理方式
由上面例子可以看出,整个认证过程的核心是“凭证的生成和验证”, 那接下来讲一下一般情况下服务端怎么生成凭证和怎么验证凭证:
- 首先,生成凭证的前提条件是提供的用户名和密码是对的。
- 生成随机唯一的session_id, 比如md5(用户id+毫秒数)
- 生成session内容,并建立session_id与session内容的对应关系
session内容一般是用户的标识信息,比如uid,也有放置更多扩展profile信息以减少查询数据库次数的
关联即是用session_id为key保存session到文件或数据库。 - 将session_id放到cookie中响应给客户端(必要时,可将session_id加密防伪)。
- 客户端拿到cookie,下次请求时带上,服务端根椐cookie中的session_id,确认是否有效
JWT
最近流行一种叫JWT的认证方式,其实也是上面的变种。
先简单来看看JWT原理:
a. 服务端验证通过后,生成凭证(即jwt的token)
b. 请求时带上token,服务端验证token有效,验证通过,返回结果.
如果你看过JWT约定的格式算法,那应该会发现,这个生成过程,实际上只是上面提到的“session_id生成和加密防伪”, 也就是说,JWT是将常规认证简化了(只生成了凭证,不将凭证记录在案):
JWT生成凭证时,在凭证中加入了用户信息(好比是银行存折是实名的)
凭证不再使用cookie传输和保存
JWT的内容可以认为是明文的,只是增加了数字签名(相当银行存折,盖上大印防伪)
服务端认证时,无需再查询,直接根椐内容和签名,就能确认是否有效。(嗯,银行看一下存折,再看一下印章,就认为存折是真的,这好像有什么问题?)
有没有看出来,jwt只是换了一种说法和约定了内容格式,实际上用cookie也是一样的效果?
a. 服务端验证用户名密码正确,将用户ID等必须信息,生成一个字串,然后签名返回客户端
b. 客户端下次请求带上,服务端根椐签名确认客户端有效并从中取得用户标识来进行相关数据操作。
相对于常规方式,jwt简化了服务端的认证(从另一个角度看,其实是用降低安全性换来的,下文会说明)。
安全和防范
因为http协议本身无状态,所以才产生出这种的认证过程,但从认证过程可以看出,由于服务端只认凭证,这里面存在一定的安全隐患:
- 凭证被盗取
凭证是通过cookie传递,并保存在本机,这里头有被窃取的风险,所以需要一定的防范措施,比如:
- 设置cookie的http_only属性
- 使用https来传输
- 设置凭证有效期
- 伪造凭证
这个很好理解,也就是说我自己造一个凭证出来骗服务器。
再来为什么说jwt降低了安全性?先简单看看jwt的要点:
- JWT的header和playload可以说是明文的(只是用base64编码)
- playload一般会包括用户标识,不然无法确定是哪一个用户
- header中声明了签名算法
- 使用同一个secret
不难看出,secret是最关键的,一旦secret被获取或猜出来,那将是灾难性的后果,攻击者能毫不费力的冒充所有用户,因为用户标识一般是有规则的(比如是自增的uid),根椐这个标识和有效的jwt格式,拿到secret就能自己生成所有用户的jwt。
这就是为什么不用纯cookie而要用session的原因之一。
即是说,你就算获取了我的算法和secret,可以伪造session_id,但你不知道我有哪些session_id,尽可能的减低了风险。实际上更早期的认证方式就是用纯cookie的,JWT只是规范了认证的内容,有些文章把JWT说成是新一代的认证我是不认同的。
你可能会说,secret肯定要保存好啊,这没错,但你有没想过,总有开发或运维能接触到secret吧?怎么防范?
另外,算法知道了,别有用心的人要用穷举法来猜出secret也是可能的,更可怕的是很多人的secret甚至是一些常见词语(我甚至看过有人就把“secret”作为secret的)
最后,假如原来知道secret的同学离职了,你要修改secret吧? 但所有的token都是根椐旧的secret算出来的,你一改,就全认证失败了,这在正常业务中也是很难接受的。
session和JWT对比
session的优点是更安全,缺点是认证过程较复杂,性能相对较差(需要IO),分布式不好做,但这些缺点都已有成熟的方案来解决。
jwt的优点是认证简单、性能高、本身无状态无IO依赖天然的支持分布式随意扩展。但其缺点是安全风险较大。(这对于大部分带有敏感操作的站点或API服务是不适合的)
对于jwt的使用,我个人建议:
- 仅将jwt用在非敏感操作的api接口
- 如果使用jwt,尽量使用足够复杂的secret
- 必要时,可以使用自定义的非标准加密算法
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。