재인쇄됨: Android webview는 https에서 SSL 양방향 인증을 구현합니다.

APP 애플리케이션 보안 보고서 WebView에 HTTPS 확인 위험이 없는 문제를 해결합니다 .

1. 개요
1. 소개
Https는 단순히 Http의 보안 버전입니다.Https 프로토콜은 SSL+Http 프로토콜로 구성된 네트워크 프로토콜로 암호화된 전송 및 ID 인증을 수행할 수 있으며 Http 프로토콜보다 더 안전합니다.

여기에 언급된 보안은 SSL에 의존하며 SSL의 기능은 다음과 같습니다.

데이터가 올바른 클라이언트와 서버로 전송되도록 사용자와 서버를 인증합니다. (인증서 검증)
나. 데이터 전송 시 데이터가 도용되지 않도록 데이터를 암호화합니다. (암호화)
c.데이터 무결성을 유지하고 전송 중에 데이터가 변경되지 않도록 합니다. (다이제스트 알고리즘)
Https는 데이터를 전송하기 전에 클라이언트와 서버 간에 핸드셰이크가 필요하며 핸드셰이크가 통과된 후에야 데이터 전송 프로세스가 진행됩니다. 이 핸드셰이크는 위에서 언급한 SSL의 첫 번째 기능으로 사용자와 서버를 인증하는 것으로 인증서로 구현되는 인증 방식입니다.

위의 내용은 Https에 대한 간략한 소개일 뿐이며 보다 포괄적이고 구체적인 개요는 Baidu로 안내할 수 있습니다.

2. 이 기사의 초점
이 기사의 초점은 SSL 인증서의 양방향 인증, 즉 클라이언트와 서버가 악수하는 것을 실현하는 것입니다.

포함하다:

클라이언트 및 서버 인증서를 생성합니다.
b. Https를 지원하는 서버를 구축합니다.
c. webview의 양방향 인증서 인증을 실현합니다.
2. 인증서 생성
이 기사에서는 자체 서명된 인증서를 사용하는데 왜 기관에서 발급한 인증서를 사용하지 않습니까? 그들은 돈을 원하기 때문에! ! ! 물론 회사에서 신청하면 가장 좋습니다. 다음은 서버측 자체 서명된 인증서를 생성합니다.

인증서를 생성하는 방법은 무엇입니까? JDK에는 매우 간단한 keytool 도구가 있습니다. JDK가 설치되어 있으면 인증서를 생성할 수 있습니다. 명령줄로 직접 이동합니다.

keytool -genkey -alias zx_server -keyalg RSA -keystore D:\key\zx_server.jks -validity 3600 -storepass 123456

위의 명령을 사용하여 D 디스크의 "key" 폴더에 서버측 인증서 저장소 파일 zx_server.jks를 생성하고 해당 키 저장소 암호는 123456입니다.

다음으로 zx_server.jks 인증서 라이브러리를 사용하여 클라이언트에서 사용할 수 있는 서버측 인증서를 생성합니다.

keytool -export -alias zx_server -file D:\key\zx_server.cer -keystore D:\key\zx_server.jks -storepass 123456

공개 키를 포함하는 서버측 인증서 zx_server.cer이 생성됩니다.

3. Tomcat을 다운로드하고 자체 서명된 인증서를 사용하여 Https를 구성하여
Tomcat을 먼저 다운로드합니다. 이 문서에서는 tomcat 7을 사용합니다. 주소는 http://tomcat.apache.org/download-70.cgi입니다.

압축된 패키지를 받은 후 tomcat/config/server.xml 파일을 찾아 텍스트 형식으로 엽니다.

Servcie 태그 아래에 다음 태그를 추가합니다.

<Connector SSLEnabled="true" 
			acceptCount="100" 
			disableUploadTimeout="true" 
			enableLookups="true" 
			keystoreFile="D:/key/zx_server.jks" 
			keystorePass="123456" 
			maxSpareThreads="75" 
			maxThreads="200" 
			minSpareThreads="5" port="8443" 
			protocol="org.apache.coyote.http11.Http11NioProtocol" scheme="https" 
			secure="true" 
			sslProtocol="TLS"
			clientAuth="false" /> 

참고: keystoreFile은 방금 생성된 zx_server.jks 파일의 경로(여기에 자신의 경로를 입력)이고 keystorePass의 값은 키 저장소 암호(123456)입니다.

이제 Https를 지원하는 tomcat 서버가 구성되었습니다. 간단합니다. tomcat/bin/startup.bat를 찾아 두 번 클릭하여 서버를 직접 시작합니다.

성공적으로 시작되면 브라우저를 열고 https://localhost:8443/을 입력하면 인증서를 신뢰할 수 없다는 경고가 표시되고 이를 무시하고 직접 입력하면 tomcat의 기본 홈 페이지가 표시됩니다.

넷째, webview는 양방향 인증을 구현합니다.
이 단계는 이 기사의 초점입니다. 위의 단계를 통해 https 서버가 설정되었으며 이제 양방향 인증을 구현해야 합니다. 즉, 클라이언트에도 "jks 파일"이 있고 서버에는 "cer 파일"이 있어야 합니다. 이에 대응하여 추상적으로 말하면 서버 측을 인증하는 것은 클라이언트 측입니다.

위 단계에서 zx_server.jks 및 zx_server.cer 파일이 생성되었습니다.위 인증서 생성 방법에 따라 zx_client.jks 및 zx_client.cer라는 두 개의 클라이언트 파일을 생성합니다. 이제 클라이언트 인증서를 구성합니다.

1. 서버 구성 서버
구성은 매우 간단합니다 위 3단계에서 tomcat 구성 시 추가한 태그에 몇 가지 속성을 추가합니다.

<Connector SSLEnabled="true" 
			acceptCount="100" 
			disableUploadTimeout="true" 
			enableLookups="true" 
			keystoreFile="D:/key/zx_server.jks" 
			keystorePass="123456" 
			maxSpareThreads="75" 
			maxThreads="200" 
			minSpareThreads="5" port="8443" 
			protocol="org.apache.coyote.http11.Http11NioProtocol" scheme="https" 
			secure="true" 
			sslProtocol="TLS"
			clientAuth="true"
			truststoreFile="D:/key/zx_client.cer" /> 

위 레이블의 나머지 부분은 변경되지 않고 레이블 끝에 clientAuth만 true로 설정되고 추가 속성 truststoreFile이 추가됩니다. 이 속성은 방금 생성한 zx_client.cer 파일이어야 하지만 오류가 보고됩니다. 서버를 추가한 후 시작합니다. 이제 zx_client.cer 파일을 새로 생성된 jks 파일에 추가합니다.
keytool -import -alias D:\key\zx_client -file D:\key\zx_client.cer -keystore D:\key\zx_client_for_sever.jks

이제 Server.xml 파일을 수정하고 truststoreFile 속성 값을 방금 생성한 zx_client_for_sever.jks 파일로 변경합니다.

<Connector //其它属性不变
			clientAuth="true"
			truststoreFile="D:/key/zx_client_for_sever.jks" /> 

이때 tomcat을 다시 시작하고 브라우저를 사용하여 지금 바로 주소를 방문하면 "localhost가 로그인 인증서를 수락하지 않거나 로그인 인증서가 만료되었을 수 있습니다."라는 메시지가 표시됩니다.

이제 더 이상 브라우저로 서버에 액세스할 수 없습니다. 다음으로 액세스할 수 있도록 Android 측을 구성하겠습니다.


2. webview https 인증서의 양방향 인증을 구현하도록 앱을 구성하고 일반적인 구현 아이디어에 대해 이야기합니다.

a. 코드의 인증서에 대한 신뢰 인증 수행
b. WebViewClient의 shouldInterceptRequest 메서드를 다시 작성하고, WebView의 요청 요청을 가로채고, HttpsUrlConnection이 SSL을 설정하는 SocketFactory를 얻고, 이 HttpsUrlConnection을 사용하여 데이터를 가로챈 다음 새 WebResourceResponse를 WebView에 반환합니다. .

위의 아이디어에 따라 시작하기 전에 문제를 해결하십시오.Android 플랫폼은 bks 형식의 인증서 파일만 인식할 수 있습니다. 이제 jks 파일을 변환해야 합니다.

변환하는 방법? 여러 가지 방법이 있습니다. 다음은 portecle을 사용하여 주소를 다운로드하는 것입니다. https://sourceforge.net/projects/portecle/?source=typ_redirect

다운로드 후 파일의 압축을 풀고 portecle.jar 파일을 찾아 더블 클릭하여 실행하면 기본적으로 변환할 jks 파일 선택 -> 비밀번호 입력 -> 변환할 형식 선택 -> 저장 파일, 일반적인 단계는 다음과 같습니다.

a. jks 파일을 선택합니다.

B. 비밀번호 입력

c. 변환 형식을 선택하고 암호를 다시 입력하십시오.

d. 변환이 성공했다는 메시지가 표시되면 바로 저장하십시오.

단계는 매우 간단하지만 일반적인 단계는 계속 게시됩니다.

이제 zx_client.bks 파일이 생성되고 위에서 생성된 zx_server.cer 파일을 찾아 이 두 파일을 Android assets 폴더에 넣습니다.

그런 다음 WebViewClient를 다시 작성하여 이 두 인증서를 도입하고 코드를 직접 붙여넣습니다.

package com.zx.webview_ssl;
 
import android.annotation.TargetApi;
import android.net.Uri;
import android.os.Build;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
 
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
 
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
 
public class SslWebViewClient extends WebViewClient {
    
    
 
    private SSLContext sslContext;
 
    public SslWebViewClient() {
    
    
        try {
    
    
            TrustManager[] trustManagers = prepareTrustManager(MyApplication.mContext.getResources().getAssets().open("zx_server.cer"));
            KeyManager[] keyManagers = prepareKeyManager(MyApplication.mContext.getResources().getAssets().open("zx_client.bks"), "123456");
            sslContext = SSLContext.getInstance("TLS");
            X509TrustManager trustManager = null;
            if (null != trustManagers){
    
    
                trustManager = new MyTrustManager(chooseTrustManager(trustManagers));
            }else {
    
    
                trustManager = new UnSafeTrustManager();
            }
            sslContext.init(keyManagers, new TrustManager[]{
    
    trustManager}, new SecureRandom());
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
    
    
            e.printStackTrace();
        } catch (KeyStoreException e) {
    
    
            e.printStackTrace();
        } catch (KeyManagementException e) {
    
    
            e.printStackTrace();
        }
    }
 
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
    
    
        return processRequest(Uri.parse(url));
    }
 
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
    
    
        return processRequest(request.getUrl());
    }
 
    private WebResourceResponse processRequest(Uri uri) {
    
    
        try {
    
    
            //设置连接
            URL url = new URL(uri.toString());
            HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
            //为request设置SSL Socket Factory
            urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
 
            urlConnection.setHostnameVerifier(new HostnameVerifier() {
    
    
                @Override
                public boolean verify(String hostname, SSLSession session) {
    
    
                    return true;
                }
            });
 
            //获取请求的内容、contentType、encoding
            InputStream inputStream = urlConnection.getInputStream();
            String contentType = urlConnection.getContentType();
            String encoding = urlConnection.getContentEncoding();
            if (null != contentType){
    
    
                String mimeType = contentType;
                if (contentType.contains(";")){
    
    
                    mimeType = contentType.split(";")[0].trim();
                }
                //返回新的response
                return new WebResourceResponse(mimeType, encoding, inputStream);
            }
 
        } catch (MalformedURLException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        return null;
    }
 
    private TrustManager[] prepareTrustManager(InputStream... certificates) {
    
    
        if (certificates == null || certificates.length <= 0){
    
    
            return null;
        }
        try {
    
    
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates) {
    
    
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
                try {
    
    
                    if (certificate != null)
                        certificate.close();
                } catch (IOException e){
    
    
 
                }
            }
            TrustManagerFactory trustManagerFactory = null;
            trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            return trustManagers;
        } catch (NoSuchAlgorithmException e) {
    
    
            e.printStackTrace();
        } catch (CertificateException e) {
    
    
            e.printStackTrace();
        } catch (KeyStoreException e) {
    
    
            e.printStackTrace();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return null;
 
    }
 
    private KeyManager[] prepareKeyManager(InputStream bksFile, String password) {
    
    
        try {
    
    
            if (bksFile == null || password == null){
    
    
                return null;
            }
            KeyStore clientKeyStore = KeyStore.getInstance("BKS");
            clientKeyStore.load(bksFile, password.toCharArray());
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(clientKeyStore, password.toCharArray());
            return keyManagerFactory.getKeyManagers();
        } catch (KeyStoreException e) {
    
    
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
    
    
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
    
    
            e.printStackTrace();
        } catch (CertificateException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return null;
    }
 
    private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {
    
    
        for (TrustManager trustManager : trustManagers) {
    
    
            if (trustManager instanceof X509TrustManager) {
    
    
                return (X509TrustManager) trustManager;
            }
        }
        return null;
    }
 
    public static class MyTrustManager implements X509TrustManager{
    
    
        private X509TrustManager defaultTrustManager;
        private X509TrustManager localTrustManager;
 
        public MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException {
    
    
            TrustManagerFactory var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            var4.init((KeyStore) null);
            defaultTrustManager = chooseTrustManager(var4.getTrustManagers());
            this.localTrustManager = localTrustManager;
        }
 
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
    
 
        }
 
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
    
            try {
    
    
                defaultTrustManager.checkServerTrusted(chain, authType);
            } catch (CertificateException ce) {
    
    
                localTrustManager.checkServerTrusted(chain, authType);
            }
        }
 
        @Override
        public X509Certificate[] getAcceptedIssuers() {
    
    
            return new X509Certificate[0];
        }
    }
 
    public static class UnSafeTrustManager implements X509TrustManager {
    
    
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
    
    
        }
 
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
    
    
        }
 
        @Override
        public X509Certificate[] getAcceptedIssuers() {
    
    
            return new java.security.cert.X509Certificate[]{
    
    };
        }
    }
 
 
}

코드가 꽤 긴데 핵심 부분은 각각 41행과 42행에서 호출되는 prepareTrustManager와 prepareKeyManager 메소드라고 간단히 설명하겠습니다. 이 두 메소드는 각각 zx_client.cer과 zx_client.bks 인증서를 초기화한 다음 두 인증서를 실행합니다 연결을 설정한 다음 79행에서 HttpsUrlConnection에 대한 SSLSocketFactory를 설정합니다. 이 SSLSocketFactory는 50행에서 초기화된 SSLContext에서 가져옵니다. 이때 요청은 안전하며 최종적으로 98행에서 새 응답을 반환합니다. 이러한 방식으로 위에서 언급한 아이디어가 기본적으로 실현되어 WebView의 SSL 양방향 인증을 실현합니다.

마지막으로 휴대폰의 실행 효과를 살펴보겠습니다.

성공적으로 tomcat 홈페이지에 접속한 것을 확인할 수 있습니다.

5. 마지막으로
일부 뱅킹 또는 금융 앱과 같이 매우 적은 수의 애플리케이션에만 양방향 인증이 필요합니다. 위의 단계를 따르는 한 https에서 webview의 SSL 양방향 인증을 실현할 수 있습니다.

소스 코드: https://github.com/xunzzz/WebView_SSL

참고:

http://www.cnblogs.com/devinzhang/archive/2012/02/28/2371631.html
http://blog.csdn.net/kpioneer123/article/details/51491739
http://blog.csdn.net/ lmj623565791/기사/세부정보/48129405

추천

출처blog.csdn.net/u013290250/article/details/121379479