Skip to content

鉴权体系说明 ​

文档说明 ​

本规范为全接口统一鉴权标准,平台所有对外提供的业务接口均采用 AKSK Bearer 鉴权方案。

重要约定 ​

  1. 对接方要求:仅支持第三方后端服务(Node.js/Java/Python/Go 等服务端调用)
  2. 安全要求:禁止前端浏览器直接存储 SK,密钥必须存放于服务端环境变量或配置文件
  3. 鉴权实现:鉴权逻辑完全基于服务端源码实现,签名、校验规则全局统一

一、基础通用规范 ​

接口通用基础规则 ​

  • 接口调用Base URL:https://neolink.com/backend/
规则项说明
鉴权覆盖所有接口统一鉴权,无免登白名单接口
HTTP 方法支持 GET/POST/PUT/DELETE 任意 HTTP Method
Content-Type统一为 application/json
鉴权位置鉴权信息固定放在 HTTP 请求头 Authorization

AK/SK 凭证说明 ​

字段类型用途安全要求
AK (Access Key)公钥标识标识调用方用户身份,参与签名计算可随请求链路打印日志
SK (Secret Key)私钥密钥HMAC-SHA256 签名密钥,校验核心凭证严禁明文传输、前端存储、提交代码仓库,仅后端配置/环境变量读取

测试示例凭证 ​

⚠️ 仅供参考,不可直接使用,AKSK在秘钥管理创建获取

AK: rak_if7r3r5rr2t1inhutldf
SK: rsk_tw4tfhidw9tuqu6r4vbgklmprp0xwrhr

二、鉴权核心:Token 生成规范 ​

Payload 结构体定义 ​

Token 生成的签名载体结构(字段大小写严格区分):

json
{
  "Login": "user-test",
  "Reseller": "re-test",
  "Ts": 1782965433,
  "Nonce": "Xs92dF7s3kGd29sL"
}

Payload 字段详细说明 ​

字段类型必填约束规则
Loginstring是待操作的用户账号/手机号,该用户必须归属当前 AK 对应的用户
Resellerstring否代理商编码,与 AK 归属代理商一致;无代理商的直客业务传空字符串 ""
Tsint64是Unix 秒级时间戳;服务端校验客户端时间与服务端时间差值 ≤300s,超出直接拒绝
Noncestring是密码学安全随机串,长度 8~128 位,仅大小写字母 + 数字;每次接口请求必须生成全新值,重复 Nonce 判定为重放攻击拦截

Token 完整生成步骤(顺序不可颠倒) ​

步骤 1:生成请求临时参数 ​

  • 生成 16 位随机 Nonce(推荐长度)
  • 获取当前系统时间,转换为秒级时间戳
    • JavaScript/TypeScript:Math.floor(Date.now() / 1000)
    • 其他语言:使用对应的系统时间戳方法

步骤 2:序列化 Payload ​

将 Payload 对象转为标准 JSON 字符串,无多余空格、换行:

{"Login":"user-test","Reseller":"re-test","Ts":1782965433,"Nonce":"Xs92dF7s3kGd29sL"}

步骤 3:URL-Safe Raw Base64 编码 ​

等价 Go 标准库 base64.RawURLEncoding,转换规则:

  1. 执行标准 Base64 编码
  2. 字符替换:+ → -,/ → _
  3. 删除末尾所有填充符号 =

示例输出:

eyJMb2dpbiI6InVzZXItdGVzdCIsIlJlc2VsbGVyIjoicmUtdGVzdCIsIlRzIjoxNzgyOTY1NDMzLCJOb25jZSI6IlhzOTJkRjdzM2tHZDI5c0wifQ

步骤 4:拼接签名原文 ​

signSource = {AK}.{PayloadBase64字符串}

示例:
signSource = rak_if7r3r5rr2t1inhutldf.eyJMb2dpbiI6InVzZXItdGVzdCIsIlJlc2VsbGVyIjoicmUtdGVzdCIsIlRzIjoxNzgyOTY1NDMzLCJOb25jZSI6IlhzOTJkRjdzM2tHZDI5c0wifQ

步骤 5:HMAC-SHA256 计算签名 ​

  • 密钥:SK 明文
  • 哈希算法:SHA256
  • 输出格式:小写十六进制字符串(大写会验签失败)

步骤 6:拼接完整 AK-SK Token ​

token = aksk_{AK}.{PayloadBase64}.{HMAC签名Hex}

示例:
token = aksk_rak_if7r3r5rr2t1inhutldf.eyJMb2dpbiI6InVzZXItdGVzdCIsIlJlc2VsbGVyIjoicmUtdGVzdCIsIlRzIjoxNzgyOTY1NDMzLCJOb25jZSI6IlhzOTJkRjdzM2tHZDI5c0wifQ.a7f29d0c8e1b356xxxx

💡 前缀 aksk_ 固定不可修改

步骤 7:组装鉴权请求头 ​

Authorization: Bearer {token}

三、通用请求头规范 ​

所有接口统一遵循以下请求头规范:

Header Key是否必填说明
Authorization是Bearer + 上文生成的完整 aksk token
Content-Type是固定 application/json
X-Accept-Language可选传 zh 时,业务错误 message 返回中文文案
Referer可选代理商前端域名,用于平台统计代理商来源;无则不传(代理商用户必传,个人用户非必传)

完整请求头示例 ​

http
Authorization: Bearer aksk_rak_if7r3r5rr2t1inhutldf.eyJMb2dpbiI6Imd4eS10ZXN0IiwiUmVzZWxsZXIiOiJjbXE6IiwiVHM6IjE3ODI5NjU0MzMiLCJOb25jZSI6IlhzOTJkRjdzM2tHZDI5c0wifQ.a7f29d0c8e1b356xxxx
Content-Type: application/json
X-Accept-Language: zh
Referer: https://re-test.example.com/

四、通用返回格式 ​

所有接口 HTTP 状态码统一返回 200,业务成功/失败通过 JSON 内 success 字段区分:

json
{
  "success": true,
  "message": "操作描述文案",
  "data": {}
}

鉴权相关标准错误返回 ​

AK/SK 认证阶段报错(HTTP 401) ​

错误信息触发条件阶段
Invalid AK/SK token formatToken 格式不是 aksk_<ak>.<payload>.<sig> 三段Token 解析
Signature verification failedAK 不存在或签名验证失败凭据查询 / 签名验证
Invalid or expired timestamp时间戳不在允许窗口(默认 ±300 秒)内时间戳校验
Replay request detected同一 AK+Nonce 在 TTL 内重复提交防重放检查
User not foundpayload.login 匹配的用户不存在用户查询

五、对接常见坑点 ⚠️ ​

1. 时间戳单位错误 ​

问题:JavaScript/TypeScript 原生 Date.now() 是毫秒

解决:必须除以 1000 向下取整转为秒,否则直接判定时间戳非法

javascript
// ❌ 错误
const ts = Date.now();

// ✅ 正确
const ts = Math.floor(Date.now() / 1000);

2. Base64 格式不标准 ​

问题:未替换 +/-、未删除末尾 = 填充符

解决:严格按照 URL-Safe Raw Base64 编码规则处理

3. Payload 字段大小写错误 ​

问题:使用小写 login/reseller/ts/nonce 会导致服务端 JSON 解析错乱

解决:必须使用大小写字段名:Login/Reseller/Ts/Nonce

4. 签名输出大写 Hex ​

问题:HMAC 摘要输出为大写字符串

解决:HMAC 摘要必须转小写,大写字符串无法通过服务端校验

python
# ❌ 错误
signature = hmac.new(sk.encode(), sign_source.encode(), hashlib.sha256).hexdigest().upper()

# ✅ 正确
signature = hmac.new(sk.encode(), sign_source.encode(), hashlib.sha256).hexdigest()

5. Nonce 复用 ​

问题:同一随机串短时间内重复请求,触发重放拦截

解决:每次请求必须全新生成 Nonce

6. SK 明文泄露风险 ​

问题:SK 被打印日志、前端存储、提交代码仓库

解决:SK 仅允许存放于后端服务环境变量或加密配置


六、代码示例 ​

python
import hmac
import hashlib
import base64
import json
import time
import random
import string
import requests
from urllib.parse import quote

class AKSKAuth:
    def __init__(self, ak, sk):
        self.ak = ak
        self.sk = sk
    
    def generate_nonce(self, length=16):
        """生成指定长度的随机 Nonce"""
        chars = string.ascii_letters + string.digits
        return ''.join(random.choice(chars) for _ in range(length))
    
    def get_timestamp(self):
        """获取当前秒级时间戳"""
        return int(time.time())
    
    def build_payload(self, login, reseller=""):
        """构建 Payload 对象"""
        return {
            "Login": login,
            "Reseller": reseller,
            "Ts": self.get_timestamp(),
            "Nonce": self.generate_nonce()
        }
    
    def encode_payload(self, payload):
        """将 Payload 转为 URL-Safe Raw Base64 编码"""
        json_str = json.dumps(payload, separators=(',', ':'), ensure_ascii=False)
        base64_encoded = base64.b64encode(json_str.encode()).decode()
        url_safe = base64_encoded.replace('+', '-').replace('/', '_').rstrip('=')
        return url_safe
    
    def generate_signature(self, ak, payload_base64):
        """生成 HMAC-SHA256 签名"""
        sign_source = f"{ak}.{payload_base64}"
        signature = hmac.new(
            self.sk.encode(),
            sign_source.encode(),
            hashlib.sha256
        ).hexdigest()
        return signature, sign_source
    
    def generate_token(self, login, reseller=""):
        """生成完整的 AK/SK Token"""
        payload = self.build_payload(login, reseller)
        payload_base64 = self.encode_payload(payload)
        signature, _ = self.generate_signature(self.ak, payload_base64)
        token = f"aksk_{self.ak}.{payload_base64}.{signature}"
        return token
    
    def get_auth_header(self, login, reseller=""):
        """获取完整的 Authorization 请求头"""
        token = self.generate_token(login, reseller)
        return f"Bearer {token}"
    
    def request_with_auth(self, method, url, login, reseller="", **kwargs):
        """使用 AK/SK 认证发送 HTTP 请求"""
        headers = kwargs.get('headers', {})
        headers['Authorization'] = self.get_auth_header(login, reseller)
        headers['Content-Type'] = 'application/json'
        kwargs['headers'] = headers
        response = requests.request(method, url, **kwargs)
        return response

# 使用示例
if __name__ == "__main__":
    AK = "rak_if7r3r5rr2t1inhutldf"
    SK = "rsk_tw4tfhidw9tuqu6r4vbgklmprp0xwrhr"
    auth = AKSKAuth(AK, SK)
    token = auth.generate_token(login="user-test", reseller="re-test")
    print(f"Generated Token: {token}")
    auth_header = auth.get_auth_header(login="user-test", reseller="re-test")
    print(f"Authorization: {auth_header}")
typescript
import axios, { AxiosRequestConfig } from 'axios';
import crypto from 'crypto';

interface PayloadData {
  Login: string;
  Reseller: string;
  Ts: number;
  Nonce: string;
}

class AKSKAuth {
  private ak: string;
  private sk: string;

  constructor(ak: string, sk: string) {
    this.ak = ak;
    this.sk = sk;
  }

  private generateNonce(length: number = 16): string {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let result = '';
    for (let i = 0; i < length; i++) {
      result += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return result;
  }

  private getTimestamp(): number {
    return Math.floor(Date.now() / 1000);
  }

  private buildPayload(login: string, reseller: string = ''): PayloadData {
    return {
      Login: login,
      Reseller: reseller,
      Ts: this.getTimestamp(),
      Nonce: this.generateNonce()
    };
  }

  private encodePayload(payload: PayloadData): string {
    const jsonStr = JSON.stringify(payload);
    const base64Encoded = Buffer.from(jsonStr).toString('base64');
    const urlSafe = base64Encoded
      .replace(/\+/g, '-')
      .replace(/\//g, '_')
      .replace(/=+$/, '');
    return urlSafe;
  }

  private generateSignature(ak: string, payloadBase64: string): { signature: string; signSource: string } {
    const signSource = `${ak}.${payloadBase64}`;
    const signature = crypto
      .createHmac('sha256', this.sk)
      .update(signSource)
      .digest('hex');
    return { signature, signSource };
  }

  public generateToken(login: string, reseller: string = ''): string {
    const payload = this.buildPayload(login, reseller);
    const payloadBase64 = this.encodePayload(payload);
    const { signature } = this.generateSignature(this.ak, payloadBase64);
    const token = `aksk_${this.ak}.${payloadBase64}.${signature}`;
    return token;
  }

  public getAuthHeader(login: string, reseller: string = ''): string {
    const token = this.generateToken(login, reseller);
    return `Bearer ${token}`;
  }

  public async requestWithAuth(
    method: 'GET' | 'POST' | 'PUT' | 'DELETE',
    url: string,
    login: string,
    reseller: string = '',
    data?: any,
    headers?: any
  ) {
    const config: AxiosRequestConfig = {
      method,
      url,
      headers: {
        ...headers,
        'Authorization': this.getAuthHeader(login, reseller),
        'Content-Type': 'application/json'
      }
    };
    if (data) {
      config.data = data;
    }
    return axios(config);
  }
}

// 使用示例
(async () => {
  const AK = 'rak_if7r3r5rr2t1inhutldf';
  const SK = 'rsk_tw4tfhidw9tuqu6r4vbgklmprp0xwrhr';
  const auth = new AKSKAuth(AK, SK);
  const token = auth.generateToken('user-test', 're-test');
  console.log(`Generated Token: ${token}`);
  const authHeader = auth.getAuthHeader('user-test', 're-test');
  console.log(`Authorization: ${authHeader}`);
})();
go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"math/rand"
	"net/http"
	"strings"
	"time"

	"github.com/go-resty/resty/v2"
)

type PayloadData struct {
	Login    string `json:"Login"`
	Reseller string `json:"Reseller"`
	Ts       int64  `json:"Ts"`
	Nonce    string `json:"Nonce"`
}

type AKSKAuth struct {
	AK string
	SK string
}

func (a *AKSKAuth) GenerateNonce(length int) string {
	const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
	b := make([]byte, length)
	for i := range b {
		b[i] = charset[rand.Intn(len(charset))]
	}
	return string(b)
}

func (a *AKSKAuth) GetTimestamp() int64 {
	return time.Now().Unix()
}

func (a *AKSKAuth) BuildPayload(login, reseller string) PayloadData {
	return PayloadData{
		Login:    login,
		Reseller: reseller,
		Ts:       a.GetTimestamp(),
		Nonce:    a.GenerateNonce(16),
	}
}

func (a *AKSKAuth) EncodePayload(payload PayloadData) (string, error) {
	jsonBytes, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}
	urlSafe := base64.RawURLEncoding.EncodeToString(jsonBytes)
	return urlSafe, nil
}

func (a *AKSKAuth) GenerateSignature(ak, payloadBase64 string) (string, string) {
	signSource := fmt.Sprintf("%s.%s", ak, payloadBase64)
	h := hmac.New(sha256.New, []byte(a.SK))
	h.Write([]byte(signSource))
	signature := fmt.Sprintf("%x", h.Sum(nil))
	return signature, signSource
}

func (a *AKSKAuth) GenerateToken(login, reseller string) (string, error) {
	payload := a.BuildPayload(login, reseller)
	payloadBase64, err := a.EncodePayload(payload)
	if err != nil {
		return "", err
	}
	signature, _ := a.GenerateSignature(a.AK, payloadBase64)
	token := fmt.Sprintf("aksk_%s.%s.%s", a.AK, payloadBase64, signature)
	return token, nil
}

func (a *AKSKAuth) GetAuthHeader(login, reseller string) (string, error) {
	token, err := a.GenerateToken(login, reseller)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("Bearer %s", token), nil
}

// 使用示例
func main() {
	AK := "rak_if7r3r5rr2t1inhutldf"
	SK := "rsk_tw4tfhidw9tuqu6r4vbgklmprp0xwrhr"
	auth := &AKSKAuth{AK: AK, SK: SK}
	token, err := auth.GenerateToken("user-test", "re-test")
	if err != nil {
		fmt.Printf("Error generating token: %v\n", err)
		return
	}
	fmt.Printf("Generated Token: %s\n", token)
	authHeader, err := auth.GetAuthHeader("user-test", "re-test")
	if err != nil {
		fmt.Printf("Error getting auth header: %v\n", err)
		return
	}
	fmt.Printf("Authorization: %s\n", authHeader)
}
java
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import okhttp3.*;
import org.apache.commons.codec.binary.Hex;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Random;

public class AKSKAuth {
    private String ak;
    private String sk;
    private Gson gson;

    public AKSKAuth(String ak, String sk) {
        this.ak = ak;
        this.sk = sk;
        this.gson = new Gson();
    }

    private String generateNonce(int length) {
        String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            sb.append(chars.charAt(random.nextInt(chars.length())));
        }
        return sb.toString();
    }

    private long getTimestamp() {
        return System.currentTimeMillis() / 1000;
    }

    private JsonObject buildPayload(String login, String reseller) {
        JsonObject payload = new JsonObject();
        payload.addProperty("Login", login);
        payload.addProperty("Reseller", reseller);
        payload.addProperty("Ts", getTimestamp());
        payload.addProperty("Nonce", generateNonce(16));
        return payload;
    }

    private String encodePayload(JsonObject payload) {
        String jsonStr = gson.toJson(payload);
        String base64Encoded = Base64.getEncoder().encodeToString(jsonStr.getBytes(StandardCharsets.UTF_8));
        return base64Encoded
                .replace("+", "-")
                .replace("/", "_")
                .replaceAll("=+$", "");
    }

    private String generateSignature(String ak, String payloadBase64) throws Exception {
        String signSource = ak + "." + payloadBase64;
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKey = new SecretKeySpec(sk.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(secretKey);
        byte[] signature = mac.doFinal(signSource.getBytes(StandardCharsets.UTF_8));
        return Hex.encodeHexString(signature);
    }

    public String generateToken(String login, String reseller) throws Exception {
        JsonObject payload = buildPayload(login, reseller);
        String payloadBase64 = encodePayload(payload);
        String signature = generateSignature(ak, payloadBase64);
        return String.format("aksk_%s.%s.%s", ak, payloadBase64, signature);
    }

    public String getAuthHeader(String login, String reseller) throws Exception {
        String token = generateToken(login, reseller);
        return "Bearer " + token;
    }

    public static void main(String[] args) {
        String AK = "rak_if7r3r5rr2t1inhutldf";
        String SK = "rsk_tw4tfhidw9tuqu6r4vbgklmprp0xwrhr";
        AKSKAuth auth = new AKSKAuth(AK, SK);
        try {
            String token = auth.generateToken("user-test", "re-test");
            System.out.println("Generated Token: " + token);
            String authHeader = auth.getAuthHeader("user-test", "re-test");
            System.out.println("Authorization: " + authHeader);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
bash
#!/bin/bash

AK="rak_if7r3r5rr2t1inhutldf"
SK="rsk_tw4tfhidw9tuqu6r4vbgklmprp0xwrhr"
LOGIN="user-test"
RESELLER="re-test"

# 生成时间戳(秒级)
TIMESTAMP=$(date +%s)

# 生成随机 Nonce(16 位)
NONCE=$(openssl rand -hex 8 | tr -d '\n')

# 构建 Payload JSON
PAYLOAD="{\"Login\":\"$LOGIN\",\"Reseller\":\"$RESELLER\",\"Ts\":$TIMESTAMP,\"Nonce\":\"$NONCE\"}"

# Base64 URL-Safe 编码 Payload
PAYLOAD_BASE64=$(echo -n "$PAYLOAD" | base64 | tr '+/' '-_' | tr -d '=')

# 拼接签名原文
SIGN_SOURCE="$AK.$PAYLOAD_BASE64"

# 计算 HMAC-SHA256 签名(小写 Hex)
SIGNATURE=$(echo -n "$SIGN_SOURCE" | openssl dgst -sha256 -hmac "$SK" -hex | cut -d' ' -f2)

# 组装完整 Token
TOKEN="aksk_$AK.$PAYLOAD_BASE64.$SIGNATURE"

# 构建 Authorization 请求头
AUTH_HEADER="Bearer $TOKEN"

echo "Token: $TOKEN"
echo "Authorization: $AUTH_HEADER"

# 发送请求示例
curl -X POST https://api.example.com/v1/chat/completions \
  -H "Authorization: $AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -H "X-Accept-Language: zh" \
  -d '{
    "model": "gpt-4",
    "messages": [
      {"role": "user", "content": "Hello"}
    ]
  }'

下一步 ​