diff --git a/Shared/KBSignUtils.h b/Shared/KBSignUtils.h index 958fce4..605bad8 100644 --- a/Shared/KBSignUtils.h +++ b/Shared/KBSignUtils.h @@ -20,6 +20,10 @@ NS_ASSUME_NONNULL_BEGIN + (NSString *)signWithParams:(NSDictionary *)params secret:(NSString *)secret; +/// 获取签名原始拼接字符串(HMAC 前的明文) ++ (NSString *)signSourceStringWithParams:(NSDictionary *)params + secret:(NSString *)secret; + /// 秒级时间戳(字符串) + (NSString *)currentTimestamp; @@ -29,4 +33,3 @@ NS_ASSUME_NONNULL_BEGIN @end NS_ASSUME_NONNULL_END - diff --git a/Shared/KBSignUtils.m b/Shared/KBSignUtils.m index 4d03036..c6efe2a 100644 --- a/Shared/KBSignUtils.m +++ b/Shared/KBSignUtils.m @@ -12,10 +12,16 @@ + (NSString *)urlEncode:(NSString *)value { if (!value) return @""; - // 和 Swift 里的 .urlQueryAllowed 类似 - NSCharacterSet *allowed = [NSCharacterSet URLQueryAllowedCharacterSet]; + // 按 application/x-www-form-urlencoded 规则编码(更贴近后端常见实现) + NSMutableCharacterSet *allowed = [NSMutableCharacterSet alphanumericCharacterSet]; + [allowed addCharactersInString:@"-._*"]; NSString *encoded = [value stringByAddingPercentEncodingWithAllowedCharacters:allowed]; - return encoded ?: value; + if (!encoded) { + return value; + } + // 空格改为 +(URLEncoder 风格) + encoded = [encoded stringByReplacingOccurrencesOfString:@"%20" withString:@"+"]; + return encoded; } + (NSString *)hmacSHA256:(NSString *)data secret:(NSString *)secret { @@ -41,6 +47,12 @@ + (NSString *)signWithParams:(NSDictionary *)params secret:(NSString *)secret { + NSString *dataString = [self signSourceStringWithParams:params secret:secret]; + return [self hmacSHA256:dataString secret:secret]; +} + ++ (NSString *)signSourceStringWithParams:(NSDictionary *)params + secret:(NSString *)secret { // 1. 过滤空值 & sign 自身 NSMutableDictionary *filtered = [NSMutableDictionary dictionary]; @@ -62,15 +74,11 @@ [components addObject:part]; } - NSString *encodedSecret = [self urlEncode:secret]; + NSString *encodedSecret = [self urlEncode:secret ?: @""]; NSString *secretPart = [NSString stringWithFormat:@"secret=%@", encodedSecret]; [components addObject:secretPart]; - NSString *dataString = [components componentsJoinedByString:@"&"]; - - // 4. HMAC-SHA256 - NSString *sign = [self hmacSHA256:dataString secret:secret]; - return sign; + return [components componentsJoinedByString:@"&"]; } + (NSString *)currentTimestamp { @@ -89,4 +97,3 @@ } @end - diff --git a/keyBoard/Class/AiTalk/VM/AiVM.m b/keyBoard/Class/AiTalk/VM/AiVM.m index 4d4157b..27c14d7 100644 --- a/keyBoard/Class/AiTalk/VM/AiVM.m +++ b/keyBoard/Class/AiTalk/VM/AiVM.m @@ -119,12 +119,7 @@ autoShowBusinessError:NO return; } - NSString *encodedContent = - [content stringByAddingPercentEncodingWithAllowedCharacters: - [NSCharacterSet URLQueryAllowedCharacterSet]]; - NSString *path = [NSString - stringWithFormat:@"%@?content=%@&companionId=%ld", API_AI_CHAT_MESSAGE, - encodedContent ?: @"", (long)companionId]; + NSString *path = API_AI_CHAT_MESSAGE; NSDictionary *params = @{ @"content" : content ?: @"", @"companionId" : @(companionId) diff --git a/keyBoard/Class/Network/KBNetworkManager.m b/keyBoard/Class/Network/KBNetworkManager.m index c8e5a48..d123657 100644 --- a/keyBoard/Class/Network/KBNetworkManager.m +++ b/keyBoard/Class/Network/KBNetworkManager.m @@ -33,6 +33,29 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; @implementation KBNetworkManager +static NSString *KBSignStringFromObject(id obj) { + if (!obj || obj == (id)kCFNull) { + return nil; + } + if ([obj isKindOfClass:[NSString class]]) { + return (NSString *)obj; + } + if ([obj isKindOfClass:[NSNumber class]]) { + return [(NSNumber *)obj stringValue]; + } + if ([obj isKindOfClass:[NSArray class]] || [obj isKindOfClass:[NSDictionary class]]) { + NSJSONWritingOptions options = 0; + if (@available(iOS 11.0, *)) { + options = NSJSONWritingSortedKeys; + } + NSData *data = [NSJSONSerialization dataWithJSONObject:obj options:options error:nil]; + if (data.length > 0) { + return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + } + } + return [obj description]; +} + + (instancetype)shared { static KBNetworkManager *m; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ m = [KBNetworkManager new]; }); return m; @@ -73,14 +96,24 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; signParams[@"nonce"] = nonce; // 把 body 里的字段也加入签名参数 [bodyParams enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - if ([obj isKindOfClass:[NSString class]]) { - signParams[key] = obj; - } else { - signParams[key] = [obj description]; + NSString *value = KBSignStringFromObject(obj); + if (value.length == 0) { + return; } + signParams[key] = value; }]; + NSString *signSource = [KBSignUtils signSourceStringWithParams:signParams secret:secret]; NSString *sign = [KBSignUtils signWithParams:signParams secret:secret]; +#if DEBUG + if (signSource.length > 0) { + NSString *secretPart = [NSString stringWithFormat:@"secret=%@", [KBSignUtils urlEncode:secret ?: @""]]; + NSString *masked = [signSource stringByReplacingOccurrencesOfString:secretPart withString:@"secret=***"]; + KBLOG(@"[KBNetwork] sign source: %@", masked); + KBLOG(@"[KBNetwork] sign value: %@", sign ?: @""); + } +#endif + // 将签名相关字段合并进默认请求头 NSMutableDictionary *headers = [self.defaultHeaders mutableCopy] ?: [NSMutableDictionary dictionary]; @@ -202,6 +235,7 @@ autoShowBusinessError:YES parameters:(NSDictionary *)parameters headers:(NSDictionary *)headers completion:(KBNetworkDataCompletion)completion { + [self getSignWithParare:parameters]; if (!self.isEnabled) { NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorDisabled @@ -243,6 +277,7 @@ autoShowBusinessError:YES completion:(KBNetworkCompletion)completion { NSLog(@"[KBNetworkManager] UPLOAD called, enabled=%d, path=%@", self.isEnabled, path); + [self getSignWithParare:@{}]; if (![self ensureEnabled:completion]) return nil; NSString *urlString = [self buildURLStringWithPath:path]; @@ -330,6 +365,7 @@ autoShowBusinessError:YES parameters:(NSDictionary *)parameters headers:(NSDictionary *)headers completion:(KBNetworkCompletion)completion { + [self getSignWithParare:parameters ?: @{}]; if (!fileURL || !fileURL.isFileURL) { if (completion) { NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain