API接口安全
流程
1.客户端登录后从后端拿到token
2.客户端生成时间戳
3.客户端将所有的业务参数进行签名
,包括时间戳
和token
4.将token
,签名
,时间戳
,业务参数
发送到服务器端
5.服务器端拿到数据首先判断时间戳是否超出有效时间范围(3分钟)
再判断token
是否有效
再根据前端传来的参数进行同样的流程生成签名
两者签名进行比对,如果相同且是第一次请求则将签名存入redis
中,并设置过期时间(3分钟)
如果redis
已经存在该签名则说是二次请求
,应该拒绝请求。
如果两者签名不同则返回签名错误。
服务器端代码
- 生成签名
package hello
import (
"fmt"
"github.com/gogf/gf/crypto/gmd5"
"sort"
"strings"
)
// 生成签名
func GenerateSign(params map[string]interface{}, token string) string {
// 1.遍历map中的key加入列表中
var keys []string
for k := range params{
keys = append(keys, k)
}
// 2.对列表中的key按字符串排序
sort.Strings(keys)
// 3.拼接字符串
stringList := []string{}
for _, key := range keys {
stringList = append(stringList, fmt.Sprintf("%s%v", key, params[key]))
}
stringA := strings.Join(stringList, "")
// 4.拼接token(首尾拼接token)
stringB := fmt.Sprintf("%s%s%s", token, stringA, token)
// 5.md5加密, 转大写
encrypt, _ := gmd5.Encrypt(stringB)
sign := strings.ToUpper(encrypt)
return sign
}
- 接口签名验证中间件
// 接口签名验证中间件
func CORSAuthMiddleware(r *ghttp.Request) {
r.Response.CORSDefault()
// 获取前端传来的token
token := r.GetHeader("Authorization")
// 验证token是否有效
secret := g.Cfg().GetBytes("web.secret")
_, err := gojwt.ParseToken(token, secret)
RecvError(err, r)
// 接收前端参数
type Params struct{
Sign string `v:"required#sign签名不能为空" p:"sign"`
Timestamp int64 `v:"min:1#请传入正常的时间戳" p:"timestamp"`
}
var params = new(Params)
Validate(¶ms, r)
// 判断时间前端传来的时间戳是否超出有效时间范围(3分钟)
serverTimestamp := time.Now().Unix()
clientTimestamp := params.Timestamp
if(serverTimestamp - clientTimestamp) > 180 {
r.Response.WriteJsonExit(RenderError("请求超时"))
}
// 判断redis是否已存在该sign,如果已存在则说明该请求之前访问过,则让该请求无效
v, err := g.Redis().DoVar("EXISTS", params.Sign)
RecvError(err, r)
if v.Int() != 0 {
r.Response.WriteJsonExit(RenderSuccess("", "不允许二次请求"))
}
// 前端传来的数据进行签名
data := r.GetMap()
delete(data, "sign") // sign不参与生成签名
sign := GenerateSign(data, token)
// 前后端签名对比
if(sign != params.Sign) {
r.Response.WriteJsonExit(RenderError("签名错误"))
}
// 签名一致存入redis中
g.Redis().DoVar("SET", sign, sign)
// 设置redis中的签名过期时间为180秒
g.Redis().DoVar("EXPIRE", sign, 180)
r.Middleware.Next()
}
前端请求代码
import requests, hashlib, time
# 登录获取token
def login() -> str:
url = 'http://127.0.0.1:8199/login'
resp = requests.post(url, data={'username': 'admin', 'password': '111111'})
token = resp.json().get('data').get('token')
return token
# 签名生成
def GenerateSign(params: dict, token: str) -> str:
# 对字典参数key进行排序
sortKey = sorted(params)
# 拼接key和val
stringA = ''
for item in sortKey:
stringA += f'{item}{params[item]}'
# 拼接token(首尾拼接token)
stringB = f'{token}{stringA}{token}'
# md5加密stringB
a = hashlib.md5()
a.update(stringB.encode())
# 转大写
sign = a.hexdigest().upper()
return sign
# 需要登录后才能访问的
def hello():
url = 'http://127.0.0.1:8199/hello'
token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.6ZMu-JFD23Yg2zagr7SyLW0iVYq8xfINqtx9h6Mqi6g'
params = {'name': 'wang','weight': 75, 'age': 24}
params['timestamp'] = 1597744205
params['sign'] = "A5E531E4D01B302FAEC38CEE01D6EFF8"
resp = requests.get(url, params, headers={'Authorization': token})
print(resp.json())
hello()
网友评论