【接口自动化】-11-接口加密签名 全局设置封装
一、RSA加密
⭐ 先了解 RSA 加密的基本概念
RSA 是一种非对称加密算法,需要一对密钥:
- 公钥(public key):用于加密数据,可公开(如放在服务器上供客户端使用)。
- 私钥(private key):用于解密数据,必须保密(只有服务器持有)。
这段代码的作用是 “用公钥加密数据”,加密后的内容只能用对应的私钥解密,保证数据传输安全(如密码、token 等敏感信息)。
def rsa_encode(self, data):# 1. 加载公钥(用于加密)with open("../hotload/public.pem") as f:pubkey = rsa.PublicKey.load_pkcs1(f.read().encode()) # 2. 转换为 UTF-8 编码的字节data = str(data).encode("utf-8") # 3. 用公钥加密,得到字节数据byte_value = rsa.encrypt(data, pubkey) # 4. Base64 编码(将字节转为可读字符串)rsa_value = base64.b64encode(byte_value).decode("utf-8") return rsa_value
1. 加载公钥(第 3-4 行)
with open("../hotload/public.pem") as f:pubkey = rsa.PublicKey.load_pkcs1(f.read().encode())
- 作用:从文件中读取 RSA 公钥,用于后续加密。
- 细节:
../hotload/public.pem
是公钥文件的路径(.pem
是常见的密钥文件格式)。f.read()
读取文件内容(字符串),.encode()
转为字节(因为加密函数需要字节类型)。rsa.PublicKey.load_pkcs1(...)
是rsa
库的方法,将读取的字节数据解析为 “公钥对象”(供加密使用)。
2. 处理原始数据(第 6 行)
data = str(data).encode("utf-8")
- 作用:将输入的原始数据(可能是字符串、数字等)转换为 RSA 加密要求的 “UTF-8 编码字节”。
- 细节:
str(data)
确保数据先转为字符串(避免数字、布尔值等类型直接加密报错)。.encode("utf-8")
将字符串转为字节(RSA 加密算法只能处理字节数据)。
3. RSA 加密(第 8 行)
byte_value = rsa.encrypt(data, pubkey)
- 作用:用公钥对处理后的字节数据进行加密。
- 细节:
rsa.encrypt(数据, 公钥)
是rsa
库的加密方法,返回加密后的字节数据(无法直接阅读和传输)。- 加密后的结果
byte_value
是一串乱码字节(如b'\x02\x1a...'
),不适合在接口中直接传递。
4. Base64 编码(第 10 行)
rsa_value = base64.b64encode(byte_value).decode("utf-8")
- 作用:将加密后的字节数据转换为 “可阅读、可传输的字符串”。
- 细节:
base64.b64encode(byte_value)
将字节数据转为 Base64 编码的字节(如b'VGhpcyBpcyBhIHRlc3Q='
)。.decode("utf-8")
将 Base64 字节转为字符串(如'VGhpcyBpcyBhIHRlc3Q='
),方便在接口参数中传递。
5. 返回结果(第 12 行)
return rsa_value
- 最终返回的是 “RSA 加密后再 Base64 编码的字符串”,可以安全地在网络中传输。
⭐ 举例:实际加密效果
假设输入数据是字符串 "mypassword123"
,执行函数后:
- 公钥从
public.pem
中加载成功。 - 数据转为字节:
b'mypassword123'
。 - RSA 加密后得到字节:
b'\x02\x1a\x03...'
(乱码)。 - Base64 编码后得到字符串:
'QZ5fD...x2aA=='
(可传输)。
接收方用对应的私钥解密后,能还原出原始数据 "mypassword123"
。
⭐ 为什么要这样设计?
- RSA 加密保证安全性:只有持有私钥的一方才能解密,防止数据被窃取后泄露。
- Base64 编码解决传输问题:RSA 加密结果是字节,无法直接在接口参数(通常要求字符串)中传输,Base64 能将字节转为安全的字符串。
- 通用性:将加密逻辑封装为函数,可在测试用例中通过
${rsa_encode(数据)}
直接调用(如之前学习的 YAML 用例)。
⭐ 总结
这段代码的核心流程是:
加载公钥 → 数据转为字节 → RSA加密 → Base64编码 → 返回字符串
作用是将敏感数据(如密码)通过 RSA 加密,转换为可传输的字符串,确保接口传输过程中的安全性。理解后,你可以:
- 替换
public.pem
路径,使用自己的公钥文件。 - 在接口测试用例中调用该函数,加密需要保护的参数。
- 结合私钥解密代码(通常在服务器端),验证加密是否正确。
二、加密参数处理
⭐ 功能模块划分
模块 | 作用 |
---|---|
debug_talk.py | 实现 加密工具函数(MD5、Base64、RSA 加密),供 YAML 用例调用 |
*.yaml (如 md5.yaml ) | 定义 接口用例(请求信息、加密参数占位符、断言) |
requests_util.py | 实现 请求发送核心逻辑(send_all_requests 类 / 函数,处理加密参数、发送请求) |
⭐ debug_talk.py
:加密工具函数解析
1. MD5 加密(md5_encode
)
def md5_encode(self, data):# 1. 转换为 UTF-8 编码的字节data = str(data).encode("utf-8") # 2. MD5 加密(哈希算法),生成十六进制结果md5_value = hashlib.md5(data).hexdigest() return md5_value
- 作用:将输入数据(如
admin
)转为 MD5 加密字符串(如21232f297a57a5a743894a0e4a801fc3
)。
2. Base64 加密(base64_encode
)
def base64_encode(self, data):# 1. 转换为 UTF-8 编码的字节data = str(data).encode("utf-8") # 2. Base64 加密,再解码为字符串base64_value = base64.b64encode(data).decode("utf-8") return base64_value
- 作用:将输入数据(如
admin
)转为 Base64 编码字符串(如YWRtaW4=
)。
3. RSA 加密(rsa_encode
)
def rsa_encode(self, data):# 1. 加载公钥(用于加密)with open("../hotload/public.pem") as f:pubkey = rsa.PublicKey.load_pkcs1(f.read().encode()) # 2. 转换为 UTF-8 编码的字节data = str(data).encode("utf-8") # 3. 用公钥加密,得到字节数据byte_value = rsa.encrypt(data, pubkey) # 4. Base64 编码(将字节转为可读字符串)rsa_value = base64.b64encode(byte_value).decode("utf-8") return rsa_value
- 作用:用 RSA 公钥加密数据,再通过 Base64 编码转为字符串(RSA 加密结果是字节,需 Base64 转成接口可传的字符串)。
⭐ *.yaml
:接口用例定义
以 md5.yaml
为例:
feature: 加密模块
story: md5加密接口
title: md5加密接口
request:method: posturl: http://101.34.221.219:5000/md5logindata:username: ${md5_encode(admin)} # 调用md5_encode加密password: ${md5_encode(123)} # 调用md5_encode加密
validate:equals:断言状态码为200: [200, status_code]
- 关键:
${md5_encode(admin)}
是函数占位符,框架会自动调用debug_talk.py
中的md5_encode
函数,将结果替换到请求参数中。
⭐ requests_util.py
:请求发送核心逻辑
class RequestUtil:sess = requests.Session() # 会话对象,保持cookie等def send_request(self, **kwargs):# 处理加密参数(框架会自动识别${函数名(...)}并调用对应加密函数)# 此处省略“解析YAML中函数占位符并替换为加密结果”的逻辑(需结合框架上下文)# 发送请求res = self.sess.request(**kwargs) # 用requests发送请求,**kwargs是请求参数(method/url/data等)# 处理响应(日志记录)if "json" in res.headers.get("Content-Type", ""):logger.info(res.json()) # 响应是JSON,记录JSON内容else:logger.info(res.text) # 响应非JSON,记录文本内容return res
headers.get("Content-Type", "")
是字典的get
方法,作用是:- 尝试获取响应头中
Content-Type
字段的值(这个字段用于说明响应内容的格式)。 - 如果
Content-Type
不存在,就返回默认值""
(空字符串),避免报错。
- 尝试获取响应头中
- 作用:
- 接收 YAML 中定义的请求参数(已替换加密后的参数)。
- 发送 HTTP 请求,获取响应。
- 根据响应类型(JSON / 非 JSON)记录日志,便于调试。
⭐ 与之前代码的联系(数据驱动 + 加密的整合)
-
数据驱动与加密的结合:
之前的parametrize
实现了 “多组数据驱动单接口”,现在在此基础上,用例中的参数可以是 “加密后的数据”(通过debug_talk.py
的函数生成)。 -
请求发送的统一入口:
之前的send_all_requests
负责 “发送普通请求”,现在升级为 “发送带加密参数的请求”—— 框架会先解析 YAML 中的加密占位符(如${md5_encode(admin)}
),调用debug_talk.py
生成加密值,再传递给send_request
发送。
⭐ 整体流程总结
- 编写用例:在 YAML 中用
${加密函数(参数)}
定义需要加密的参数。 - 解析用例:框架识别
${...}
占位符,调用debug_talk.py
中的对应加密函数,生成加密后的值。 - 发送请求:
requests_util.py
接收替换后的参数,发送 HTTP 请求。 - 处理响应:记录响应日志,完成接口测试。
三、接口签名
首先,“签名” 你可以理解成给数据盖个 “防伪章”。为啥要盖呢?就像你寄贵重快递,怕路上被人偷偷拆开换了东西,所以搞个只有你和收件方知道的 “暗号章”,收件方一看章对,就知道东西没被动过。接口签名也是这作用,防止别人在网络传输里篡改你要发的数据。
第一步:挑出要盖印的 “材料”(选参数)
要发的参数里,空的、没用的就别要了(比如有的参数值是空,就不参与签名),然后把剩下有用的参数,按照参数名的字母顺序排好队。比如参数有a
“c
“m
这些,就按字母顺序排,排好后用 “key=value&key=value
” 这种格式连起来,变成一个字符串,叫stringA
。
第二步:加上 “秘密暗号”(加密钥)
把第一步弄好的stringA
,再加上一个只有你和接口提供方知道的 “秘密字符串”(叫key
,就像只有你俩知道的暗号),拼成新的字符串stringSignTemp
。
第三步:生成 “防伪章”(算 MD5 并大写)
用一种叫 MD5 的算法,把stringSignTemp
变成一串固定长度的字符,然后把这串字符全改成大写,这就是最终的 “签名”(sign
)啦。
最后咋用?
把这个sign
放到请求里发过去,接口那边收到后,会用同样的步骤(选参数、排序、加key
、算 MD5 大写)也生成一个sign
,然后和你发的对比。要是一样,就说明数据没被篡改,是合法请求;不一样,就说明数据被人动过,接口就会拒绝。
- 首先列出了 a=dorun、c=login、m=u、backurl=xxx、username=baili、password=baili123 这些参数。
- “stringA = a=dorun&c=login&m=u&backurl=xxx”:按照第一张图里的第一步,把这些参数按照参数名 ASCII 码从小到大排序后,用 URL 键值对格式拼接成了 stringA。
- “stringSignTemp = a=dorun&c=login&m=u&backurl=xxx&key=?”:在 stringA 的基础上拼接上 key(这里用?表示,实际应该是具体的 key 值),得到 stringSignTemp。
- “md5_value = md5(stringSignTemp)”:对 stringSignTemp 进行 MD5 运算,得到 md5_value。
- “sign = md5_value.upper()”:把 md5_value 转换成大写,得到 sign。
- sign 的值一般是放到请求头里面。
四、setting.py
# 数据库连接配置
db_username = "sdm723416659"
db_password = "Msjy123456"
db_database = "sdm723416659_db"
db_host = "sdm723416659.my3w.com"
db_port = 3306# 全局公共请求参数
global_args = {"application": "app","application_client_type": "h5"
}# 接口自动化中间变量存储文件
extract_name = "extracts.yaml"# 测试报告配置
allure_project_name = "金融项目接口自动化报告"
1. conn_database
方法用于连接数据库。连接数据库时需要的用户名(user
)、密码(password
等参数,没有直接写死在代码里,而是通过setting.db_username
这种方式从setting.py
文件中获取。
好处:如果数据库信息变了,比如换了密码,只改setting.py
里的db_password
就行,不用改这个工具类的代码。
2. global_args
是所有接口请求都需要带的公共参数,比如"application": "app"
,所有接口都会用到这两个参数。send_all_request
方法里有一行kwargs["params"].update(setting.global_args)
,意思是把setting.py
里的global_args
公共参数,自动添加到每个接口请求的参数里。
好处:如果所有接口的公共参数需要改(比如把"h5"
改成"pc"
),只改setting.py
里的global_args
,不用每个接口都改一遍。
3.extract_name = "extracts.yaml"
:规定了存储接口之间传递的中间变量的文件名,比如一个接口返回的 token 需要给另一个接口用,就存在这个文件里。
好处:这样提取到setting配置文件,这个文件的名字可以随便起都行。extract_name = "cyf.yaml"也行
4.allure_project_name
:规定了生成的测试报告的项目名称,这样报告的标题就统一由这里控制。
总结:setting.py
就像一个 “中央配置中心”,把原来散落在各个代码文件里的固定值(比如数据库密码、公共参数)都集中到这里,改配置时只用动这一个文件,极大减少了改代码的工作量。
然后,我们平时只要维护setting和debug_util这两个文件和新增编写yaml就行啦。这个框架几乎就完成啦。