使用Axios接口请求-签名认证的方式

前言

一般需要登录的系统我们可以使用Token进行身份认证,但是如果不用登录,我们该如何验证接口请求的合理行呢?

一般来说我们可以把参数进行一定方式的加密生成签名,后端直接校验参数和签名是否匹配,匹配才能正常请求。

详情

注意点

要注意以下几点

  • 因为对象的属性的顺序可能不一致,所以我们在生成签名的时候要进行Key排序。
  • 对象可能多层嵌套,我们要把对象拍平。
  • 对象拍平的时候要注意数组的处理。
  • multipart/form-data请求要进行判断。

工具类

/assets/utils/sign_utils.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import md5 from "blueimp-md5";

export function setHeaderSign(config) {
let signObj = {};

const timestamp = Math.floor(Date.now() / 1000); // 秒级时间戳
// console.log("timestamp", timestamp);
Object.assign(signObj, { timestamp: timestamp });
config.headers["timestamp"] = timestamp;

// 获取 URL 参数对象(已解析)
const params = config.params;
if (params) {
// console.log("URL参数:", params);
Object.assign(signObj, params);
}

// 获取 Content-Type 头(转为小写便于比较)
const contentType = (config.headers["Content-Type"] || "").toLowerCase();

// 判断是否是 multipart/formdata
const isMultipart = contentType.includes("multipart/form-data");

// 获取请求体(data 字段包含请求体)
const data = config.data;
if (data && !isMultipart) {
// console.log("请求体:", data);
Object.assign(signObj, data);
}

// console.info("signObj", signObj);
const signStr = getSignStr(signObj);
// console.info("signStr", signStr);
let signature = md5(signStr);
// console.info("signature", signature);
config.headers["signature"] = signature;
}

export function getSignStr(params) {
if (params) {
params = flattenObject(params);
const keys = Object.keys(params).sort();
return keys.map((key) => `${key}=${params[key]}`).join("&");
} else {
return "";
}
}

function flattenObject(obj, parentKey = "", result = {}) {
for (const [key, value] of Object.entries(obj)) {
const newKey = parentKey ? `${parentKey}.${key}` : key;

if (typeof value === "object" && value !== null && !Array.isArray(value)) {
// 递归处理嵌套对象
flattenObject(value, newKey, result);
} else if (value && Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
const element = value[i];
if (typeof element === "object") {
let tempKey = `${newKey}.${i}`;
flattenObject(element, tempKey, result);
} else {
result[`${newKey}.${i}`] = element;
}
}
} else {
// 基本类型值,直接添加到结果对象
result[newKey] = value;
}
}
return result;
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import axios from "axios";
import { setHeaderSign } from "@/assets/utils/sign_utils";

const http = axios.create({
baseURL: window.baseURL,
timeout: 1000 * 10, // 请求超时时间10秒(单位:毫秒)
validateStatus: function (status) {
return status >= 200 && status <= 500; // 默认的
},
headers: {
"Content-Type": "application/json",
},
});

http.interceptors.request.use(
(config) => {
setHeaderSign(config);
return config;
},
(err) => {
return Promise.reject({
code: 1,
msg: err.message || "请求失败",
});
}
);

export default http;

Tip

数据拍平

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function flattenObject(obj, parentKey = "", result = {}) {
for (const [key, value] of Object.entries(obj)) {
const newKey = parentKey ? `${parentKey}.${key}` : key;

if (typeof value === "object" && value !== null && !Array.isArray(value)) {
// 递归处理嵌套对象
flattenObject(value, newKey, result);
} else if (value && Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
const element = value[i];
if (typeof element === "object") {
let tempKey = `${newKey}.${i}`;
flattenObject(element, tempKey, result);
} else {
result[`${newKey}.${i}`] = element;
}
}
} else {
// 基本类型值,直接添加到结果对象
result[newKey] = value;
}
}
return result;
}

数据是这样的

1
2
3
4
5
6
7
8
9
10
11
12
const nestedObj = {
a: 1,
b: { c: 2, d: { e: 3 } },
f: [4, 5],
g: [
{ a: 1, b: 2 },
{ c: 3, d: 4 },
],
};

const flattenedObj = flattenObject(nestedObj);
console.log(flattenedObj);

结果是这样的

1
2
3
4
5
6
7
8
9
10
11
{
"a": 1,
"b.c": 2,
"b.d.e": 3,
"f.0": 4,
"f.1": 5,
"g.0.a": 1,
"g.0.b": 2,
"g.1.c": 3,
"g.1.d": 4
}