pytest + yaml 框架 -34.接口 sign 签名请求预处理

2023-08-22 11:38:00 浏览数 (2)

前言

一般公司对外的接口都会用到 sign 签名,对不同的客户提供不同的apikey ,这样可以提高接口请求的安全性,避免被人抓包后修改请求参数乱请求。 接口sign签名

一登陆的接口请求为例,如下接口抓包报文信息,其中sign的签名规则如下

  • 第一步,拼接字符串,首先去除sign参数本身,然后去除值是空的参数p3,剩下p2=v2&p1=v1&method=cancel&pn=vn,
  • 然后按参数名字符升序排序,method=cancel&p1=v1&p2=v2&pn=vn.
  • 第二步,然后做参数名和值的拼接,最后得到methodcancelp1v1p2v2pnvn
  • 第三步,在上面拼接得到的字符串后加上验证密钥apikey,我们假设是abc,得到新的字符串methodcancelp1v1p2v2pnvnabc
  • 第四步,然后将这个字符串换为小写进行md5计算,假设得到的是abcdef,这个值即为sign签名值。 注意,计算md5之前请确保接口与接入方的字符串编码一致,如统一使用utf-8编码或者GBK编码,如果编码方式不一致则计算出来的签名会校验失败。
代码语言:javascript复制
POST http://127.0.0.1:8000/api/v3/login HTTP/1.1
User-Agent: Fiddler
Content-Type: application/json
Host: 127.0.0.1:8000
Content-Length: 111

{
    "username": "test",
    "password": "123456",
    "sign": "1aca01806e93bb408041965a817666af"

}

HTTP/1.1 200 OK
Date: Sat, 26 Oct 2019 03:38:31 GMT
Server: WSGIServer/0.2 CPython/3.6.0
Content-Type: application/json
Vary: Accept, Cookie
Allow: POST, OPTIONS
X-Frame-Options: SAMEORIGIN
Content-Length: 109

{"code": 0, "msg": "login success!", "username": "test", "token": "a76ba3b8fcbdff82f6a94e5ad5bf8fb934192e5f"}

实现 sign 签名

在 conftest.py 编写 pre_sign 函数,对请求的 body 部分预处理

代码语言:javascript复制
import hashlib
from pytest_yaml_yoyo import my_builtins

def sign_body(body: dict) -> str:
    """对body 签名"""
    key = "12345678"      # 接口项目的开发提供
    # 去掉sign 和值为空的
    new_body = [''.join(item) for item in body.items() if item[0] != 'sign' and item[1] != '']
    # print(new_body)
    # 做排序
    new_body.sort()
    # 拼接
    # print(new_body)
    str_body = ''.join(new_body)   key
    # print(str_body)
    # md5加密

    def jiamimd5(src: str):
        """md5加密"""
        m = hashlib.md5()
        m.update(src.encode('UTF-8'))
        return m.hexdigest()

    sign = jiamimd5(str_body)
    return sign

def pre_sign(req: dict):
    print(f'请求预处理:{req}')
    sign = sign_body(req.get('json'))
    req['json']['sign'] = sign
    print(f'处理后的req数据:{req}')

my_builtins.pre_sign = pre_sign

pre_sign 函数中的 req 参数 对应yaml 用例中的 request 请求参数,是一个字典类型的数据

test_sign_login.yml 用例内容

代码语言:javascript复制
config:
  name: 登录
  hooks:
    request: ['pre_sign']

test_login:
  name: 登录
  request:
    method: POST
    url: /api/v3/login
    json:
      username: test8
      password: "123456"
  validate:
    - eq: [body.code, 0]

运行用例

代码语言:javascript复制
pytest test_sign_login.yml

运行日志

代码语言:javascript复制
test_sign_login.yml::test_login
---------------------------------------------------- live log call -----------------------------------------------------
2023-06-08 09:45:11 [INFO]: 执行文件-> test_sign_login.yml
2023-06-08 09:45:11 [INFO]: base_url-> http://127.0.0.1:8200
2023-06-08 09:45:11 [INFO]: config variables-> {}
2023-06-08 09:45:11 [INFO]: 运行用例-> test_login
请求预处理:{'method': 'POST', 'url': '/api/v3/login', 'json': {'username': 'test8', 'password': '123456'}}
处理后的req数据:{'method': 'POST', 'url': '/api/v3/login', 'json': {'username': 'test8', 'password': '123456', 'sign': '
65faa7273d552aaedda3abdd1fe5c865'}}
2023-06-08 09:45:11 [INFO]: --------  request info ----------
2023-06-08 09:45:11 [INFO]: yml raw  -->: {'method': 'POST', 'url': '/api/v3/login', 'json': {'username': 'test8', 'passw
ord': '123456', 'sign': '65faa7273d552aaedda3abdd1fe5c865'}}
2023-06-08 09:45:11 [INFO]: method   -->: POST
2023-06-08 09:45:11 [INFO]: url      -->: /api/v3/login
2023-06-08 09:45:11 [INFO]: headers  -->: {'User-Agent': 'python-requests/2.30.0', 'Accept-Encoding': 'gzip, deflate', 'A
ccept': '*/*', 'Connection': 'keep-alive'}
2023-06-08 09:45:11 [INFO]: json     -->: {"username": "test8", "password": "123456", "sign": "65faa7273d552aaedda3abdd1f
e5c865"}
2023-06-08 09:45:11 [INFO]: ------  response info  200 OK ------
2023-06-08 09:45:11 [INFO]: 耗时     <--: 0.207054s
2023-06-08 09:45:11 [INFO]: url      <--: http://127.0.0.1:8200/api/v3/login
2023-06-08 09:45:11 [INFO]: headers  <--: {'Date': 'Thu, 08 Jun 2023 01:45:11 GMT', 'Server': 'WSGIServer/0.2 CPython/3.6
.8', 'Content-Type': 'application/json', 'Allow': 'POST, OPTIONS', 'X-Frame-Options': 'SAMEORIGIN', 'Content-Length': '11
0', 'Vary': 'Cookie'}
2023-06-08 09:45:11 [INFO]: cookies  <--: {}
2023-06-08 09:45:11 [INFO]: raw text <--: {"code": 0, "msg": "login success!", "username": "test8", "token": "045acf05c42
2ad8a40e2309ecb8de830d664e50c"}
2023-06-08 09:45:11 [INFO]: validate 校验内容-> [{'eq': ['body.code', 0]}]
2023-06-08 09:45:11 [INFO]: validate 校验结果-> eq: [0, 0]
2023-06-08 09:45:11 [INFO]: export 导出全局变量:{}
PASSED

从运行结果可以看到,虽然用例里面没有传sign参数,通过hooks中的request 钩子,对请求参数预处理,实现了动态签名自动添加sign参数了。

代码语言:javascript复制
请求预处理:{'method': 'POST', 'url': '/api/v3/login', 'json': {'username': 'test8', 'password': '123456'}}
处理后的req数据:{'method': 'POST', 'url': '/api/v3/login', 'json': {'username': 'test8', 'password': '123456', 'sign': '
65faa7273d552aaedda3abdd1fe5c865'}}

用例中hooks参数使用

前面示例是把hooks参数放到config中, 作用范围是对当前yaml用例文件中的每个请求都会自动调用预处理函数。

代码语言:javascript复制
config:
  name: 登录
  hooks:
    request: ['pre_sign']

如果只是针对单个请求预处理,可以把hooks参数放到接口的请求参数中

代码语言:javascript复制
config:
  name: 登录

test_login:
  name: 登录
  request:
    method: POST
    url: /api/v3/login
    json:
      username: test8
      password: "123456"
    hooks:
      request: ['pre_sign']
  validate:
    - eq: [body.code, 0]

这样hooks的作用范围就只针对单个接口请求了。

0 人点赞