-
Safari Cookie互通流程大致如下:
- 用戶使用Safari瀏覽wap,wap將個(gè)性化信息寫入cookie(取名要跟客戶端約定好,在demo中偶用testcookie)
- 用戶跳出wap進(jìn)入Apple Store下載app并啟動(dòng)運(yùn)行
- 初次運(yùn)行,初始化一個(gè)透明Safari(iOS10有新的約束,詳情看下面)
- Safari訪問(wèn)同一域名獲取cookie,并通過(guò)openURL(application:openURL:options:)回傳給app
- app拿到瀏覽器數(shù)據(jù)后,銷毀對(duì)應(yīng)Safari
-
iOS11 SFAuthenticationSession獲取cookie跟使用SFSafariViewController的流程大致相同,如下:
- 用戶使用Safari瀏覽wap,wap將個(gè)性化信息寫入cookie(取名要跟客戶端約定好,在demo中偶用testcookie)
- 用戶跳出wap進(jìn)入Apple Store下載app并啟動(dòng)運(yùn)行
- 初次運(yùn)行,初始化一個(gè)SFAuthenticationSession的實(shí)例authSession并需要strong持有(因?yàn)檫@個(gè)實(shí)例會(huì)被釋放,授權(quán)提示一閃而過(guò),讓你看看就好 ( ?? ?) ,就是這么驕傲放縱)
- authSession訪問(wèn)同一域名進(jìn)行身份認(rèn)證獲取cookie,并通過(guò)completionHandler回傳給app
- 拿到cookie后,訪問(wèn)的wap頁(yè)面會(huì)自行退出,但需要注意的是:若未拿到cookie,wap頁(yè)面需要人為操作才能消失,所以在沒(méi)有有效cookie,wap也要調(diào)用URL Schemes
對(duì)比發(fā)現(xiàn),其實(shí)兩個(gè)的差別很小,就是3.4.5這里稍稍不同,而wap代碼用同一份即可,這點(diǎn)還是很欣慰的,老淚縱橫呀,有木有,但是其實(shí)體驗(yàn)真心算不上好,感覺(jué)效果也就iOS9這一版本能讓人稍稍有點(diǎn)安慰
好了,不吹水了,咱們開(kāi)始正式的實(shí)踐操作
- iOS9-iOS10 SFSafariViewController 使用
- 初始化一個(gè)Safari并訪問(wèn)同一個(gè)域名,這里偶在本地進(jìn)行測(cè)試,所帶Schemes為openURL所對(duì)應(yīng)的URL Schemes,真的使用中無(wú)需這個(gè)參數(shù)
self.safariVC = [[SFSafariViewController alloc] initWithURL:[NSURL URLWithString:@"http://localhost/HelloPHP/index.html?Schemes=test1.liya"]];
self.safariVC.delegate = self;
2.接下來(lái)這步比較重要,為了讓用戶無(wú)感,Safari應(yīng)該是透明的,而這在iOS9的操作大部分如下:
self.safariVC.view.alpha = 0.0;
self.safariVC.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self presentViewController:self.safariVC animated:YES completion:nil];
但是在iOS10后就不被允許了(上面代碼會(huì)發(fā)現(xiàn)并未完成訪問(wèn)域名等操作),首先,最低透明度必須至少0.05,其次,跟window不可無(wú)重疊區(qū)域,具體可查閱review 5.1.1
SafariViewContoller must be used to visibly present information to users; the controller may not be hidden or obscured by other views or layers. Additionally, an app may not use SafariViewController to track users without their knowledge and consent.
現(xiàn)一般將Safari作為子控制器進(jìn)行添加,完整初始代碼如下
if ([LYSystem systemVersion] >= 9 && [LYSystem systemVersion] < 11) {
self.safariVC = [[SFSafariViewController alloc] initWithURL:[NSURL URLWithString:@"http://localhost/HelloPHP/index.html?Schemes=test1.liya"]];
self.safariVC.delegate = self;
self.safariVC.view.alpha = 0.05;
[self addChildViewController:self.safariVC];
self.safariVC.view.frame = CGRectMake(0.0, 0.0, 0.5, 0.5);
[self.view insertSubview:self.safariVC.view atIndex:0];
[self.safariVC didMoveToParentViewController:self];
}
3.接下來(lái)就到了最重要的個(gè)性化數(shù)據(jù)獲取啦(AppDelegate.m),對(duì)其中url進(jìn)行解析判斷即可
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
NSLog(@"openURL = %@", url);
return YES;
}
4.成功拿到數(shù)據(jù)后記得銷毀Safari,實(shí)際使用過(guò)程中同時(shí)也要考慮到超時(shí)的情況,可能獲取cookie失敗(demo只進(jìn)行簡(jiǎn)單成功的處理)
- (void)safariViewController:(SFSafariViewController *)controller didCompleteInitialLoad:(BOOL)didLoadSuccessfully {
if (didLoadSuccessfully) {
[self.safariVC willMoveToParentViewController:nil];
[self.safariVC.view removeFromSuperview];
[self.safariVC removeFromParentViewController];
self.safariVC = nil;
}
}
- iOS11 SFAuthenticationSession 使用
1.初始化一個(gè)SFAuthenticationSession并訪問(wèn)同一個(gè)域名,這一步類似
if ([LYSystem systemVersion] >= 11) {
self.authSession = [[SFAuthenticationSession alloc] initWithURL:[NSURL URLWithString:self.urlStr] callbackURLScheme:nil completionHandler:^(NSURL * _Nullable callbackURL, NSError * _Nullable error) {
NSLog(@"callbackURL = %@", callbackURL);
NSLog(@"error = %@", error.description);
self.authSession = nil;
}];
[self.authSession start];
}
- 跟SFSafariViewController數(shù)據(jù)獲取的地方不同,在回調(diào)completionHandler block中callbackURL攜帶數(shù)據(jù),在error為nil時(shí)對(duì)其解析即可
- 用SFAuthenticationSession,會(huì)跳出一個(gè)授權(quán)頁(yè)面,選擇Cancel時(shí),completionHandler會(huì)收到error code=1的錯(cuò)誤,進(jìn)行授權(quán)確認(rèn)后,才進(jìn)行域名訪問(wèn),并對(duì)應(yīng)進(jìn)行處理
![]() 授權(quán)頁(yè)
|
![]() 授權(quán)后
|
|---|
- 到了這里,偶們也發(fā)現(xiàn)了,這個(gè)跟SFSafariViewController表現(xiàn)有所差距,滿滿的存在感,什么無(wú)感呀,扯淡,所以要解決兩點(diǎn),就是授權(quán)彈框提示隱藏并自行執(zhí)行繼續(xù)操作,還有訪問(wèn)域名頁(yè)面隱藏
4.1 授權(quán)彈框
每次請(qǐng)求都會(huì)彈出授權(quán)窗口,是可忍孰不可忍,跟蹤后發(fā)現(xiàn)授權(quán)窗口為SFBrowserRemoteViewController,并且訪問(wèn)頁(yè)面SFAuthenticationViewController以子控制器將其進(jìn)行添加,理所當(dāng)然以為可以類似切換APP桌面icon的彈框一樣,hook后不彈框并進(jìn)行允許操作,最后未實(shí)現(xiàn)(各種實(shí)驗(yàn)過(guò)程就不說(shuō)了),這一點(diǎn)會(huì)繼續(xù)探索(這一步希望有了解的可以告訴偶耶)
4.2 域名訪問(wèn)頁(yè)面
看頁(yè)面展示形式,比較明顯的模態(tài)跳轉(zhuǎn),想到的是在present前,改變頁(yè)面的一些屬性進(jìn)行處理,hook presentViewController:animated:completion:方法
+ (void)load {
if ([LYSystem systemVersion] >= 11) {
[UIViewController liya_swizzleMethod:@selector(presentViewController:animated:completion:) withMethod:@selector(liya_presentViewController:animated:completion:) error:nil];
}
}
當(dāng)發(fā)現(xiàn)是授權(quán)顯示頁(yè)面時(shí),將其透明度改為0等其他操作即可實(shí)現(xiàn)web無(wú)感訪問(wèn)
- (void)liya_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
Class SFAuthenticationViewController = NSClassFromString(@"SFAuthenticationViewController");
if (SFAuthenticationViewController && [viewControllerToPresent isKindOfClass:SFAuthenticationViewController]) {
viewControllerToPresent.view.alpha = 0.0;
viewControllerToPresent.view.frame = CGRectZero;
viewControllerToPresent.modalPresentationStyle = UIModalPresentationOverCurrentContext;
}
[self liya_presentViewController:viewControllerToPresent animated:flag completion:completion];
}
- 用于測(cè)試的靜態(tài)網(wǎng)頁(yè)
由于簡(jiǎn)單的測(cè)試,所以就不麻煩相關(guān)后臺(tái)人員,自行寫個(gè)簡(jiǎn)單的本地靜態(tài)頁(yè)面即可,這里推薦下PhpStorm 或者 WebStorm,使用方法自行g(shù)oogle喲,還是挺方便的。。。偶用于實(shí)踐的靜態(tài)頁(yè)面代碼相當(dāng)簡(jiǎn)單(不能添加附件,只能鏈接網(wǎng)址,這樣還要把這么一份小文件放入云盤中,實(shí)在麻煩,所以直接代碼全貼出來(lái)啦),只有如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" name="viewport" content="width=device-width">
<title>Liya測(cè)試叮叮咚</title>
</head>
<body>
<script>
var cookie = document.cookie;
var testcookies = cookie.match(/testcookie=(\w+)/);
var testcookie;
if (testcookies) {
testcookie = testcookies[1];
document.writeln("當(dāng)前cookie " + testcookie + '.');
} else {
document.writeln("當(dāng)前沒(méi)有cookie,輸入一個(gè) ");
testcookie = "";
}
function getParameterByName(name) {
var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}
var Schemes = getParameterByName("Schemes");
if (Schemes) {
if (testcookie.length > 0) {
location.href = Schemes+"://testcookie/" + testcookie;
} else {
location.href = Schemes+"://";
}
}
function saveTestcookie() {
document.cookie = 'testcookie=' + document.getElementById('testcookie').value + ';max-age=3600';
location.reload();
}
</script>
<input type="text" id="testcookie">
<input type="submit" id="submit_cookie" value="新cookie保存" onclick="saveTestcookie();">
</body>
</html>

