This commit is contained in:
2025-12-04 21:10:32 +08:00
parent 40d9b5aad4
commit f7d11c5f8b
5 changed files with 8 additions and 12 deletions

32
Shared/KBSignUtils.h Normal file
View File

@@ -0,0 +1,32 @@
//
// KBSignUtils.h
// keyBoard
//
// Created by Mac on 2025/12/4.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface KBSignUtils : NSObject
/// URL 编码(与后端保持一致)
+ (NSString *)urlEncode:(NSString *)value;
/// HMAC-SHA256返回十六进制小写字符串
+ (NSString *)hmacSHA256:(NSString *)data secret:(NSString *)secret;
/// 生成签名:传入参与签名的所有参数(不含 sign 自身)
+ (NSString *)signWithParams:(NSDictionary<NSString *, NSString *> *)params
secret:(NSString *)secret;
/// 秒级时间戳(字符串)
+ (NSString *)currentTimestamp;
/// 简单 nonce 生成(默认 16 位)
+ (NSString *)generateNonceWithLength:(NSUInteger)length;
@end
NS_ASSUME_NONNULL_END

92
Shared/KBSignUtils.m Normal file
View File

@@ -0,0 +1,92 @@
//
// KBSignUtils.m
// keyBoard
//
// Created by Mac on 2025/12/4.
//
#import "KBSignUtils.h"
#import <CommonCrypto/CommonCrypto.h>
@implementation KBSignUtils
+ (NSString *)urlEncode:(NSString *)value {
if (!value) return @"";
// Swift .urlQueryAllowed
NSCharacterSet *allowed = [NSCharacterSet URLQueryAllowedCharacterSet];
NSString *encoded = [value stringByAddingPercentEncodingWithAllowedCharacters:allowed];
return encoded ?: value;
}
+ (NSString *)hmacSHA256:(NSString *)data secret:(NSString *)secret {
const char *cKey = [secret cStringUsingEncoding:NSUTF8StringEncoding];
const char *cData = [data cStringUsingEncoding:NSUTF8StringEncoding];
unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256,
cKey,
strlen(cKey),
cData,
strlen(cData),
cHMAC);
//
NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
[output appendFormat:@"%02x", cHMAC[i]];
}
return output;
}
+ (NSString *)signWithParams:(NSDictionary<NSString *, NSString *> *)params
secret:(NSString *)secret {
// 1. & sign
NSMutableDictionary<NSString *, NSString *> *filtered = [NSMutableDictionary dictionary];
[params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
if (!obj || obj.length == 0) return;
if ([[key lowercaseString] isEqualToString:@"sign"]) return;
filtered[key] = obj;
}];
// 2. key
NSArray<NSString *> *sortedKeys = [[filtered allKeys] sortedArrayUsingSelector:@selector(compare:)];
// 3. key=value&key2=value2...&secret=xxx
NSMutableArray<NSString *> *components = [NSMutableArray array];
for (NSString *key in sortedKeys) {
NSString *value = filtered[key] ?: @"";
NSString *encodedValue = [self urlEncode:value];
NSString *part = [NSString stringWithFormat:@"%@=%@", key, encodedValue];
[components addObject:part];
}
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;
}
+ (NSString *)currentTimestamp {
NSTimeInterval ts = [[NSDate date] timeIntervalSince1970];
long long seconds = (long long)ts;
return [NSString stringWithFormat:@"%lld", seconds];
}
+ (NSString *)generateNonceWithLength:(NSUInteger)length {
// UUID - , length
NSString *uuid = [[NSUUID UUID].UUIDString stringByReplacingOccurrencesOfString:@"-" withString:@""];
if (length == 0 || length > uuid.length) {
return uuid;
}
return [uuid substringToIndex:length];
}
@end

View File

@@ -0,0 +1,44 @@
//
// KBUserSessionManager.h
// keyBoard
//
// Created by Mac on 2025/12/1.
//
// KBUserSessionManager.h
// 主 App + 键盘扩展 共用的登录会话管理
#import <Foundation/Foundation.h>
@class KBUser;
NS_ASSUME_NONNULL_BEGIN
@interface KBUserSessionManager : NSObject
+ (instancetype)shared;
/// 当前登录用户(可能为 nil
@property (atomic, strong, readonly, nullable) KBUser *currentUser;
/// 是否已登录:由 KBAuthManager 基于 accessToken 判断
@property (nonatomic, assign, readonly) BOOL isLoggedIn;
/// 启动时调用:做“卸载重装”检测 & 恢复本地缓存
- (void)bootstrapIfNeeded;
/// 当前 accessToken可能为 nil/空字符串
- (nullable NSString *)accessToken;
/// 登录成功统一入口:写 Keychain + 缓存用户信息
- (void)handleLoginSuccessWithUser:(KBUser *)user;
/// 退出登录:清 Keychain + 本地缓存
- (void)logout;
- (void)goLoginVC;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,171 @@
//
// KBUserSessionManager.m
// keyBoard
//
// Created by Mac on 2025/12/1.
//
#import "KBUserSessionManager.h"
#import "KBAuthManager.h"
#import "KBUser.h"
#import "KBConfig.h"
#import <MJExtension/MJExtension.h>
#import "KBLoginVC.h"
/// App Group key
static NSString * const kKBSessionUserStoreKey = @"KBSession.currentUser";
/// key
static NSString * const kKBSessionInstallFlagKey = @"KBSession.installInitialized";
@interface KBUserSessionManager ()
@property (nonatomic, strong) NSUserDefaults *defaults;
@property (atomic, strong, readwrite, nullable) KBUser *currentUser;
@property (nonatomic, assign) BOOL didBootstrap;
@end
@implementation KBUserSessionManager
+ (instancetype)shared {
static KBUserSessionManager *m;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
m = [KBUserSessionManager new];
});
return m;
}
- (instancetype)init {
if (self = [super init]) {
// App Group UserDefaultsApp
NSUserDefaults *ud = [[NSUserDefaults alloc] initWithSuiteName:AppGroup];
_defaults = ud ?: [NSUserDefaults standardUserDefaults];
// token signOut
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_onAuthChanged:)
name:KBAuthChangedNotification
object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Public
- (void)bootstrapIfNeeded {
if (self.didBootstrap) return;
self.didBootstrap = YES;
// 使 App standardUserDefaults
// Keychain token
NSUserDefaults *installDefaults = [NSUserDefaults standardUserDefaults];
BOOL hasInitializedThisInstall = [installDefaults boolForKey:kKBSessionInstallFlagKey];
KBAuthManager *auth = [KBAuthManager shared]; // reloadFromKeychain
if (!hasInitializedThisInstall) {
//
[installDefaults setBool:YES forKey:kKBSessionInstallFlagKey];
[installDefaults synchronize];
// Keychain token
if (auth.current.accessToken.length > 0) {
[auth signOut];
}
//
[self.defaults removeObjectForKey:kKBSessionUserStoreKey];
[self.defaults synchronize];
self.currentUser = nil;
} else {
//
[self p_loadUserFromStore];
}
}
- (BOOL)isLoggedIn {
return [[KBAuthManager shared] isLoggedIn];
}
- (NSString *)accessToken {
return [KBAuthManager shared].current.accessToken;
}
- (void)handleLoginSuccessWithUser:(KBUser *)user {
if (!user) return;
// Keychain KBAuthManager
NSString *token = user.token;
if (token.length > 0) {
[[KBAuthManager shared] saveAccessToken:token
refreshToken:nil
expiryDate:nil
userIdentifier:nil];
}
// App/使
self.currentUser = user;
[self p_saveUserToStore:user];
}
- (void)logout {
// Keychain
[[KBAuthManager shared] signOut];
//
self.currentUser = nil;
[self.defaults removeObjectForKey:kKBSessionUserStoreKey];
[self.defaults synchronize];
}
#pragma mark - Private
- (void)p_loadUserFromStore {
id obj = [self.defaults objectForKey:kKBSessionUserStoreKey];
if (![obj isKindOfClass:[NSDictionary class]]) {
self.currentUser = nil;
return;
}
KBUser *user = [KBUser mj_objectWithKeyValues:(NSDictionary *)obj];
self.currentUser = user;
}
- (void)p_saveUserToStore:(KBUser *)user {
if (!user) {
[self.defaults removeObjectForKey:kKBSessionUserStoreKey];
[self.defaults synchronize];
return;
}
NSDictionary *dict = [user mj_keyValues];
if (dict) {
[self.defaults setObject:dict forKey:kKBSessionUserStoreKey];
[self.defaults synchronize];
}
}
- (void)_onAuthChanged:(NSNotification *)note {
// KBAuthManager +shared +shared
// 使
KBAuthManager *auth = nil;
if ([note.object isKindOfClass:[KBAuthManager class]]) {
auth = (KBAuthManager *)note.object;
} else {
auth = [KBAuthManager shared];
}
// token currentUser
if (![auth isLoggedIn]) {
self.currentUser = nil;
[self.defaults removeObjectForKey:kKBSessionUserStoreKey];
[self.defaults synchronize];
}
}
- (void)goLoginVC{
KBLoginVC *vc = [[KBLoginVC alloc] init];
[KB_CURRENT_NAV pushViewController:vc animated:true];
}
@end