JSBridge 실현 메커니즘
안 드 로 이 드 에서 4.2 이전에 addJavaScriptInterface () 는 js 에 native 를 호출 하 는 방법 을 제공 합 니 다. 안전 위험 이 존재 합 니 다. 구체 적 으로 어떻게 발생 하 는 지 보 세 요. addJavaScriptInterface 위험 안 드 로 이 드 는 현재 webView Client 의 shouldOverrideUrl Loading (url) 과 WebChromeClient 의 onJSPrompt (url) 함수 로 문 제 를 해결 하고 있 습 니 다.나타 난 해결 방안 은 비교적 성숙 한 프레임 워 크 인 WebViewJS Bridge 가 있 습 니 다. 오늘 제 가 말씀 드 리 고 싶 은 것 은 바로 이 프레임 워 크 입 니 다. 본인 도 OC 개발 을 했 고 시간 도 했 기 때 문 입 니 다. 마침 이 부분 을 보 았 습 니 다. OC 버 전의 분석 을 했 는데 원리 가 대체적으로 일치 합 니 다.
의 원리
하 나 를 분석 하 는 방식 은 먼저 결과 에 착안 하여 어떻게 사용 하 는 지 알 고 사용 하 는 입구 에서 하나씩 깊이 발굴 할 수 있 습 니 다. 점 에서 면 까지 이것 은 제 일 관 된 업무 스타일 입 니 다. 자, 말 하지 않 고 먼저 보고 사용 할 수 있 습 니 다. 만약 에 H5 단 이 native 앨범 인터페이스 를 열 려 면 다음은 OC 단 호출 코드 입 니 다.
self.bridge = [WebViewJavascriptBridge bridgeForWebView:self.webView];
[self.bridge setWebViewDelegate:self];
/* JS OC API: , registerHandler */
[self.bridge registerHandler:@"openCamera" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@" %@ ", data[@"count"]);//data js . responseCallback OC js .
responseCallback(@" ");
UIImagePickerController *imageVC = [[UIImagePickerController alloc] init];
imageVC.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self presentViewController:imageVC animated:YES completion:nil];
}];
저희 가 registerHanlder 함수 에 들 어 갑 니 다.
typedef void (^WVJBResponseCallback)(id responseData);
typedef void (^WVJBHandler)(id data, WVJBResponseCallback responseCallback);
@property (strong, nonatomic) NSMutableDictionary* messageHandlers;
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
_base.messageHandlers[handlerName] = [handler copy];
}
OC 에서 registerHandler 함 수 를 호출 한 모든 데 이 터 를 messaeHandlers 사전 에 저장 하 는 것 을 알 수 있 습 니 다. key 는 위 에서 언급 한 @ "openCamera" 문자열 임 을 알 고 있 습 니 다.
다음은 H5 의 동작 을 출발 하여 native 앨범 인터페이스 를 열 어 H5 로 이동 하여 주요 코드 를 제공 합 니 다.
JS OC
// , js
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback];
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
// OC JS , OC JS
setupWebViewJavascriptBridge(function(bridge) {
/* OC JS API, JS API, OC , OC */
// OC
document.getElementById('btn').onclick = function () {
bridge.callHandler('openCamera', {'count':'10 '}, function responseCallback(responseData) {
console.log("OC :", responseData)
});
};
페이지 를 불 러 올 때 setUpWebView JavascripBridge (callabck) 방법 이 실 행 됩 니 다. 이 때 webview 의 shouldStart Load With Request 방법 이 실 행 됩 니 다. 실현 을 보 겠 습 니 다.
#define kOldProtocolScheme @"wvjbscheme"
#define kNewProtocolScheme @"https"
#define kQueueHasMessage @"__wvjb_queue_message__"
#define kBridgeLoaded @"__bridge_loaded__"
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (webView != _webView) { return YES; }
NSURL *url = [request URL];
__strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
if ([_base isWebViewJavascriptBridgeURL:url]) {
// .
if ([_base isBridgeLoadedURL:url]) {
// H5
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) {
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
} else {
[_base logUnkownMessage:url];
}
return NO;
} else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
} else {
return YES;
}
}
// wvjbscheme https url YES.
- (BOOL)isWebViewJavascriptBridgeURL:(NSURL*)url {
if (![self isSchemeMatch:url]) {
return NO;
}
return [self isBridgeLoadedURL:url] || [self isQueueMessageURL:url];
}
- (BOOL)isSchemeMatch:(NSURL*)url {
NSString* scheme = url.scheme.lowercaseString;
return [scheme isEqualToString:kNewProtocolScheme] || [scheme isEqualToString:kOldProtocolScheme];
}
- (BOOL)isQueueMessageURL:(NSURL*)url {
NSString* host = url.host.lowercaseString;
return [self isSchemeMatch:url] && [host isEqualToString:kQueueHasMessage];
}
// url wvjbscheme .
- (BOOL)isBridgeLoadedURL:(NSURL*)url {
NSString* host = url.host.lowercaseString;
return [self isSchemeMatch:url] && [host isEqualToString:kBridgeLoaded];
}
위의 [ base injectJavascriptFile] 에서;우리 돌아 가서 무슨 일이 일 어 났 는 지 보 자.
- (void)injectJavascriptFile {
NSString *js = WebViewJavascriptBridge_js();
[self _evaluateJavascript:js];
if (self.startupMessageQueue) {
NSArray* queue = self.startupMessageQueue;
self.startupMessageQueue = nil;
for (id queuedMessage in queue) {
[self _dispatchMessage:queuedMessage];
}
}
}
// webView JavaScriptBridge_js()
NSString * WebViewJavascriptBridge_js() {
#define __wvjb_js_func__(x) #x
// BEGIN preprocessorJSCode
static NSString * preprocessorJSCode = @__wvjb_js_func__(
;(function() {
if (window.WebViewJavascriptBridge) {
return;
}
if (!window.onerror) {
window.onerror = function(msg, url, line) {
console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line);
}
}
window.WebViewJavascriptBridge = {
registerHandler: registerHandler,
callHandler: callHandler,
disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
_fetchQueue: _fetchQueue,
_handleMessageFromObjC: _handleMessageFromObjC
};
var messagingIframe;
var sendMessageQueue = [];
var messageHandlers = {};
var CUSTOM_PROTOCOL_SCHEME = 'https';
var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__';
var responseCallbacks = {};
var uniqueId = 1;
var dispatchMessagesWithTimeoutSafety = true;
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
function callHandler(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName:handlerName, data:data }, responseCallback);
}
function disableJavscriptAlertBoxSafetyTimeout() {
dispatchMessagesWithTimeoutSafety = false;
}
function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
responseCallbacks[callbackId] = responseCallback;
message['callbackId'] = callbackId;
}
sendMessageQueue.push(message);
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
function _dispatchMessageFromObjC(messageJSON) {
if (dispatchMessagesWithTimeoutSafety) {
setTimeout(_doDispatchMessageFromObjC);
} else {
_doDispatchMessageFromObjC();
}
function _doDispatchMessageFromObjC() {
var message = JSON.parse(messageJSON);
var messageHandler;
var responseCallback;
if (message.responseId) {
responseCallback = responseCallbacks[message.responseId];
if (!responseCallback) {
return;
}
responseCallback(message.responseData);
delete responseCallbacks[message.responseId];
} else {
if (message.callbackId) {
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
};
}
var handler = messageHandlers[message.handlerName];
if (!handler) {
console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
} else {
handler(message.data, responseCallback);
}
}
}
}
function _handleMessageFromObjC(messageJSON) {
_dispatchMessageFromObjC(messageJSON);
}
messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
document.documentElement.appendChild(messagingIframe);
registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);
setTimeout(_callWVJBCallbacks, 0);
function _callWVJBCallbacks() {
var callbacks = window.WVJBCallbacks;
delete window.WVJBCallbacks;
for (var i=0; i
위의 NSString * js = WebView JavascriptBridgejs(); [self evaluate Javascript: js] 에 서 는 H5 단 에 웹 뷰 JavaScriptBridge 대상 을 등록 한 것 을 알 수 있 습 니 다. 이 대상 은 call Hanlder 와 register 방법 이 있 고 sendmessage Queue 메시지 큐 가 있 습 니 다.
클릭 하여 앨범 열기 버튼
bridge.callHandler('openCamera', {'count':'10 '}, function responseCallback(responseData) {
console.log("OC :", responseData)
});
function callHandler(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName:handlerName, data:data }, responseCallback);
}
function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
responseCallbacks[callbackId] = responseCallback;
message['callbackId'] = callbackId;
}
sendMessageQueue.push(message);
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
우 리 는 놀 라 운 발견 이 실 행 될 것 이다doSend 방법 으로 메 시 지 를 만 들 고 메시지 큐 에 추가 합 니 다. 이 메 시 지 는 {handler Name: 'openCamera', data: {'count': '10 장'}, callbackId: 'xxxx'} 을 포함 합 니 다.messageingIframe.src= wvjbscheme://wvjb_queue_message, 여기 서 webView 의 shouldStartRequest 방법 을 실행 합 니 다.
- (NSString *)webViewJavascriptFetchQueyCommand {
return @"WebViewJavascriptBridge._fetchQueue();";
}
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
//messageQueueString , jsbridge
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
// json . .
[_base flushMessageQueue:messageQueueString];
분석 [ base flushMessageQueue: message QueueString]
- (void)flushMessageQueue:(NSString *)messageQueueString{
if (messageQueueString == nil || messageQueueString.length == 0) {
NSLog(@"WebViewJavascriptBridge: WARNING: ObjC got nil while fetching the message queue JSON from webview. This can happen if the WebViewJavascriptBridge JS is not currently present in the webview, e.g if the webview just loaded a new page.");
return;
}
id messages = [self _deserializeMessageJSON:messageQueueString];
for (WVJBMessage* message in messages) {
if (![message isKindOfClass:[WVJBMessage class]]) {
NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
continue;
}
[self _log:@"RCVD" json:message];
NSString* responseId = message[@"responseId"];
if (responseId) {
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacks removeObjectForKey:responseId];
} else {
// .message responseId
WVJBResponseCallback responseCallback = NULL;
NSString* callbackId = message[@"callbackId"];
// js callbackId .
if (callbackId) {
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
// responseData(...) .
[self _queueMessage:msg];
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
}
// registerHandler "openCamera" ,
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
if (!handler) {
NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
continue;
}
// block .
handler(message[@"data"], responseCallback);
}
}
}
포켓 회전 이 다시 시작 점 으로 돌 아 왔 습 니 다. 위 에서 네 이 티 브 엔 드 와 h5 엔 드 가 같은 handler Name 을 포함 하고 있 음 을 알 수 있 습 니 다. 네 이 티 브 엔 드 registerHandler 는 H5 엔 드 callHandler 에 있어 야 합 니 다. callHandler 는 OC 엔 드 에 메 시 지 를 보 내 는 프로 토 콜 방법 을 실 행 했 습 니 다. 이후 OC 엔 드 는 H5 엔 드 에서 message Queue 를 받 았 습 니 다.OC 엔 드 에서 message Queue 를 최종 분석 하여 message Handlers 의 block 리 셋 모듈 WVJhandler 를 찾 았 습 니 다. 그 후에 block (data, responseCallback) 이 응답 한 결 과 를 얻 었 습 니 다. 그런데 이 responseCallback 은 어떻게 된 일 입 니까?서 두 르 지 마 세 요. registerhandler 의 response Callback ("메 시 지 를 받 았 습 니 다") 을 기억 하 세 요.
response Callback 분석 뭐 했 어 요?
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}
//WVJBMessage .
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
// responseData(...) .
[self _queueMessage:msg];
};
- (void)_queueMessage:(WVJBMessage*)message {
if (self.startupMessageQueue) {
[self.startupMessageQueue addObject:message];
} else {
[self _dispatchMessage:message];
}
}
- (void)_dispatchMessage:(WVJBMessage*)message {
NSString *messageJSON = [self _serializeMessage:message pretty:NO];
[self _log:@"SEND" json:messageJSON];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"
" withString:@"\
"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];
NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
if ([[NSThread currentThread] isMainThread]) {
[self _evaluateJavascript:javascriptCommand];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self _evaluateJavascript:javascriptCommand];
});
}
}
위의 코드 는 매우 간단 하 다. 나 는 너무 많이 설명 하고 싶 지 않다. 결국 아래 의 코드 를 실행 할 것 이다.
NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
// H5
function _dispatchMessageFromObjC(messageJSON) {
if (dispatchMessagesWithTimeoutSafety) {
setTimeout(_doDispatchMessageFromObjC);
} else {
_doDispatchMessageFromObjC();
}
function _doDispatchMessageFromObjC() {
var message = JSON.parse(messageJSON);
var messageHandler;
var responseCallback;
if (message.responseId) {
//WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
responseCallback = responseCallbacks[message.responseId];
if (!responseCallback) {
return;
}
responseCallback(message.responseData);
delete responseCallbacks[message.responseId];
} else {
if (message.callbackId) {
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
};
}
var handler = messageHandlers[message.handlerName];
if (!handler) {
console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
} else {
handler(message.data, responseCallback);
}
}
}
}
function _handleMessageFromObjC(messageJSON) {
_dispatchMessageFromObjC(messageJSON);
}
그때 의 callHandler ('openCamera', {'count': 10}, function (data) {}) 를 기억 합 니 다.function (data) 은 responseCallback 리 셋 입 니 다. OC 엔 드 의 리 셋 값 을 받 는 데 사 용 됩 니 다. OC 에서 responseCallback (@ "메 시 지 를 받 았 습 니 다") 은 결국 OC 의 를 촉발 합 니 다.dispatchMessage 방법, 이 방법 은 H5 중의 를 촉발 합 니 다.dispatchMessage FromObjC 방법 은 최종 적 으로 callbackId 를 통 해 대응 하 는 callback 을 찾 아 리 턴 값 을 실현 합 니 다.
결어
저 는 단 방향 으로 H5 단 트리거 사건 부터 리 턴 까지 전체 과정 이 가치 가 있 습 니 다. 그리고 OC 단 에서 H5 단 을 촉발 한 것 입 니 다. 여 기 는 분석 하지 않 고 원리 가 일치 합 니 다. 마지막 으로 12 시 에 도 글 자 를 외 우 며 왕 자 를 때 리 고 잤 습 니 다.
1. Android 4.2 이하, addJavascriptInterface 방법 에 보안 구멍 이 있 습 니 다. js 코드 는 자바 층 의 운행 대상 을 가 져 와 현재 사용자 가 악성 코드 를 실행 하 는 것 을 위조 할 수 있 습 니 다.2, ios 7 이하, JavaScript 는 native 코드 를 호출 할 수 없습니다.3. js 성명 의 대상 은 loadUrl 을 통 해 페이지 에 주입 되 기 때문에 이 대상 은 자바 대상 이 아니 라 js 대상 이 고 getClass 등 Object 방법 이 없 기 때문에 Runtime 대상 을 얻 을 수 없고 악성 코드 의 주입 을 피 할 수 있 습 니 다.4. JSBridge 는 URL 해석 의 상호작용 방식 으로 성숙 한 해결 방안 으로 확대 하기 쉽 고 중대 한 안전성 문제 가 없다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.