浅谈 RESTful Api 对APP身份校验的一些思考

写这篇文章的初衷来源于最近心血来潮想独立开发一款自己的产品,由于本人一直从事Android开发,但产品又需要数据的支持,迫于无奈又捡起了我那半桶水的服务端开发,在做架构设计的时候遇到了一些问题,下面想谈谈在Servier/APP通讯中对用户身份安全认证的一些思考,也希望可以抛砖引玉,和大家一起探讨出更好的解决方案。

本文章打算从下面几个点切入:
1、认识什么是Session和Token认证?
2、Session和Token认证分别会引发些什么问题?
3、如何解决这些问题,还有更深层次的思考?

Session认证

由于HTTP请求协议是无状态的,用户每次访问服务器,对服务器而言都是全新的访客,服务器没有办法识别是哪个用户,更不用说去记录用户上一次的活动状态了,所以我们需要某种机制来维持这个关系,就这样Session诞生了,每一个Session都对应着一个SessionId(唯一标识),在这个Session里可以保存用户的各种状态信息,那服务器要怎么识别Session具体属于哪个用户的呢,此时Cookie就派上用场了,它是存放在客户端的,里面记录着一个SessionId,只要用户在请求服务器的时候,带上这个Cookie,也就可以和服务器“相认”了。

Session认证会引发的一些问题

  1. 由于Session是存放在服务器的内存中的,随着用户量的增加,会导致服务器内存吃紧,压力和开销也明显增大。

  2. 当业务量足够大,单台服务器吃不消的时候,我们往往会做负载均衡来分担单台服务器的压力,此时就会面临到一些问题,比如用户在A服务器登录建立起Session会话,当再次访问服务器的时候,被负载均衡策略代理到了B服务器上,此时B服务器上是没有该用户的Session会话的,会导致用户需要重新登录等。

  3. 在移动端上,每次请求服务器都要带上Cookie,有点显得“过重”,并且也不是很好“维护”。

当然,以上提到的这些点,对于现在来说已经有很多成熟的解决方案了:

  1. 比如内存吃紧,单台服务器压力过大,自动扩容就可以解决这个问题。

  2. 在做服务器负载均衡的时候,可能会导致用户丢失Session会话,只要处理好Session的保持、复制或者共享即可,比如用Nginx 做负载均衡的Session保持,Tomcat来做Session的复制,通过KV数据库来做Session的共享,比如Redis等。

  3. 要在移动端上维护Cookie,GitHub上也有一些开源框架,可以很轻松的帮我们处理这个问题。

既然问题都可以得到解决,为什么我还想去谈Token认证,其实这只是“另外一种”解决问题的思路,能让问题更加友好,更加不费力的解决。

Token认证

Token认证也是无状态的,它的出现使得服务器不再需要为每个用户在内存中开辟一个Session会话,简单点说,就是在用户登录成功的时候,服务器会给用户颁发一个特殊的令牌(Token),这个令牌类似于身份证,每个用户都是不一样的,每次用户访问服务器的时候,只需要带上这个令牌,服务器便可知道具体是哪个用户,对于客户端而言,它只是一串字符串,维护起来很轻松,比如可以存放在内存,缓存文件,数据库,或者so文件中,对于服务端而言,很大程度上节约了内存的开销,只需要将用户id和token做KV的关联即可。

Token认证会引发的一些问题

1、如何传递Token才是比较安全的做法?
2、如何防范当传输信息被非法盗取后发起的恶意请求?

针对以上的问题,我们一步步的来探讨:
首先我们对Token认证做了简单的介绍,这时候我们应该去考虑,当用户去访问服务器的时候,如果只是简单的将Token拼接在URL之后,或者附带在HTTP请求头里,这样是很容易被窃取的,由于Token是客户端和服务器之间唯一识别的凭证,只要知道了这个Token和它所携带的方式,那么就可以模拟用户的一些操作了,比如修改用户信息,虚拟财产交易,更改订单状态等。

如何传递Token才是比较安全的做法?

1、这里引入一个比较主流的规范:JWT

JSON WEB Token(JWT),是一种基于JSON的、用于在网络上声明某种主张的令牌(token)。JWT通常由三部分组成:头信息(header),消息体(payload)和签名(signature)。

关于JWT一些更具体的信息,大家可以在https://jwt.io上了解,这里我就不做过多的介绍了,我直接切入主题。

JWT它是由三个部分组成的,分别是头部(Header),载体(Payload),签名(Signature),它们之间用字符串“.”进行连接,也就是:

头部(Header).载体(Payload).签名(Signature)

官方对每个部分应该包含什么信息也给出了一些建议(并不是强制要求),这里我们来简单看一下:

头部(Header):
由两部分组成:令牌类型,加密算法,比如:

{
  "alg": "HS256",//加密算法为HS256
  "typ": "JWT"//令牌类型为JWT
}

载体(Payload):
由一些有效信息组成(不应该包含敏感信息,比如用户的手机,密码等),比如:

{
  "sub": "1234567890",//面向的用户编号为1234567890
  "name": "John Doe",//面向的用户名为John Doe
  "admin": true//是否是管理员
}

到这里,可能有朋友会问,这信息也太简单了吧,一个布尔值就可以确定用户是不是管理员,那么我手动修改下信息,那是不是也可以变成管理员了,别着急,我们继续往下看。

关键部分,签名(Signature),它是由头部和 载体两部分经过一些编码处理和加密组成的,官方给了一个示例:

  HS256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

我们可以看出这个签名是是由:
经过Base64编码的头部 + 字符串“.” + 经过Base64编码的载体一起拼成后的字符串,再和秘钥secret进行加密算法(头部指定的HS256算法)组成。


JWT生成示例

如上图所示,可以我们就可以得到JWT了,我们再来梳理一下流程:
当用户登录服务器的时候,服务器会根据用户的信息生成JWT,并保存在KV数据库里,然后回传给用户生成的JWT,用户下一次再访问服务器的时候只需要带上这个JWT,即可完成身份的验证。


JWT交互时序图

为什么这样可以保证信息的安全,因为签名(Signature)是由服务器通过头部和载体和加密算法组成,虽然头部和载体是可以通过Base64解码成明文的,但是我们在生成签名的时候已经确定好的里面的内容,加入用户篡改头部或者载体的信息,比如把管理员权限字段设置为true,那么和我们服务器所存的签名肯定是对应不起来的,这时候服务器就会拒绝掉该用户的请求。

关于JWT的优点:
  1. 由JSON格式组成,所以具有跨平台性,可以支持各种编程语言。

  2. 减少了服务器的压力,相比在内存中开辟Session,存储字符串明显来的省力许多,但是这里需要注意用户的规模,虽然这里我们减少了I/O的开销,但是我们却多了加解密的过程,两者需要权衡。

  3. 解决了分布式共享Session的问题,不需要再去考虑文章开头所提到的负载均衡策略所带来的问题。

关于JWT的缺点:

没有什么东西是完美无瑕的,JWT虽然解决了一些问题,但是也同时带来了一些新的问题,我列举几个:

  1. 无法控制单点登录,假设用户在A设备登录了服务器,服务器正常返回JWT,用户又在B设备登陆了服务器,服务器又返回一个JWT,此时2个JWT都是可用的,服务器没办法踢出其中任意设备的用户。

  2. 当用户退出登录或者修改密码的时候,JWT没有办法回收,之前和朋友讨论过也看了一些开源项目,大多数的做法就是直接把JWT丢掉,类似删Cookie形式,但是这里还是存在安全隐患,如果被窃取,依旧会造成一些问题,因为这个JWT还是有效可用的。
    可能有些朋友会说,在载体里面设置JWT的有效时间不就好了,但是这样又会导致新的问题,这个有效时间多久合适?太短,会造成用户频繁的过期登录,太长,当发生以上问题的时候,还是要忍耐有效期内的操作。

  3. 关于安全操作,回到文章开头的问题,当JWT被抓包窃取后,是不是就可以模拟用户(有效期内)操作了?比如修改用户的订单信息,频繁的调用验证码接口等,这里涉及到URL的重放等攻击,先不做深究,有时间再写一篇文章来详细说明。

如何防范当传输信息被非法盗取后发起的恶意请求?

2、另外一种加密Token的方式

整体执行流程还是不变的,只是在处理这个Token的时候,让它变得更安全(加密,时效,可控)
具体流程:


登录时序图
  1. 客户端在登录的时候除了账号密码一些信息外,还要额外的带上客户端的本地时间戳。

  2. 服务器验证账号密码,并求出当前服务器时间戳减去客户端时间戳的时间差,并生成一个Token,返回给客户端的信息为:用户编号(不一定是数据表的自增id,出自一些安全性的考虑,文章篇幅有限,这里就暂不拓展讲了),时间差,Token等信息,并在KV数据库(比如Redis)建立用户编号->Token关系。

  3. 客户端存储用户编号,Token和时间差,再下一次访问服务器的时候,利用访问的URL,用户编号,Token,访问时间(客户端本地时间戳 + 时间差)加密(可以是对称或者非对称加密)形成一个签名(signature),然后再重新拼接url+用户编号+访问时间+签名 去访问服务器。

  4. 服务器接收到客户端请求,首先判断访问时间是否超过当前时间30秒(自定义),如果有则拒绝请求,如果没有,根据用户编号到KV数据库获取Token,然后进行同样的算法得出签名与客户端传递的签名相比对,如果一样则方可通过,否则拒绝。

这种方案的好处:
  1. 让访问链接具备时效性,为什么要去判断这个访问时间?就是防止链接被重用(URL重放),避免接口被抓包获取,被重复使用,这样一来,就算完整的链接暴露了,这个链接的有效访问时间也只有30秒。

  2. 解决了用户单点登录,修改密码,登出的问题,因为Token是由服务器维护的,在用户信息以上操作的时候,我们对Token进行刷新即可,利用旧的Token再生成签名自然校验就不通过了。

关于客户端Token的保存

这里针对客户端而外再提一下关于Token的保存,毕竟客户端存在被反编译的风险的,因为本人对Android开发比较熟悉,这里就拿Android来举例:

  1. 做好代码的混淆和加固是最根本的。

  2. 不要把这些比较重要的信息写在配置文件,缓存文件,数据库里等,因为Android系统是可以通过Root拿到高级权限的。

  3. 我们可以把Token的生成策略存放so文件中,然后用JNI去调用,因为so文件是由C++编写的,反编译难度大大增大,可能有人会说那我拷贝so文件,不就依旧可以在其他地方使用了吗?所以我们也可以采取点其他方法,比如添加包名的验证等。

好了,文章到这里就收尾了,以上是本人对服务端身份校验的一些理解与思考,由于篇幅的限制,有些地方没有办法讲的太细,也肯定存在不足的地方(比如在登录,发送验证码,下订单等涉及到安全操作的时候使用HTTPS),希望有更好方案的朋友一起交流。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容