【问题标题】:How do I add custom SSL certificate validation to osmdroid's MapTileDownloader?如何将自定义 SSL 证书验证添加到 osmdroid 的 MapTileDownloader?
【发布时间】:2012-08-28 13:42:59
【问题描述】:

我正在尝试从内部 SSL 服务器加载地图图块。 Android 系统无法识别 SSL 证书的信任根。

W/o*.o*.t*.m*.MapTileDow*(2837): IOException 下载 MapTile: /8/37/4 : javax.net.ssl.SSLPeerUnverifiedException:没有对等证书

我已经熟悉这个问题,并在应用程序based on this excellent SO answer 的其余部分中解决了它。本质上,我扩展了我自己的SSLSocketFactoryX509TrustManager,它们从与应用程序捆绑的.bks 文件中加载我的SSL 证书的信任根。为了创建安全连接,我调用了((HttpsURLConnection) connection).setSSLSocketFactory(mySSLSocketFactory),并使用我的类和我的信任根来验证证书。

我的问题是如何为 osmdroid 做同样的事情?我正在创建自己的XYTileSource,在其中设置我的地图图块的 URL、文件扩展名、大小等。我看到 osmdroid 创建了它的连接以在MapTileDownloader 中下载地图平铺图像。我可以编写自己的替换类,以同样的方式解决 SSL 问题,但我如何告诉 osmdroid 使用我的自定义下载器而不是默认下载器?

【问题讨论】:

    标签: java android ssl osmdroid


    【解决方案1】:

    事实证明,由于 public MapView(Context context, int tileSizePixels, ResourceProxy resourceProxy, MapTileProviderBase aTileProvider) 构造函数,这在不改变 osmdroid 源的情况下是可能的。

    假设您已经有一个自定义类,如 MySSLSocketFactory(其中extends javax.net.ssl.SSLSocketFactory),基本过程如下所示:

    1. MapTileDownloader 创建一个插入式替换类,以利用MySSLSocketFactory 的方式执行下载。我们称之为 MyTileDownloader

    2. MapTileProviderBasic 创建一个替换类,用于实例化您的自定义 MyTileDownloader。我们称之为 MyTileProvider

    3. 将您的磁贴源实例化为new XYTileSource(无需编写自定义类)。

    4. 用您的磁贴源实例实例化 MyTileProvider

    5. 使用您的磁贴提供程序实例实例化 MapVew


    MySSLSocketFactory 留给读者作为练习。见this post


    MyTileDownloader 看起来像这样:

    import java.io.BufferedInputStream;
    import java.io.BufferedOutputStream;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.net.UnknownHostException;
    
    import javax.net.ssl.HttpsURLConnection;
    import javax.net.ssl.SSLSocketFactory;
    
    import org.osmdroid.tileprovider.MapTile;
    import org.osmdroid.tileprovider.MapTileRequestState;
    import org.osmdroid.tileprovider.modules.IFilesystemCache;
    import org.osmdroid.tileprovider.modules.INetworkAvailablityCheck;
    import org.osmdroid.tileprovider.modules.MapTileDownloader;
    import org.osmdroid.tileprovider.modules.MapTileModuleProviderBase;
    import org.osmdroid.tileprovider.tilesource.BitmapTileSourceBase.LowMemoryException;
    import org.osmdroid.tileprovider.tilesource.ITileSource;
    import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase;
    import org.osmdroid.tileprovider.util.StreamUtils;
    
    import android.graphics.drawable.Drawable;
    import android.text.TextUtils;
    import android.util.Log;
    
    /**
     * A drop-in replacement for {@link MapTileDownloader}. This loads tiles from an
     * HTTP or HTTPS server, making use of a custom {@link SSLSocketFactory} for SSL
     * peer verification.
     */
    public class MyTileDownloader extends MapTileModuleProviderBase {
        private static final String TAG = "MyMapTileDownloader";
    
        protected OnlineTileSourceBase mTileSource;
        protected final IFilesystemCache mFilesystemCache;
        protected final INetworkAvailablityCheck mNetworkAvailablityCheck;
        protected final SSLSocketFactory mSSLSocketFactory;
    
        public MyTileDownloader(ITileSource pTileSource,
                IFilesystemCache pFilesystemCache,
                INetworkAvailablityCheck pNetworkAvailablityCheck,
                SSLSocketFactory pSSLSocketFactory) {
            super(4, TILE_DOWNLOAD_MAXIMUM_QUEUE_SIZE);
            setTileSource(pTileSource);
            mFilesystemCache = pFilesystemCache;
            mNetworkAvailablityCheck = pNetworkAvailablityCheck;
            mSSLSocketFactory = pSSLSocketFactory;
        }
    
        public ITileSource getTileSource() {
            return mTileSource;
        }
    
        @Override
        public void setTileSource(final ITileSource tileSource) {
            // We are only interested in OnlineTileSourceBase tile sources
            if (tileSource instanceof OnlineTileSourceBase)
                mTileSource = (OnlineTileSourceBase) tileSource;
            else
                mTileSource = null;
        }
    
        @Override
        public boolean getUsesDataConnection() {
            return true;
        }
    
        @Override
        protected String getName() {
            return "Online Tile Download Provider";
        }
    
        @Override
        protected String getThreadGroupName() {
            return "downloader";
        }
    
        @Override
        public int getMinimumZoomLevel() {
            return (mTileSource != null ? mTileSource.getMinimumZoomLevel()
                    : MINIMUM_ZOOMLEVEL);
        }
    
        @Override
        public int getMaximumZoomLevel() {
            return (mTileSource != null ? mTileSource.getMaximumZoomLevel()
                    : MAXIMUM_ZOOMLEVEL);
        }
    
        @Override
        protected Runnable getTileLoader() {
            return new TileLoader();
        };
    
        private class TileLoader extends MapTileModuleProviderBase.TileLoader {
            @Override
            public Drawable loadTile(final MapTileRequestState aState)
                    throws CantContinueException {
                if (mTileSource == null)
                    return null;
    
                InputStream in = null;
                OutputStream out = null;
                final MapTile tile = aState.getMapTile();
    
                try {
                    if (mNetworkAvailablityCheck != null
                            && !mNetworkAvailablityCheck.getNetworkAvailable()) {
                        if (DEBUGMODE)
                            Log.d(TAG, "Skipping " + getName()
                                    + " due to NetworkAvailabliltyCheck.");
                        return null;
                    }
    
                    final String tileURLString = mTileSource.getTileURLString(tile);
                    if (DEBUGMODE)
                        Log.d(TAG, "Downloading Maptile from url: " + tileURLString);
    
                    if (TextUtils.isEmpty(tileURLString))
                        return null;
    
                    // Create an HttpURLConnection to download the tile
                    URL url = new URL(tileURLString);
                    HttpURLConnection connection = (HttpURLConnection) url
                            .openConnection();
                    connection.setConnectTimeout(30000);
                    connection.setReadTimeout(30000);
    
                    // Use our custom SSLSocketFactory for secure connections
                    if ("https".equalsIgnoreCase(url.getProtocol()))
                        ((HttpsURLConnection) connection)
                                .setSSLSocketFactory(mSSLSocketFactory);
    
                    // Open the input stream
                    in = new BufferedInputStream(connection.getInputStream(),
                            StreamUtils.IO_BUFFER_SIZE);
    
                    // Check to see if we got success
                    if (connection.getResponseCode() != 200) {
                        Log.w(TAG, "Problem downloading MapTile: " + tile
                                + " HTTP response: " + connection.getHeaderField(0));
                        return null;
                    }
    
                    // Read the tile into an in-memory byte array
                    final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
                    out = new BufferedOutputStream(dataStream,
                            StreamUtils.IO_BUFFER_SIZE);
                    StreamUtils.copy(in, out);
                    out.flush();
                    final byte[] data = dataStream.toByteArray();
                    final ByteArrayInputStream byteStream = new ByteArrayInputStream(
                            data);
    
                    // Save the data to the filesystem cache
                    if (mFilesystemCache != null) {
                        mFilesystemCache.saveFile(mTileSource, tile, byteStream);
                        byteStream.reset();
                    }
                    final Drawable result = mTileSource.getDrawable(byteStream);
                    return result;
    
                } catch (final UnknownHostException e) {
                    Log.w(TAG, "UnknownHostException downloading MapTile: " + tile
                            + " : " + e);
                    throw new CantContinueException(e);
    
                } catch (final LowMemoryException e) {
                    Log.w(TAG, "LowMemoryException downloading MapTile: " + tile
                            + " : " + e);
                    throw new CantContinueException(e);
    
                } catch (final FileNotFoundException e) {
                    Log.w(TAG, "Tile not found: " + tile + " : " + e);
    
                } catch (final IOException e) {
                    Log.w(TAG, "IOException downloading MapTile: " + tile + " : "
                            + e);
    
                } catch (final Throwable e) {
                    Log.e(TAG, "Error downloading MapTile: " + tile, e);
    
                } finally {
                    StreamUtils.closeStream(in);
                    StreamUtils.closeStream(out);
                }
                return null;
            }
    
            @Override
            protected void tileLoaded(final MapTileRequestState pState,
                    final Drawable pDrawable) {
                // Don't return the tile Drawable because we'll wait for the fs
                // provider to ask for it. This prevent flickering when a load
                // of delayed downloads complete for tiles that we might not
                // even be interested in any more.
                super.tileLoaded(pState, null);
            }
        }
    }
    

    MyTileProvider 看起来像这样。

    请注意,您需要一种方法来访问此类中的 MySSLSocketFactory 实例。这留给读者作为练习。我使用app.getSSLSocketFactory() 进行了此操作,其中appextends Application 的自定义类的一个实例,但您的里程可能会有所不同。

    import javax.net.ssl.SSLSocketFactory;
    
    import org.osmdroid.tileprovider.IMapTileProviderCallback;
    import org.osmdroid.tileprovider.IRegisterReceiver;
    import org.osmdroid.tileprovider.MapTileProviderArray;
    import org.osmdroid.tileprovider.MapTileProviderBasic;
    import org.osmdroid.tileprovider.modules.INetworkAvailablityCheck;
    import org.osmdroid.tileprovider.modules.MapTileFileArchiveProvider;
    import org.osmdroid.tileprovider.modules.MapTileFilesystemProvider;
    import org.osmdroid.tileprovider.modules.NetworkAvailabliltyCheck;
    import org.osmdroid.tileprovider.modules.TileWriter;
    import org.osmdroid.tileprovider.tilesource.ITileSource;
    import org.osmdroid.tileprovider.util.SimpleRegisterReceiver;
    
    import android.content.Context;
    
    /**
     * A drop-in replacement for {@link MapTileProviderBasic}. This top-level tile
     * provider implements a basic tile request chain which includes a
     * {@link MapTileFilesystemProvider} (a file-system cache), a
     * {@link MapTileFileArchiveProvider} (archive provider), and a
     * {@link MyTileDownloader} (downloads map tiles via tile source).
     */
    public class MyTileProvider extends MapTileProviderArray implements
            IMapTileProviderCallback {
        public MyTileProvider(final Context pContext, final ITileSource pTileSource) {
            this(new SimpleRegisterReceiver(pContext),
                    new NetworkAvailabliltyCheck(pContext), pTileSource, app
                            .getSSLSocketFactory());
        }
    
        protected MyTileProvider(final IRegisterReceiver pRegisterReceiver,
                final INetworkAvailablityCheck aNetworkAvailablityCheck,
                final ITileSource pTileSource,
                final SSLSocketFactory pSSLSocketFactory) {
            super(pTileSource, pRegisterReceiver);
    
            // Look for raw tiles on the file system
            final MapTileFilesystemProvider fileSystemProvider = new MapTileFilesystemProvider(
                    pRegisterReceiver, pTileSource);
            mTileProviderList.add(fileSystemProvider);
    
            // Look for tile archives on the file system
            final MapTileFileArchiveProvider archiveProvider = new MapTileFileArchiveProvider(
                    pRegisterReceiver, pTileSource);
            mTileProviderList.add(archiveProvider);
    
            // Look for raw tiles on the Internet
            final TileWriter tileWriter = new TileWriter();
            final MyTileDownloader downloaderProvider = new MyTileDownloader(
                    pTileSource, tileWriter, aNetworkAvailablityCheck,
                    pSSLSocketFactory);
            mTileProviderList.add(downloaderProvider);
        }
    }
    

    最后,实例化看起来像这样:

    XYTileSource tileSource = new XYTileSource("MapQuest", null, 3, 8, 256, ".jpg",
        "https://10.0.0.1/path/to/your/map/tiles/");
    MapTileProviderBase tileProvider = new MyTileProvider(context, tileSource);
    ResourceProxy resourceProxy = new DefaultResourceProxyImpl(context);
    MapView mapView = new MapView(context, 256, resourceProxy, tileProvider);
    

    【讨论】:

    • 这个答案对实现自定义 TileProvider 的基本身份验证很有帮助。
    【解决方案2】:

    我不使用 osmdroid,但除非它具有替换下载器类的公共接口,否则最好的办法是获取源并对其进行修补以使其可配置或使用您自己的下载器类。如果MapTileDownloader 实现了某个接口,您可能会做一些反射巫术来在运行时替换它,但这可能会产生未知的副作用。

    【讨论】:

      猜你喜欢
      • 2019-07-21
      • 1970-01-01
      • 2021-12-20
      • 2014-12-03
      • 1970-01-01
      • 2020-10-31
      • 1970-01-01
      • 1970-01-01
      • 2022-01-09
      相关资源
      最近更新 更多