zoukankan      html  css  js  c++  java
  • Android Socket与HTTPS校验

    在Android中使用HTTPS的场景比较频繁,所以对于HTTPS的证书应该如何校验呢?关于HTTPS的校验原理可以参考我之前写的一篇文章:《 HTTPS协议实现原理 》,相信看完后应该对HTTPS有一个比较大致的了解。而且对HTTP(s)请求的工具进行了封装,需要体会这种封装工具类的思路,也就是编码中常见的Listener机制。然后是Android中TCP、UDP通信的例子,主要是把Android设备作为Client端,如果对Java的Socket编程比较熟悉的话,这些都是特别简单的示例程序,非常容易看懂。

    TCP/UDP 简单示例

    下面的例子演示了Client向Server发送了一串小写英文,Server返回大写字符串的功能:

    UDPServer.java:

    public class UDPServer {
        private static final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
        public static void main(String[] args) throws Exception {
            DatagramSocket datagramSocket;
            datagramSocket = new DatagramSocket(8090);
            byte[] buf;
            DatagramPacket packet;
            while (true){
                buf = new byte[1024];
                packet = new DatagramPacket(buf, buf.length);
                datagramSocket.receive(packet);
                String content = new String(packet.getData());
                InetAddress address = packet.getAddress();
                System.out.println(format.format(new Date()) + "-" + address + "-" + content);
                int port = packet.getPort();
                String replyContent = content.toUpperCase();
                byte[] sendData = replyContent.getBytes();
                DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, address, port);
                datagramSocket.send(sendPacket);
            }
        }
    }
    

    UDPClient.java:

    public class UDPClient {
        private static final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
        public static void main(String[] args) throws Exception {
            System.out.println("请输入一句英文,服务器会返回其大写形式[exit退出]");
            Scanner scanner = new Scanner(System.in);
            InetAddress address = InetAddress.getLocalHost();
            DatagramPacket packet;
            DatagramSocket socket = new DatagramSocket();
            while(true){
                String line = scanner.nextLine();
                if("exit".equals(line)) break;
                byte[] bytes = line.getBytes();
                packet = new DatagramPacket(bytes, bytes.length, address, 8090);
                socket.send(packet);
                byte[] recvBuf = new byte[1024];
                DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length);
                socket.receive(recvPacket);
                System.out.println(format.format(new Date()) + "-" + address + "-" + new String(recvBuf));
            }
            socket.close();
        }
    }
    

    TCPServer.java:

    public class TCPServer {
        static SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
        public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket(9090);
            while (true){
                Socket socket = serverSocket.accept();
                InetAddress address = socket.getInetAddress();
                InputStream is = socket.getInputStream();
                byte[] readBuf = new byte[1024];
                try{
                    int len = is.read(readBuf);
                    String recv = new String(readBuf, 0, len);
                    System.out.println(format.format(new Date()) + "-" + address + "-" + recv);
                    OutputStream os = socket.getOutputStream();
                    os.write(recv.toUpperCase().getBytes());
                } catch (SocketException e){
                    System.err.println("客户端未发送信息");
                } finally {
                    socket.close();
                }
            }
        }
    }
    

    TCPClient.java:

    public class TCPClient {
        private static final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
        public static void main(String[] args) throws Exception {
            System.out.println("请输入一句英文,服务器会返回其大写形式[exit退出]");
            Scanner scanner = new Scanner(System.in);
            while(true){
                Socket socket = new Socket("127.0.0.1", 9090);
                String line = scanner.nextLine();
                if("exit".equals(line)) break;
                OutputStream os = socket.getOutputStream();
                os.write(line.getBytes());
                InputStream is = socket.getInputStream();
                byte[] readBuf = new byte[1024];
                String recv = new String(readBuf, 0, is.read(readBuf));
                InetAddress address = socket.getInetAddress();
                System.out.println(format.format(new Date()) + "-" + address + "-" + recv);
                socket.close();
            }
        }
    }
    

    Client移植到Android

    将两个Client移植到Android:

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="10dp"
        tools:context=".MainActivity">
    
        <EditText
            android:hint="输入发送内容"
            android:id="@+id/et_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <EditText
                android:text="192.168.1.113:8090"
                android:id="@+id/et_udp_server"
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="wrap_content">
            </EditText>
            <Button
                android:text="UDP发送"
                android:onClick="sendUdpMessage"
                android:layout_weight="1"
                android:layout_width="0dp"
                android:layout_height="wrap_content"/>
        </LinearLayout>
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <EditText
                android:text="192.168.1.113:9090"
                android:id="@+id/et_tcp_server"
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="wrap_content">
            </EditText>
            <Button
                android:text="TCP发送"
                android:onClick="sendTcpMessage"
                android:layout_weight="1"
                android:layout_width="0dp"
                android:layout_height="wrap_content"/>
        </LinearLayout>
        <TextView
            android:id="@+id/tv_show"
            android:text="收到回复:"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
    

    MainActivity.java:

    public class MainActivity extends AppCompatActivity {
        private static final String TAG = "MainActivity";
        private static final SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss", Locale.CHINA);
        private EditText etInput;
        private TextView textView;
        private EditText udpServerET;
        private EditText tcpServerET;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            etInput = findViewById(R.id.et_content);
            textView = findViewById(R.id.tv_show);
            udpServerET = findViewById(R.id.et_udp_server);
            tcpServerET = findViewById(R.id.et_tcp_server);
        }
    
        public void sendTcpMessage(View view) {
            String[] tcpInfo = tcpServerET.getText().toString().split(":");
            String inputContent = etInput.getText().toString();
            new Thread(()->{
                try (Socket socket = new Socket(tcpInfo[0], Integer.parseInt(tcpInfo[1]))){
                    OutputStream os = socket.getOutputStream();
                    os.write(inputContent.getBytes());
                    InputStream is = socket.getInputStream();
                    byte[] readBuf = new byte[1024];
                    String recv = new String(readBuf, 0, is.read(readBuf));
                    InetAddress address = socket.getInetAddress();
                    String ret = String.format("%s-%s-%s", df.format(new Date()), address, recv);
                    runOnUiThread(()-> textView.setText(ret));
                }catch (IOException e){
                    Log.e(TAG, "sendTcpMessage: Error!");
                }
            }).start();
        }
    
        public void sendUdpMessage(View view) {
            String[] udpInfo = udpServerET.getText().toString().split(":");
            String inputContent = etInput.getText().toString();
            new Thread(()->{
                try {
                    DatagramSocket socket = new DatagramSocket();
                    byte[] bytes = inputContent.getBytes();
                    InetAddress address = InetAddress.getByName(udpInfo[0]);
                    int serverPort = Integer.parseInt(udpInfo[1]);
                    DatagramPacket packet = new DatagramPacket(bytes, bytes.length, address, serverPort);
                    socket.send(packet);
                    byte[] recvBuf = new byte[1024];
                    DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length);
                    socket.receive(recvPacket);
                    String ret = String.format("%s-%s-%s", df.format(new Date()), address, new String(recvBuf));
                    runOnUiThread(()-> textView.setText(ret));
                }catch (IOException e){
                    Log.e(TAG, "sendUdpMessage: Error!");
                }
            }).start();
        }
    }
    

    AndroidManifest.xml:

    <uses-permission android:name="android.permission.INTERNET"/>
    

    注意点:1、网络访问权限 2、子线程代码中使用runOnUiThread()方法可更新UI

    Android访问HTTPS

    对于一个普通的HTTP请求,我们可以使用如下方式来发起请求,下面是一个简易的Http请求工具类:

    public class HttpUtils {
        private static Handler mUIHandler = new Handler(Looper.getMainLooper());
    
        interface HttpListener {
            void onSuccess(String content);
    
            void onFail(Exception e);
        }
    
        public static void doGet(String urlStr, HttpListener listener) {
            new Thread(() -> {
                Looper.prepare();
                try {
                    URL url = new URL(urlStr);
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.setRequestMethod("GET");
                    conn.setConnectTimeout(5000);
                    conn.setReadTimeout(5000);
                    conn.connect();
    
                    try (InputStream is = conn.getInputStream();
                         InputStreamReader reader = new InputStreamReader(is)
                    ) {
                        char[] buf = new char[4096];
                        int len;
                        StringBuilder sb = new StringBuilder();
                        while ((len = reader.read(buf)) != -1) {
                            sb.append(new String(buf, 0, len));
                        }
                        mUIHandler.post(() -> listener.onSuccess(sb.toString()));
                    } catch (IOException e) {
                        e.printStackTrace();
                        listener.onFail(e);
                    }
                }catch (IOException e){
                    e.printStackTrace();
                    listener.onFail(e);
                }
            }).start();
        }
    }
    

    1、不校验证书(不推荐)

    MyX509TrustManager.java,MyX509TrustManager实现不做任何事情:

    ...
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    
    import javax.net.ssl.X509TrustManager;
    
    public class MyX509TrustManager implements X509TrustManager {
    
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    		// TODO...
        }
    
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            // TODO...
        }
    
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }
    

    HttpsUtils.java

    ...
    public class HttpsUtils {
        private static Handler mUIHandler = new Handler(Looper.getMainLooper());
    
        interface HttpListener {
            void onSuccess(String content);
    
            void onFail(Exception e);
        }
    
        public static void doGet(Context context, String urlStr, HttpListener listener) {
            new Thread(() -> {
                Looper.prepare();
                try {
                    URL url = new URL(urlStr);
                    HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
                    SSLContext sslContext = SSLContext.getInstance("TLS");
                    // 放入自定义的MyX509TrustManager对象即可
                    TrustManager[] trustManagers = {new MyX509TrustManager()};
                    sslContext.init(null, trustManagers, new SecureRandom());
                    conn.setSSLSocketFactory(sslContext.getSocketFactory());
                    conn.setRequestMethod("GET");
                    conn.setConnectTimeout(5000);
                    conn.setReadTimeout(5000);
                    conn.connect();
    
                    try (InputStream is = conn.getInputStream();
                         InputStreamReader reader = new InputStreamReader(is)
                    ) {
                        char[] buf = new char[4096];
                        int len;
                        StringBuilder sb = new StringBuilder();
                        while ((len = reader.read(buf)) != -1) {
                            sb.append(new String(buf, 0, len));
                        }
                        mUIHandler.post(() -> listener.onSuccess(sb.toString()));
                    } catch (IOException e) {
                        e.printStackTrace();
                        listener.onFail(e);
                    }
                }catch (Exception e){
                    e.printStackTrace();
                    listener.onFail(e);
                }
            }).start();
        }
    }
    

    2、校验证书(推荐)

    拿我自己的博客站点来说,想要获得证书只需要在浏览器下载对应的证书即可(选择DER编码二进制和Base64编码均可),保存了一个名为srca.cer的文件到桌面:

    将这份证书文件复制到项目的src/main/assets/目录下,没有assets就新建,所以完整路径为src/main/assets/srca.cer。

    接下来需要实现MyX509TrustManager.java中的方法:

    public class MyX509TrustManager implements X509TrustManager {
        private static final String TAG = "MyX509TrustManager";
        // 证书对象
        private X509Certificate serverCert;
    
        public MyX509TrustManager(X509Certificate serverCert) {
            this.serverCert = serverCert;
        }
    
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
        }
    
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            // 遍历证书
            for (X509Certificate certificate: chain){
                // 校验合法性与是否过期
                certificate.checkValidity();
                try {
                    // 校验公钥
                    PublicKey publicKey = serverCert.getPublicKey();
                    certificate.verify(publicKey);
                } catch (Exception e) {
                    throw new CertificateException(e);
                }
            }
        }
    
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }
    

    同时,将使用keyStore这个API来获取TrustManager数组,HttpsUtils.java如下:

    public class Https2Utils {
        private static Handler mUIHandler = new Handler(Looper.getMainLooper());
    
        interface HttpListener {
            void onSuccess(String content);
    
            void onFail(Exception e);
        }
    
        public static void doGet(Context context, String urlStr, HttpListener listener) {
            new Thread(() -> {
                Looper.prepare();
                try {
                    URL url = new URL(urlStr);
                    HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
                    SSLContext sslContext = SSLContext.getInstance("TLS");
                    X509Certificate serverCert = getCert(context);
                    String defaultType = KeyStore.getDefaultType();
                    KeyStore keyStore = KeyStore.getInstance(defaultType);
                    keyStore.load(null);
                    // 别名、证书
                    keyStore.setCertificateEntry("srca", serverCert);
                    String algorithm = TrustManagerFactory.getDefaultAlgorithm();
                    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm);
                    trustManagerFactory.init(keyStore);
                    TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
                    sslContext.init(null, trustManagers, new SecureRandom());
                    conn.setSSLSocketFactory(sslContext.getSocketFactory());
                    // 校验域名是否合法
                    conn.setHostnameVerifier((hostname, session) -> {
                        HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier();
                        return verifier.verify("zouchanglin.cn", session);
                    });
                    conn.setRequestMethod("GET");
                    conn.setConnectTimeout(5000);
                    conn.setReadTimeout(5000);
                    conn.connect();
    
                    try (InputStream is = conn.getInputStream();
                         InputStreamReader reader = new InputStreamReader(is)
                    ) {
                        char[] buf = new char[4096];
                        int len;
                        StringBuilder sb = new StringBuilder();
                        while ((len = reader.read(buf)) != -1) {
                            sb.append(new String(buf, 0, len));
                        }
                        mUIHandler.post(() -> listener.onSuccess(sb.toString()));
                    } catch (IOException e) {
                        e.printStackTrace();
                        listener.onFail(e);
                    }
                }catch (Exception e){
                    e.printStackTrace();
                    listener.onFail(e);
                }
            }).start();
        }
    
        private static X509Certificate getCert(Context context) {
            try {
                // src/main/assets/srca.cer
                InputStream inputStream = context.getAssets().open("srca.cer");
                CertificateFactory factory = CertificateFactory.getInstance("X.509");
                return (X509Certificate) factory.generateCertificate(inputStream);
            } catch (IOException | CertificateException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    

    在MainActivity中使用也很简单:

    public class MainActivity extends AppCompatActivity {
    
        private EditText etUrl;
        private TextView tvShow;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            etUrl = findViewById(R.id.et_url);
            tvShow = findViewById(R.id.tv_show);
        }
    
        public void loadContent(View view) {
            String url = etUrl.getText().toString();
            Https2Utils.doGet(this, url, new Https2Utils.HttpListener() {
                @Override
                public void onSuccess(String content) {
                    tvShow.setText(content);
                }
    
                @Override
                public void onFail(Exception e) {
                    Toast.makeText(MainActivity.this, "Failed!", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }
    

    原文地址《Android Socket与HTTPS校验》

  • 相关阅读:
    JS——ajax login test
    Java——Java日期转Sql日期
    JDK动态代理实现原理
    Java 动态代理机制分析及扩展,第 1 部分
    Java枚举类
    Java强引用、 软引用、 弱引用、虚引用
    取模运算
    java集合框架
    字节和unicode
    编译原理随笔
  • 原文地址:https://www.cnblogs.com/timdevelop/p/14123154.html
Copyright © 2011-2022 走看看