對於單獨的Web app應用來講,加載進來的url通常不能保證它的安全性。那麼如何來處理url安全性的問題呢。java
讓咱們來看看PhoneGap是如何作的。android
PhoneGap採用了白名單的形式,認爲在白名單中的url認爲是安全的,不在白名單中的url是不安全的。對於安全的url,PhoneGap的Web app會直接打開,對於不安全的url,會經過瀏覽器打開。web
那麼怎麼增長白名單呢?PhoneGap是須要在配置文件res/xml/config.xml中設置,以下:正則表達式
<cordova> - <!-- access elements control the Android whitelist. Domains are assumed blocked unless set otherwise --> <access origin="http://127.0.0.1*" /> - <!-- allow local pages --> - <!-- <access origin="https://example.com" /> allow any secure requests to example.com --> - <!-- <access origin="https://example.com" subdomains="true" /> such as above, but including subdomains, such as www --> <access origin=".*" /> <log level="DEBUG" /> <preference name="useBrowserHistory" value="false" /> <preference name="exit-on-suspend" value="false" /> - <plugins> <plugin name="App" value="org.apache.cordova.App" /> <plugin name="Geolocation" value="org.apache.cordova.GeoBroker" /> <plugin name="Device" value="org.apache.cordova.Device" /> <plugin name="Accelerometer" value="org.apache.cordova.AccelListener" /> <plugin name="Compass" value="org.apache.cordova.CompassListener" /> <plugin name="Media" value="org.apache.cordova.AudioHandler" /> <plugin name="Camera" value="org.apache.cordova.CameraLauncher" /> <plugin name="Contacts" value="org.apache.cordova.ContactManager" /> <plugin name="File" value="org.apache.cordova.FileUtils" /> <plugin name="NetworkStatus" value="org.apache.cordova.NetworkManager" /> <plugin name="Notification" value="org.apache.cordova.Notification" /> <plugin name="Storage" value="org.apache.cordova.Storage" /> <plugin name="Temperature" value="org.apache.cordova.TempListener" /> <plugin name="FileTransfer" value="org.apache.cordova.FileTransfer" /> <plugin name="Capture" value="org.apache.cordova.Capture" /> <plugin name="Battery" value="org.apache.cordova.BatteryListener" /> <plugin name="SplashScreen" value="org.apache.cordova.SplashScreen" /> <plugin name="Echo" value="org.apache.cordova.Echo" /> <plugin name="Globalization" value="org.apache.cordova.Globalization" /> </plugins> </cordova>
其中,<access origin="http://127.0.0.1*" />就是加的白名單,咱們只須要將網址後面加上*,上格式加進配置文件便可。apache
那麼PhoneGap又是如何實現白名單的呢?讓咱們看一下源代碼:CordovaWebView.java。CordovaWebView是顯示的WebView的基類。它在初始化時,會加載配置文件的配置項,源代碼以下:瀏覽器
/** * Load Cordova configuration from res/xml/cordova.xml. * Approved list of URLs that can be loaded into DroidGap * <access origin="http://server regexp" subdomains="true" /> * Log level: ERROR, WARN, INFO, DEBUG, VERBOSE (default=ERROR) * <log level="DEBUG" /> */ private void loadConfiguration() { int id = getResources().getIdentifier("config", "xml", this.cordova.getActivity().getPackageName()); if(id == 0) { id = getResources().getIdentifier("cordova", "xml", this.cordova.getActivity().getPackageName()); Log.i("CordovaLog", "config.xml missing, reverting to cordova.xml"); } if (id == 0) { LOG.i("CordovaLog", "cordova.xml missing. Ignoring..."); return; } XmlResourceParser xml = getResources().getXml(id); int eventType = -1; while (eventType != XmlResourceParser.END_DOCUMENT) { if (eventType == XmlResourceParser.START_TAG) { String strNode = xml.getName(); if (strNode.equals("access")) { String origin = xml.getAttributeValue(null, "origin"); String subdomains = xml.getAttributeValue(null, "subdomains"); if (origin != null) { this.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0)); } } else if (strNode.equals("log")) { String level = xml.getAttributeValue(null, "level"); LOG.i("CordovaLog", "Found log level %s", level); if (level != null) { LOG.setLogLevel(level); } } else if (strNode.equals("preference")) { String name = xml.getAttributeValue(null, "name"); String value = xml.getAttributeValue(null, "value"); LOG.i("CordovaLog", "Found preference for %s=%s", name, value); Log.d("CordovaLog", "Found preference for " + name + "=" + value); // Save preferences in Intent this.cordova.getActivity().getIntent().putExtra(name, value); } } try { eventType = xml.next(); } catch (XmlPullParserException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } // Init preferences if ("true".equals(this.getProperty("useBrowserHistory", "false"))) { this.useBrowserHistory = true; } else { this.useBrowserHistory = false; } if ("true".equals(this.getProperty("fullscreen", "false"))) { this.cordova.getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); this.cordova.getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); } }
在解析xml文件時,會將origin標籤中的內容加入白名單,調用的是addWhiteListEntry方法。下面咱們來看看addWhiteListEntry方法的源代碼:安全
public void addWhiteListEntry(String origin, boolean subdomains) { try { // Unlimited access to network resources if (origin.compareTo("*") == 0) { LOG.d(TAG, "Unlimited access to network resources"); this.whiteList.add(Pattern.compile(".*")); } else { // specific access // check if subdomains should be included // TODO: we should not add more domains if * has already been added if (subdomains) { // XXX making it stupid friendly for people who forget to include protocol/SSL if (origin.startsWith("http")) { this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://(.*\\.)?"))); } else { this.whiteList.add(Pattern.compile("^https?://(.*\\.)?" + origin)); } LOG.d(TAG, "Origin to allow with subdomains: %s", origin); } else { // XXX making it stupid friendly for people who forget to include protocol/SSL if (origin.startsWith("http")) { this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://"))); } else { this.whiteList.add(Pattern.compile("^https?://" + origin)); } LOG.d(TAG, "Origin to allow: %s", origin); } } } catch (Exception e) { LOG.d(TAG, "Failed to add origin %s", origin); } }
咱們能夠看到,它用正則表達式解析後將白名單中的url加入到whiteList這個屬性中,而whiteList是個ArrayList類型的屬性。
那麼PhoneGap的Web app在顯示網頁時又是如何利用白名單的呢?讓咱們繼續來看下面的源代碼,這是加載網頁時會調用的方法:app
/** * Load the specified URL in the Cordova webview or a new browser instance. * * NOTE: If openExternal is false, only URLs listed in whitelist can be loaded. * * @param url The url to load. * @param openExternal Load url in browser instead of Cordova webview. * @param clearHistory Clear the history stack, so new page becomes top of history * @param params DroidGap parameters for new app */ public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap<String, Object> params) { LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap", url, openExternal, clearHistory); // If clearing history if (clearHistory) { this.clearHistory(); } // If loading into our webview if (!openExternal) { // Make sure url is in whitelist if (url.startsWith("file://") || url.indexOf(this.baseUrl) == 0 || isUrlWhiteListed(url)) { // TODO: What about params? // Clear out current url from history, since it will be replacing it if (clearHistory) { this.urls.clear(); } // Load new URL this.loadUrl(url); } // Load in default viewer if not else { LOG.w(TAG, "showWebPage: Cannot load URL into webview since it is not in white list. Loading into browser instead. (URL=" + url + ")"); try { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); cordova.getActivity().startActivity(intent); } catch (android.content.ActivityNotFoundException e) { LOG.e(TAG, "Error loading url " + url, e); } } } // Load in default view intent else { try { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); cordova.getActivity().startActivity(intent); } catch (android.content.ActivityNotFoundException e) { LOG.e(TAG, "Error loading url " + url, e); } } }
咱們能夠看到,裏面會用isUrlWhiteListed等方法判斷該url是否在白名單中,或者是否安全,而後對安全的url直接經過loadUrl來加載進該Web app,對於PhoneGap認爲不安全的url會經過發Intent的形式打開瀏覽器加載該網頁。
下面再貼一段isUrlWhiteListed方法的源代碼:less
/** * Determine if URL is in approved list of URLs to load. * * @param url * @return */ public boolean isUrlWhiteListed(String url) { // Check to see if we have matched url previously if (this.whiteListCache.get(url) != null) { return true; } // Look for match in white list Iterator<Pattern> pit = this.whiteList.iterator(); while (pit.hasNext()) { Pattern p = pit.next(); Matcher m = p.matcher(url); // If match found, then cache it to speed up subsequent comparisons if (m.find()) { this.whiteListCache.put(url, true); return true; } } return false; }