Objective-C 签名 SDK 接入指南

功能说明

Objective-C 签名 SDK 用于为美图开放平台 API 请求生成 X-Sdk-DateAuthorization 请求头,并返回可由 NSURLSession 直接发送的 NSURLRequest。SDK 使用 SDK-HMAC-SHA256 算法,通过 AK/SK 完成请求签名。

SK 必须保存在服务端,不可写入发布给用户的 iOS、macOS 或其他客户端。

环境要求

  • Apple Objective-C 工程
  • Foundation
  • CommonCrypto
  • NSURLSession
  • ARC

SDK 未指定最低系统版本,请以业务工程的 deployment target 和编译环境为准。

下载与引入

SDK 版本:1.0.0 下载 Objective-C SDK 与示例

解压后包含:

AIGCP-API-ObjectiveC-sdk-1.0.0/
├── MTSigner.h
├── MTSigner.m
└── MTDemo.m

MTSigner.hMTSigner.m 添加到工程 Target,在调用处引入:

#import "MTSigner.h"

接口说明

初始化签名器

- (instancetype)initWithKey:(NSString *)key
                      secret:(NSString *)secret;
参数说明
keyAccess Key(AK)
secretSecret Key(SK)

创建签名请求

- (NSURLRequest *)signRequest:(NSURL *)url
                       method:(NSString *)method
                      headers:(NSDictionary<NSString *, NSString *> *)headers
                         body:(NSString *)body
                        error:(NSError **)error;
参数说明
url最终请求 URL,包含已经编码和排序的查询参数
methodHTTP 方法,例如 GETPOST
headers参与签名的请求头,必须包含 Host
body实际发送的 UTF-8 请求体;GET 传 @""
error错误输出参数

返回值是已经设置请求方法、请求头和 body 的 NSURLRequest。应直接发送该对象,不要在签名后重新创建或修改请求。

请求头常量

常量请求头/值
kHeaderHostHost
kHeaderXDateX-Sdk-Date
kHeaderAuthorizationAuthorization
kHeaderContentSha256X-Sdk-Content-Sha256
kAlgorithmSDK-HMAC-SHA256

若未传 X-Sdk-Date,SDK 会自动生成 UTC 时间戳。签名完成后,SDK 会自动添加 Authorization

POST 完整调用示例

下面的示例调用正式同步任务接口 https://openapi.meitu.com/api/v1/sdk/sync/push。请求 body 包含五个顶层字段:tasktask_typeinit_images、作为 JSON 字符串传递的 params,以及 sync_timeout。请根据目标能力的 API 文档替换任务、业务参数、图片和超时值。

#import <Foundation/Foundation.h>
#import "MTSigner.h"

static void SendSignedPOST(void) {
    NSDictionary *environment = [NSProcessInfo processInfo].environment;
    NSString *accessKey = environment[@"MEITU_OPENAPI_AK"];
    NSString *secretKey = environment[@"MEITU_OPENAPI_SK"];
    if (accessKey.length == 0 || secretKey.length == 0) {
        NSLog(@"Missing MEITU_OPENAPI_AK or MEITU_OPENAPI_SK");
        return;
    }

    NSURL *url = [NSURL URLWithString:
        @"https://openapi.meitu.com/api/v1/sdk/sync/push"];
    NSDictionary *payload = @{
        @"task": @"/v1/replace-with-product-task",
        @"task_type": @"formula",
        @"init_images": @[
            @{
                @"url": @"https://example.com/input.jpg",
                @"profile": @{
                    @"media_profiles": @{ @"media_data_type": @"url" },
                    @"version": @"v1"
                }
            }
        ],
        @"params": @"{\"parameter\":{\"rsp_media_type\":\"url\"}}",
        @"sync_timeout": @30
    };

    NSError *jsonError = nil;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:payload
                                                       options:0
                                                         error:&jsonError];
    if (jsonData == nil) {
        NSLog(@"Failed to serialize request JSON: %@", jsonError);
        return;
    }

    NSString *body = [[NSString alloc] initWithData:jsonData
                                           encoding:NSUTF8StringEncoding];
    NSDictionary<NSString *, NSString *> *headers = @{
        kHeaderHost: @"openapi.meitu.com",
        @"Content-Type": @"application/json; charset=UTF-8",
        @"Accept": @"application/json"
    };

    MTSigner *signer = [[MTSigner alloc] initWithKey:accessKey secret:secretKey];
    NSError *signError = nil;
    NSURLRequest *request = [signer signRequest:url
                                        method:@"POST"
                                       headers:headers
                                          body:body
                                         error:&signError];
    if (request == nil || signError != nil) {
        NSLog(@"Failed to sign request: %@", signError);
        return;
    }

    NSURLSessionDataTask *task =
        [[NSURLSession sharedSession] dataTaskWithRequest:request
                                       completionHandler:^(NSData *data,
                                                           NSURLResponse *response,
                                                           NSError *error) {
        if (error != nil) {
            NSLog(@"Request failed: %@", error);
            return;
        }
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        NSString *responseBody = [[NSString alloc] initWithData:data
                                                       encoding:NSUTF8StringEncoding];
        NSLog(@"HTTP %ld", (long)httpResponse.statusCode);
        NSLog(@"%@", responseBody);
    }];
    [task resume];
}

运行前设置环境变量,并把示例中的 taskparams、图片信息和 sync_timeout 替换为目标接口要求的值。body 只序列化一次;SDK 使用同一字符串的 UTF-8 字节进行签名并创建 HTTPBody,随后直接发送签名器返回的 NSURLRequest

GET 调用示例

GET 请求通常不包含 body。所有查询参数应在签名前完成百分比编码和排序,并写入最终 URL。

NSURL *url = [NSURL URLWithString:
    @"https://openapi.meitu.com/api/v1/sdk/status?task_id=replace-with-task-id"];
NSDictionary<NSString *, NSString *> *headers = @{
    kHeaderHost: @"openapi.meitu.com",
    @"Accept": @"application/json"
};

MTSigner *signer = [[MTSigner alloc] initWithKey:accessKey secret:secretKey];
NSError *signError = nil;
NSURLRequest *request = [signer signRequest:url
                                    method:@"GET"
                                   headers:headers
                                      body:@""
                                     error:&signError];

if (request != nil && signError == nil) {
    NSURLSessionDataTask *task =
        [[NSURLSession sharedSession] dataTaskWithRequest:request
                                       completionHandler:^(NSData *data,
                                                           NSURLResponse *response,
                                                           NSError *error) {
        if (error != nil) {
            NSLog(@"Request failed: %@", error);
            return;
        }
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        NSString *body = [[NSString alloc] initWithData:data
                                               encoding:NSUTF8StringEncoding];
        NSLog(@"HTTP %ld", (long)httpResponse.statusCode);
        NSLog(@"%@", body);
    }];
    [task resume];
}

注意事项

  • 使用 HTTPS,并确保 Host 与 URL 的主机一致;本域名应填写 openapi.meitu.com
  • method 必须与实际请求一致,并使用大写形式,例如 GETPOST
  • URL、查询参数、参与签名的请求头和 body 必须在签名前确定,签名后不得修改。
  • 查询参数需先按接口签名规则完成 RFC 3986 百分比编码和排序;SDK 不会自动排序查询串。
  • POST 签名与发送必须使用完全相同的 UTF-8 body bytes。直接发送 SDK 返回的 NSURLRequest
  • GET 传空字符串 @"" 作为 body。
  • 保持服务器系统时间准确。自动生成的 X-Sdk-Date 为 UTC 时间。
  • 默认情况下 body 的 SHA-256 参与签名。若目标接口支持不校验 body 摘要,可在签名前设置:
headers[kHeaderContentSha256] = @"UNSIGNED-PAYLOAD";

常见错误

现象处理方法
返回 401403 或签名无效检查 AK/SK、服务器时间、Host、HTTP 方法,以及签名后 URL、请求头和 body 是否发生变化
POST 验签失败确认 JSON 只序列化一次,并直接发送签名器返回的请求;检查 Content-Type 和实际 UTF-8 body 是否一致
GET 验签失败检查查询参数是否在签名前完成编码和排序,并确认 body 为 @""
UNSIGNED-PAYLOAD 不生效使用 kHeaderContentSha256,确认值拼写正确,并确认目标接口支持该方式
请求超时或无法连接检查 HTTPS、DNS、网络代理和 NSURLSession 超时设置,并查看 completion handler 返回的网络错误
返回业务参数错误根据目标能力 API 文档核对 tasktask_typeinit_imagesparamssync_timeout