我的应用场景是这样的:
用户进行某项操作,需要先验证,验证通过完成操作创建记录。服务器端先将验证码发送到客户端,客户端将数据连同用户输入的验证码一起发回,和服务器端保存的验证码比对,正确则继续,否则失败。
遇到的问题是: 我在使用 RAILS 5 API,REST API 不建议在服务器端保存状态,默认的 rails 5 api 中 session 都没有启用,所以不能这样暂存验证码;由于用户可能放弃这项操作,不能先创建记录,否则会产生大量冗余数据,所以操作是验证后新建记录,所以不能先将验证码存到数据库中。另外,我在想将验证码做成 token 发送给客户端,客户端再发回来,但感觉也不是很好。
所以,请问,针对这种情况有什么比较好的处理方法?谢谢。
这个问题本质上是:服务端要在若干个 HTTP 请求的工作流中,记住用户相关的验证码数据(验证码) ,方便做后续处理(校验)。这必须把数据临时存放在某个地方。
我能想到的一个解决方法是用 redis 存储验证码,key 设置为用户 id 相关的,value 设置成验证码,再设置一个过期时间(比如 5 分钟)。这样流程就变成:
更进一步,如果服务端需要记住的验证码是 用户 + 设备 唯一的,则可以考虑服务端为每个创建操作生成一个 uuid,这样服务端记录的 key 是 用户 id + 随机 uuid ,其他部分思路相同就不多说了。
关于 JWT,它是 明文 的,把中间一段抠出来用 base64 解码就可以获得原始数据。它的签名算法只是为了防止数据被中间人篡改,而不是加密数据。显然这是不能用于验证码这种敏感数据的。
突然想到个类似 JWT 的办法,不需要额外的存储系统和类库,就是 ActiveSupport::MessageEncryptor
。它也是把数据 base64 化成一段字符串,但它可以用一个 key 去加密。key 你可以存在任何地方,比如 secrets.yml 里。这可能是成本最低的方案了。
@darkbaby123 jwt 是可以加密的。gem jwt 中 encode 方法默认使用 HS256 算法加密。
我最终使用了 Redis 缓存+jwt 的方式。服务器端先将验证码保存在缓存中,key 是 SecureRandom.urlsafe_base64,然后将 key 放入 jwt 加密发送到客户端。客户端请求的时候发送验证码(注:短信/邮件发给他的)和 token,然后用解密出来的 key 从缓存中取出验证码和客户端发来的比较,通过则继续。。。
#10 楼 @harryyoung 重新看了一下,不行。拿官方例子来说:
require 'jwt'
require 'base64'
payload = { data: 'test' }
token = JWT.encode payload, 'my$ecretK3y', 'HS256'
# => "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.ZxW8go9hz3ETCSfxFxpwSkYg_602gOPKearsf6DsxgY"
# 取 xxx.yyy.zzz 的中间段,base64 decode 。无需 secret key
Base64.decode64 token.split('.')[1]
# => "{\"data\":\"test\"}"
encode 不是 encrypt。你说的那些算法只是用来数字签名的,目的是确保 token 携带的数据没有被篡改。
顺便说一句,在浏览器端解码 token 并不是一件困难的事情。比如 auth0 的这个 angular-jwt 。因为它做的就是解码 token 获取数据的事情,所以 API 设计就没有任何 key 让你传。给个 token 它就把数据告诉你。
楼主揭示了一个很重要的事情,REST API 非常依赖缓存。我们的 API 一半以上的时间耗费在查询资源和权限验证了,这个时候就觉得还是 RPC 好,几个 REST 请求完全可以一个 RPC 搞定。但是 RPC 的组合性不好,一个接口只有一个用户。