API安全

作者: 王哈哈就很棒 | 来源:发表于2020-04-17 07:41 被阅读0次

    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(&params, 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()
    

    相关文章

      网友评论

          本文标题:API安全

          本文链接:https://www.haomeiwen.com/subject/qepsvhtx.html