
前端和后端应该如何优雅的沟通【上】:后端验证
其实我已经相当长时间没有写后端了。最近正在写一个 CRM 的后端,框架是 egg.js[node.js],所以整理了一下我在每个接口之前的中间件做了那些事情
流程
在接口之前(中间件)里做的事情
- 验证数字签名(开发环境中管理)
- 验证 token
- 接口调用日志
- 进入接口
验证数字签名
“数字签名”是个啥
百科上是这样说的:
数字签名(又称公钥数字签名)是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。
通过 http 请求接口的形式来解释数字签名,简单的理解就是通过某一种前后端约定好的算法,将当前请求的数据、发送时的时间戳、接口的路由(可选)以及一个约定的密钥(可能是一个不太长的字符串)通过某一种格式排列并且加密成无意义的字符串。
目的
这样做的目的是,当你的请求被攻击者抓包或者拦截,其中某些参数如果被修改,那么他的签名应该发生变化。但是攻击者并不知道签名的算法,所以在后台验证签名的时候发现这个请求的参数被修改过,就可以拒绝执行接口的程序。从而避免攻击,保证数据的安全。
举个栗子
计算方法
假设你的签名计算方法是:
- 获取发送时的时间戳, 写入请求的 params,
- 将所有参数进行字典排序,并且转换成 JSON 字符串,(记为
{params}
) - 将接口方法放进方括号(记为
[{method}]
) - 将接口地址放进圆括号,(记为
({path})
) - 约定的 secret (记为
{secret}
) - 排序为:
{secret}[{method}]({path}){params}
- 进行一次
md5
加密,并全部转换为大些
栗子本栗
接着假设你要请求的接口是:
[post] /token
@params {
username: 'your username',
password: 'your password'
}
按照你的计算方法进行封装(前后端使用相同的算法),以下有部分为伪代码
// javascript
import md5 from 'md5'
function createSignature (method, path, params, timestamp) {
params = {
...params,
timestamp
} // 此部理论上应该在请求是算好再传进来
params.dictOrder() // 伪代码,此处对 params 做字典排序
const secret = 'your secret' // 此处为前后端约定的密钥
const str = `${secret}[${method}](${path})${params}`
return md5(str).toUpperCase()
}
签名传参
因为 params 已经完整了,再把签名塞入 params 里放在 http 请求的 request body 里就显得没有那么科学
所以我们可以通过 url 参数传递
以上面的例子,请求地址可能是
/token?signature=4D4F6873F7973A3A3FFC02606DAA72E0
验证 token
“Token”和“Session”的区别
以前我们做用户认证靠 session,现在我们用 token
Token 的意思应该是令牌,大概就是用来证明用户身份的东西。就是通过这个 token 来判断用户是谁。token 是一段肉眼看起来没有意义的字符串,他是用你的用户信息通过某种形式加密而来的
JWT - JSON Web Token
JWT 是一个很好用的工具,在几乎所有后端平台都可以使用。同时他也提供了生成 token 和验证 token 的功能。
验证
在进入接口之前,如果是一个需要验证用户身份(简称鉴权)的接口,则需要过一次验证 token 中间件。如果没有通过 token 验证,就可以直接返回无权限了
我是所有的接口过所有的中间件。所以我可以给不需要鉴权的接口开白名单
接口调用日志
每一个接口在调用的时候,应该生成一条调用日志
至于这些日志有什么用?当你用到的时候你就会知道了
但是接口调用日志生成的部分不是拦截功能的中间件,流程走完直接进入下一个流程即可
以上就是我目前为止使用的后端验证的过程
前端的部分,请参考前端和后端应该如何优雅的沟通【下】:前端请求
其他扩展阅读:干点实事儿:api 应该怎么写