package main // 设备登录测试脚本 // 用法: go run scripts/test_device_login.go // 功能: 模拟客户端设备登录,自动加密请求体,解密响应,打印 token import ( "bytes" "crypto/hmac" "crypto/md5" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "sort" "strconv" "strings" "time" "github.com/forgoer/openssl" ) // ==================== 配置区域 ==================== const ( serverURL = "https://tapi.hifast.biz" // 服务地址 securitySecret = "c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx" // device.security_secret identifier = "test-device-script-001" // 设备唯一标识 userAgent = "TestScript/1.0" // UserAgent appId = "android-client" // AppSignature.AppSecrets 中的 key appSecret = "uB4G,XxL2{7b" // AppSignature.AppSecrets 中的 value ) // ==================== AES 工具(与服务端 pkg/aes/aes.go 一致)==================== func generateKey(key string) []byte { hash := sha256.Sum256([]byte(key)) return hash[:32] } func generateIv(iv, key string) []byte { h := md5.New() h.Write([]byte(iv)) return generateKey(hex.EncodeToString(h.Sum(nil)) + key) } func aesEncrypt(plainText []byte, keyStr string) (data string, nonce string, err error) { nonce = fmt.Sprintf("%x", time.Now().UnixNano()) key := generateKey(keyStr) iv := generateIv(nonce, keyStr) dst, err := openssl.AesCBCEncrypt(plainText, key, iv, openssl.PKCS7_PADDING) if err != nil { return "", "", err } return base64.StdEncoding.EncodeToString(dst), nonce, nil } func aesDecrypt(cipherText string, keyStr string, ivStr string) (string, error) { decode, err := base64.StdEncoding.DecodeString(cipherText) if err != nil { return "", err } key := generateKey(keyStr) iv := generateIv(ivStr, keyStr) dst, err := openssl.AesCBCDecrypt(decode, key, iv, openssl.PKCS7_PADDING) return string(dst), err } // ==================== 签名工具(与服务端 pkg/signature 一致)==================== func buildStringToSign(method, path, rawQuery string, body []byte, xAppId, timestamp, nonce string) string { canonical := canonicalQuery(rawQuery) bodyHash := sha256Hex(body) parts := []string{ strings.ToUpper(method), path, canonical, bodyHash, xAppId, timestamp, nonce, } return strings.Join(parts, "\n") } func canonicalQuery(rawQuery string) string { if rawQuery == "" { return "" } pairs := strings.Split(rawQuery, "&") sort.Strings(pairs) return strings.Join(pairs, "&") } func sha256Hex(data []byte) string { h := sha256.Sum256(data) return fmt.Sprintf("%x", h) } func buildSignature(stringToSign, secret string) string { mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(stringToSign)) return hex.EncodeToString(mac.Sum(nil)) } func signedRequest(method, url, rawQuery string, body []byte, token string) (*http.Request, error) { var bodyReader io.Reader if body != nil { bodyReader = bytes.NewReader(body) } req, err := http.NewRequest(method, url, bodyReader) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") if token != "" { req.Header.Set("Authorization", token) // 不带 Bearer 前缀,服务端直接 Parse token } timestamp := strconv.FormatInt(time.Now().Unix(), 10) nonce := fmt.Sprintf("%x", time.Now().UnixNano()) // 提取 path path := req.URL.Path sts := buildStringToSign(method, path, rawQuery, body, appId, timestamp, nonce) sig := buildSignature(sts, appSecret) req.Header.Set("X-App-Id", appId) req.Header.Set("X-Timestamp", timestamp) req.Header.Set("X-Nonce", nonce) req.Header.Set("X-Signature", sig) return req, nil } // ==================== 主逻辑 ==================== func main() { fmt.Println("=== 设备登录测试 ===") fmt.Printf("Server: %s\n", serverURL) fmt.Printf("Identifier: %s\n", identifier) fmt.Println() // 1. 构造原始请求体 payload := map[string]string{ "identifier": identifier, "user_agent": userAgent, } plainBytes, err := json.Marshal(payload) if err != nil { fmt.Printf("[ERROR] marshal payload: %v\n", err) return } fmt.Printf("原始请求体: %s\n", string(plainBytes)) // 2. AES 加密请求体 encData, nonce, err := aesEncrypt(plainBytes, securitySecret) if err != nil { fmt.Printf("[ERROR] encrypt: %v\n", err) return } encBody := map[string]string{ "data": encData, "time": nonce, } encBytes, _ := json.Marshal(encBody) fmt.Printf("加密请求体: %s\n\n", string(encBytes)) // 3. 发送请求 req, err := signedRequest("POST", serverURL+"/v1/auth/login/device", "", encBytes, "") if err != nil { fmt.Printf("[ERROR] new request: %v\n", err) return } req.Header.Set("Login-Type", "device") client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { fmt.Printf("[ERROR] request failed: %v\n", err) return } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) fmt.Printf("HTTP Status: %d\n", resp.StatusCode) fmt.Printf("原始响应: %s\n\n", string(respBody)) // 4. 解密响应 // 响应格式: {"code":200,"data":{"data":"","time":""},"message":""} var outer struct { Code int `json:"code"` Message string `json:"message"` Data json.RawMessage `json:"data"` } if err := json.Unmarshal(respBody, &outer); err != nil { fmt.Printf("[ERROR] parse response: %v\n", err) return } if outer.Code != 200 { fmt.Printf("[FAIL] 登录失败: code=%d message=%s\n", outer.Code, outer.Message) return } // data 字段是加密对象 var encResp struct { Data string `json:"data"` Time string `json:"time"` } if err := json.Unmarshal(outer.Data, &encResp); err != nil { // 如果 Device.Enable=false,data 直接就是明文对象 fmt.Printf("响应 data 非加密格式,直接解析: %s\n", string(outer.Data)) var loginResp struct { Token string `json:"token"` } if err2 := json.Unmarshal(outer.Data, &loginResp); err2 == nil && loginResp.Token != "" { fmt.Printf("[OK] Token: %s\n", loginResp.Token) } return } decrypted, err := aesDecrypt(encResp.Data, securitySecret, encResp.Time) if err != nil { fmt.Printf("[ERROR] decrypt response: %v\n", err) return } fmt.Printf("解密后响应: %s\n\n", decrypted) var loginResp struct { Token string `json:"token"` } if err := json.Unmarshal([]byte(decrypted), &loginResp); err != nil { fmt.Printf("[ERROR] parse decrypted: %v\n", err) return } fmt.Printf("[OK] Token: %s\n", loginResp.Token) // 5. 用 token 请求订阅列表 fmt.Println("\n=== 请求订阅列表 ===") subReq, err := signedRequest("GET", serverURL+"/v1/public/subscribe/list", "", nil, loginResp.Token) if err != nil { fmt.Printf("[ERROR] build subscribe request: %v\n", err) return } subReq.Header.Set("Login-Type", "device") subResp, err := client.Do(subReq) if err != nil { fmt.Printf("[ERROR] subscribe list request: %v\n", err) return } defer subResp.Body.Close() subBody, _ := io.ReadAll(subResp.Body) fmt.Printf("HTTP Status: %d\n", subResp.StatusCode) fmt.Printf("原始响应: %s\n", string(subBody)) // 解密订阅列表响应 var subOuter struct { Code int `json:"code"` Message string `json:"message"` Data json.RawMessage `json:"data"` } if err := json.Unmarshal(subBody, &subOuter); err != nil { fmt.Printf("[ERROR] parse subscribe response: %v\n", err) return } if subOuter.Code != 200 { fmt.Printf("[FAIL] 订阅列表失败: code=%d message=%s\n", subOuter.Code, subOuter.Message) return } var subEnc struct { Data string `json:"data"` Time string `json:"time"` } if err := json.Unmarshal(subOuter.Data, &subEnc); err != nil || subEnc.Data == "" { // 无加密,直接打印 fmt.Printf("\n[OK] 订阅列表(明文): %s\n", string(subOuter.Data)) return } subDecrypted, err := aesDecrypt(subEnc.Data, securitySecret, subEnc.Time) if err != nil { fmt.Printf("[ERROR] decrypt subscribe list: %v\n", err) return } fmt.Printf("\n[OK] 订阅列表(解密): %s\n", subDecrypted) }