简介
这一系列讲述的是免SDK实现分享、登录、支付等业务。
将会使用ShareSDK Demo进行部分试验。
一、拦截授权参数
1、打开ShareSDK Demo,找到AppDelegate.m 添加如下代码
//头文件不要忘
#import <objc/runtime.h>
//对UIApplication的openURL:方法进行hook
-(void)swizzleOpenUrl{
SEL openUrlSEL=@selector(openURL:);
BOOL (*openUrlIMP)(id,SEL,id) =(BOOL(*)(id,SEL,id))[UIApplication instanceMethodForSelector:openUrlSEL];
static int count=0;
BOOL (^myOpenURL)(id SELF,NSURL * url)=^(id SELF,NSURL *url){
//打印出授权的URL
NSLog(@"\n----------open url: %d----------\n%@\n%@\n",count++,url,@"\n"/*[NSThread callStackSymbols]*/);
//新浪微博 史诗级变态参数结构
UIPasteboard * pasteboard = [UIPasteboard generalPasteboard];
//新浪微博的参数也是放在剪切板里但是与微信又不一样
NSArray * sinaWeiBoInfo = pasteboard.items;
NSLog(@"%@",sinaWeiBoInfo);
//解析transferObject结构
NSDictionary * transferObjectDic = sinaWeiBoInfo[0];
NSData * transferObjectData = transferObjectDic[@"transferObject"];
NSDictionary * transferObject = [NSKeyedUnarchiver unarchiveObjectWithData:transferObjectData];
NSLog(@"%@",transferObject);
NSDictionary * userInfoDic = sinaWeiBoInfo[1];
NSData * userInfoData = userInfoDic[@"userInfo"];
NSDictionary * userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:userInfoData];
NSLog(@"%@",userInfo);
NSDictionary * appDic = sinaWeiBoInfo[2];
NSData * appData = appDic[@"app"];
NSDictionary * app = [NSKeyedUnarchiver unarchiveObjectWithData:appData];
NSLog(@"%@",app);
//003203000
NSDictionary * sdkVersionDic = sinaWeiBoInfo[3];
NSData * sdkVersionData = sdkVersionDic[@"sdkVersion"];
NSString * sdkVersion = [[NSString alloc] initWithData:sdkVersionData encoding:NSUTF8StringEncoding];
NSLog(@"%@",sdkVersion);
return (BOOL)openUrlIMP(SELF,openUrlSEL,url);
};
class_replaceMethod([UIApplication class], openUrlSEL, imp_implementationWithBlock(myOpenURL), NULL);
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//只要添加这句
[self swizzleOpenUrl];
}
2、查看控制台打印的URL
//URL
weibosdk://request?id=183DFA52-1E35-4CDD-A7A9-5A093CE2B0AF&sdkversion=003203000&luicode=10000360&lfid=com.wangquanwei.ShareSDK
//剪切板参数
(
{
transferObject = <62706c69 73743030 d4010203 0405062a 2b582476 65727369 6f6e5824 6f626a65 63747359 24617263 68697665 72542474 6f701200 0186a0aa 07081516 1718191d 25265524 6e756c6c d3090a0b 0c101457 4e532e6b 6579735a 4e532e6f 626a6563 74735624 636c6173 73a30d0e 0f800280 038004a3 11121380 05800680 08800957 5f5f636c 61737359 72657175 65737449 445b7265 64697265 63745552 495f1012 57424175 74686f72 697a6552 65717565 7374d20b 1a1b1c59 4e532e73 7472696e 6780075f 10243444 37374538 30362d43 4231332d 34313945 2d424433 302d3743 37333544 43374530 4245d21e 1f20215a 24636c61 73736e61 6d655824 636c6173 7365735f 100f4e53 4d757461 626c6553 7472696e 67a32223 245f100f 4e534d75 7461626c 65537472 696e6758 4e535374 72696e67 584e534f 626a6563 745f101a 68747470 3a2f2f77 77772e77 616e6771 75616e77 65692e63 6f6dd21e 1f27285f 10134e53 4d757461 626c6544 69637469 6f6e6172 79a32729 245c4e53 44696374 696f6e61 72795f10 0f4e534b 65796564 41726368 69766572 d12c2d54 726f6f74 80010008 0011001a 0023002d 00320037 00420048 004f0057 00620069 006d006f 00710073 00770079 007b007d 007f0087 0091009d 00b200b7 00c100c3 00ea00ef 00fa0103 01150119 012b0134 013d015a 015f0175 01790186 0198019b 01a00000 00000000 02010000 00000000 002e0000 00000000 00000000 00000000 01a2>;
},
{
userInfo = <62706c69 73743030 d4010203 0405061a 1b582476 65727369 6f6e5824 6f626a65 63747359 24617263 68697665 72542474 6f701200 0186a0a5 07081112 1355246e 756c6cd3 090a0b0c 0e10574e 532e6b65 79735a4e 532e6f62 6a656374 73562463 6c617373 a10d8002 a10f8003 80045973 74617274 54696d65 5f101732 3031372d 30372d31 34203134 3a34343a 32383a35 3934d214 1516175a 24636c61 73736e61 6d655824 636c6173 7365735f 10134e53 4d757461 626c6544 69637469 6f6e6172 79a31618 195c4e53 44696374 696f6e61 7279584e 534f626a 6563745f 100f4e53 4b657965 64417263 68697665 72d11c1d 54726f6f 74800108 111a232d 32373d43 4a525d64 66686a6c 6e789297 a2abc1c5 d2dbedf0 f5000000 00000001 01000000 00000000 1e000000 00000000 00000000 00000000 f7>;
},
{
app = <62706c69 73743030 d4010203 04050622 23582476 65727369 6f6e5824 6f626a65 63747359 24617263 68697665 72542474 6f701200 0186a0a9 07081516 1718191a 1b55246e 756c6cd3 090a0b0c 1014574e 532e6b65 79735a4e 532e6f62 6a656374 73562463 6c617373 a30d0e0f 80028003 8004a311 12138005 80068007 80085361 69645862 756e646c 65494456 6170704b 65795f10 32303141 6f444c77 35652d47 45677134 6a6a7855 576d7559 56324361 6b376143 43714d5a 4a7a5756 61354f43 515f4d58 50632e5f 1018636f 6d2e7761 6e677175 616e7765 692e5368 61726553 444b5a33 31383339 30333336 37d21c1d 1e1f5a24 636c6173 736e616d 65582463 6c617373 65735f10 134e534d 75746162 6c654469 6374696f 6e617279 a31e2021 5c4e5344 69637469 6f6e6172 79584e53 4f626a65 63745f10 0f4e534b 65796564 41726368 69766572 d1242554 726f6f74 80010008 0011001a 0023002d 00320037 00410047 004e0056 00610068 006c006e 00700072 00760078 007a007c 007e0082 008b0092 00c700e2 00ed00f2 00fd0106 011c0120 012d0136 0148014b 01500000 00000000 02010000 00000000 00260000 00000000 00000000 00000000 0152>;
},
{
sdkVersion = <30303332 30333030 30>;
}
)
3、参数解释
//URL
//微博的url scheme
weibosdk
//固定写法
request
//UUID 通过 [[NSUUID UUID] UUIDString] 获取
id
//sdk版本号
sdkversion
//不知道
luicode
//bundle id 通过 [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"] 获取
lfid
//剪切板参数
//外壳一个数组
//transferObject、userInfo、app、sdkVersion的结果都需要NSKeyedArchiver序列化
//这里为了方便讲解所以给出原始值
(
{
transferObject = {
"__class" = WBAuthorizeRequest; //授权固定写法
redirectURI = "http://www.wangquanwei.com"; //回调地址
requestID = "1B12FB5E-0F80-45A4-A86A-D1EA1A75ABB7"; //UUID
};
},
{
userInfo = {
startTime = "2017-07-10 15:47:27:182"; //时间 精确到毫秒
};
},
{
app = {
aid = "01AoDLw5e-GEgq4jjxUWmuYV2Cak7aCCqMZJzWVa5OCQ_MXPc."; //新浪微博sdk的apple 广告id
appKey = 3183903367; //新浪微博appkey
bundleID = "com.wangquanwei.ShareSDK"; //bundle id
};
},
{
sdkVersion = "003203000"; sdk版本号
}
)
二、构造授权参数
代码仅供参考,部分代码做了封装,需要到demo里的TSinaWeiBoPlatform类查看。
//授权
+ (NSMutableString *)authorizeToSinaWeiBoPlatformSettings:(NSDictionary *)settings
authType:(NSString *)authType
appId:(NSString *)appId
appSecret:(NSString *)appSecret
redirectURI:(NSString *)redirectURI
onStateChanged:(TAuthorizeStateChangedHandler)stateChangedHandler {
if (stateChangedHandler) {
[TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler = stateChangedHandler;
}
[TSinaWeiBoPlatform shareInstance].appId = appId;
[TSinaWeiBoPlatform shareInstance].appSecret = appSecret;
[TSinaWeiBoPlatform shareInstance].redirectUri = redirectURI;
NSString * uuid = [[NSUUID UUID] UUIDString];
NSString * authorizeType;
if ([authType isEqualToString:@"TAuthTypeBoth"]) {
if ([TSinaWeiBoPlatform isSinaWeiBoInstalled]) {
//安装了客户端
authorizeType = @"SSO";
}
else {
//没安装客户端
authorizeType = @"WEB";
}
}
else if ([authType isEqualToString:@"TAuthTypeSSO"]) {
authorizeType = @"SSO";
}
else {
authorizeType = @"WEB";
}
if ([authorizeType isEqualToString:@"SSO"]) {
NSDictionary * transferObject = @{@"__class" : @"WBAuthorizeRequest",
@"redirectURI" : redirectURI,
@"requestID" : uuid
};
NSData * transferObjectData = [NSKeyedArchiver archivedDataWithRootObject:transferObject];
NSDictionary * transferObjectDic = @{@"transferObject" : transferObjectData};
//获取当前时间 精确到毫秒
NSString * currentDate = @"";
NSDateFormatter * formatter = [[NSDateFormatter alloc ] init];
[formatter setDateFormat:@"YYYY-MM-dd hh:mm:ss:SSS"];
currentDate = [formatter stringFromDate:[NSDate date]];
NSDictionary * userInfo = @{@"startTime" : currentDate};
NSData * userInfoData = [NSKeyedArchiver archivedDataWithRootObject:userInfo];
NSDictionary * userInfoDic = @{@"userInfo" : userInfoData};
NSDictionary * app = @{@"appKey" : appId,
@"bundleID" : kCFBundleIdentifier};
NSData * appData = [NSKeyedArchiver archivedDataWithRootObject:app];
NSDictionary * appDic = @{@"app" : appData};
NSData * sdkVersionData = [@"003203000" dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary * sdkVersion = @{@"sdkVersion" : sdkVersionData};
NSArray * sinaWeiBoArray = @[transferObjectDic,userInfoDic,appDic,sdkVersion];
UIPasteboard * pasteboard = [UIPasteboard generalPasteboard];
[pasteboard setItems:sinaWeiBoArray];
// weibosdk://request?id=524BBFB4-7DC5-4E2B-891C-73754D39868C&sdkversion=003203000&luicode=10000360&lfid=com.wangquanwei.ShareSDKDemo
return [NSMutableString stringWithFormat:@"weibosdk://request?id=%@&sdkversion=003203000&luicode=10000360&lfid=%@",uuid,kCFBundleIdentifier];
}
else if ([authorizeType isEqualToString:@"WEB"]){
//网页授权
TWebViewVC * webViewVC = [[TWebViewVC alloc] init];
// https://openmobile.qq.com/oauth2.0/m_authorize?state=test&sdkp=i&response_type=token&display=mobile&scope=get_simple_userinfo,get_user_info,add_topic,upload_pic,add_share&status_version=10&sdkv=3.2.1&status_machine=iPhone8,2&status_os=10.3.2&switch=1&redirect_uri=auth://www.qq.com&client_id=100371282
//时间戳
NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970] * 1000;
NSString * timeIntervalStr = [NSString stringWithFormat:@"%ld",(long)timeInterval];
NSString * url = [NSString stringWithFormat:@"https://open.weibo.cn/oauth2/authorize?client_id=%@&response_type=code&redirect_uri=%@&display=mobile&state=%@",appId,redirectURI,timeIntervalStr];
webViewVC.url = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
webViewVC.title = @"Sina Web Login";
webViewVC.redirectUri = redirectURI;
webViewVC.delegate = [TSinaWeiBoPlatform shareInstance];
UIViewController * vc = [[UIApplication sharedApplication] currentViewController];
if ([vc isKindOfClass:[UINavigationController class]]) {
[(UINavigationController *)vc pushViewController:webViewVC animated:YES];
}
else if ([vc isKindOfClass:[UIViewController class]]){
[vc.navigationController pushViewController:webViewVC animated:YES];
}
}
return nil;
}
三、发起请求
代码仅供参考,部分代码做了封装,需要到demo里的Trochilus类查看。
+ (void)authorize:(TPlatformType)platformType
settings:(NSDictionary *)settings
onStateChanged:(TAuthorizeStateChangedHandler)stateChangedHandler {
[Trochilus shareInstance].isPayment = NO;
NSString * authorizeUrl = nil;
switch (platformType) {
case TPlatformTypeSinaWeibo: {
authorizeUrl = [TSinaWeiBoPlatform authorizeToSinaWeiBoPlatformSettings:nil
authType:[self platformForKey:sinaWeiBo][@"authType"]
appId:[self platformForKey:sinaWeiBo][@"appKey"]
appSecret:[self platformForKey:sinaWeiBo][@"appSecret"]
redirectURI:[self platformForKey:sinaWeiBo][@"redirectUri"] onStateChanged:^(TResponseState state, TUser *user, NSError *error) {
if (stateChangedHandler) {
stateChangedHandler(state,user,error);
}
}];
}
break;
default:
break;
}
[Trochilus sendToURL:authorizeUrl];
}
+ (void)sendToURL:(NSString *)url {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.001 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
});
}
四、新浪微博客户端回调
代码仅供参考,部分代码做了封装,需要到demo里的TSinaWeiBoPlatform类查看。
新浪微博回调并不带用户信息,需要通过token获取用户信息
+ (BOOL)handleUrlWithSinaWeiBo:(NSURL *)url {
UIPasteboard * pasteboard = [UIPasteboard generalPasteboard];
NSArray * sinaweiboArray = pasteboard.items;
//这里的transferObjectDic是授权后微博返回的,数据跟我们发送的已经不一样了
NSDictionary * transferObjectDic = sinaweiboArray[0];
NSData * transferObjectData = transferObjectDic[@"transferObject"];
NSDictionary * transferObject = [NSKeyedUnarchiver unarchiveObjectWithData:transferObjectData];
if ([transferObject[@"__class"] isEqualToString:@"WBAuthorizeResponse"]) {
//授权
if ([transferObject[@"statusCode"] integerValue] == 0) {
//授权成功
//本地保存授权过期时间 如果取不到授权过期时间那么启动授权 新浪给的时间与本地时间差8小时 需要转换
NSDate * localDate = [NSDate localDateWithGMTDate:transferObject[@"expirationDate"]];
[[NSUserDefaults standardUserDefaults] setObject:localDate forKey:@"SinaWeiBo_expirationDate"];
[[NSUserDefaults standardUserDefaults] synchronize];
[TSinaWeiBoPlatform shareInstance].user.access_token = transferObject[@"accessToken"];
[TSinaWeiBoPlatform shareInstance].user.expirationDate = transferObject[@"expirationDate"];
[TSinaWeiBoPlatform shareInstance].user.refreshToken = transferObject[@"refreshToken"];
[TSinaWeiBoPlatform shareInstance].user.requestID = transferObject[@"requestID"];
[TSinaWeiBoPlatform shareInstance].user.responseID = transferObject[@"responseID"];
[TSinaWeiBoPlatform shareInstance].user.statusCode = [transferObject[@"statusCode"] integerValue];
[TSinaWeiBoPlatform shareInstance].user.userID = transferObject[@"userID"];
[TSinaWeiBoPlatform shareInstance].user.__class = transferObject[@"__class"];
[self getUserInfoToSinaWeiBo:transferObject[@"userID"] accessToken:transferObject[@"accessToken"]];
}
else if ([transferObject[@"statusCode"] integerValue] == -1) {
//用户取消授权
if ([TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler) {
[TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler(TResponseStateCancel, nil, nil);
}
}
else {
if ([TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler) {
//授权失败
NSError *err=[NSError errorWithDomain:@"WeiBoDomain" code:[transferObject[@"statusCode"] intValue] userInfo:transferObject];
[TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler(TResponseStateFail, nil, err);
}
}
return YES;
}
return NO;
}
五、获取用户信息
//获取用户信息
+ (void)getUserInfoToSinaWeiBo:(NSString *)userID accessToken:(NSString *)accessToken {
NSString *url = [NSString stringWithFormat:@"https://api.weibo.com/2/users/show.json?uid=%@&access_token=%@",userID,accessToken];
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] init];
request.HTTPMethod = @"Get";
request.timeoutInterval = 20.5f;
request.URL = [NSURL URLWithString:url];
NSURLSessionDataTask * task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary * userInfo = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
[TSinaWeiBoPlatform shareInstance].user.userInfo = userInfo;
if (error) {
if ([TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler) {
[TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler(TResponseStateFail,nil,error);
}
}
else {
if ([TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler) {
[TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler(TResponseStateSuccess, [TSinaWeiBoPlatform shareInstance].user, nil);
}
}
}];
[task resume];
}
如果是网页授权。网页只会返回code 需要通过code获取token然后在获取用户信息
这里需要特别注意,接口写的是POST方式提交,实际上所有的参数还是以GET的形式提交的,POST传个空的内容即可,反正传什么服务器都不会去收。
如果以传统POST形式提交会报以下错误:
'{ "error":"invalid_request",
"error_code":21323,
"request":"/2/oauth2/access_token",
"error_uri":"/2/oauth2/access_token",
"error_description":"miss client id or secret"
}'
//获取 token 网页授权时用到
- (void)getAccessTokenWithCode:(NSString *)code {
//时间戳
NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970] * 1000;
NSString * timeIntervalStr = [NSString stringWithFormat:@"%ld",(long)timeInterval];
//参数实际上都是放在URL里以get形式传的 post内容为空即可。反正传什么服务器都不会去取
NSDictionary * dic = @{@"client_id" : self.appId,
@"code" : code,
@"state" : timeIntervalStr,
@"redirect_uri" : [self.redirectUri stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
@"client_secret" : self.appSecret ,
@"grant_type" : @"authorization_code"};
NSData *data= [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
NSString *url = [NSString stringWithFormat:@"https://api.weibo.com/oauth2/access_token?client_id=%@&client_secret=%@&grant_type=authorization_code&code=%@&redirect_uri=%@",self.appId,self.appSecret,code,[self.redirectUri stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] init];
request.HTTPMethod = @"Post";
request.timeoutInterval = 20.5f;
request.URL = [NSURL URLWithString:[url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
request.HTTPBody = data;
NSURLSessionDataTask * task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
if ([TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler) {
[TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler(TResponseStateFail,nil,error);
}
}
else {
NSDictionary * userInfo = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
[TSinaWeiBoPlatform getUserInfoToSinaWeiBo:userInfo[@"uid"] accessToken:userInfo[@"access_token"]];
}
}];
[task resume];
}
六、取消授权
微博有专门用来取消授权的接口,QQ、微信则无。(抓包时没发现)
//取消授权
+ (void)unAurhAct {
NSData * data = [[TSinaWeiBoPlatform shareInstance].user.access_token dataUsingEncoding:NSUTF8StringEncoding];
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] init];
request.URL = [NSURL URLWithString:@"https://api.weibo.com/oauth2/revokeoauth2"];
request.HTTPMethod = @"Post";
request.timeoutInterval = 20.5f;
request.HTTPBody = data;
NSURLSessionDataTask * task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary * json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
if (error == nil) {
if (json[@"result"]) {
if ([json[@"result"] boolValue] == YES) {
//取消授权成功
if ([TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler) {
[TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler(TResponseStateCancel, nil, nil);
}
}
else {
if ([TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler) {
//取消授权失败
NSError *err=[NSError errorWithDomain:@"weibo_authorize_response" code:-3 userInfo:@{@"description" : @"取消授权失败"}];
[TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler(TResponseStateFail, nil, err);
}
}
}
}
else {
if ([TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler) {
//取消授权失败
[TSinaWeiBoPlatform shareInstance].authorizeStateChangedHandler(TResponseStateFail, nil, error);
}
}
}];
[task resume];
}
七、参考资料
https://github.com/100apps/openshare
http://open.weibo.com/wiki/Oauth2/authorize
http://www.cnblogs.com/kvspas/archive/2011/12/30/sina-oauth-21323.html
Charles 抓包工具
八、Demo
https://github.com/quanweiwang/Trochilus
目录
分享篇
1、我的APP不可能这么胖之QQ好友分享
2、我的APP不可能这么胖之QQ空间分享
3、我的APP不可能这么胖之微信好友分享
4、我的APP不可能这么胖之微信朋友圈分享
5、我的APP不可能这么胖之新浪微博分享
登录篇
6、我的APP不可能这么胖之QQ登录
7、我的APP不可能这么胖之微信登录
8、我的APP不可能这么胖之新浪微博登录
支付篇
9、我的APP不可能这么胖之微信支付
10、我的APP不可能这么胖之支付宝支付
网友评论