Android开发经验谈Spring WebSocketjava编程相关

Java WebSocket Security 加密通信实践

2018-06-27  本文已影响12人  捉影T_T900

最近在项目中用到了WebSocket通信的内容

第一版的实现方案:spring-websocket + nv-websocket-client

第一版的模式已经正常得到应用,但有个问题,就是传输过程没有加密,完全明文的方式,这在当前的时代不合时宜,所以考虑对通信过程进行加密传输。

第二版的实现方案:Java-WebSocket

这是一个Java的WebSocket开发组件,支持整合到后端、客户端,支持SSL/TLS加密传输协议。由于我目前的项目主要应用场景在局域网内,所以对于常规的CA签名证书部署并不合适,我就用自签名的形式进行加密传输检验。

话不多说,直接开干。

一、准备工作

准备自签名证书

keytool -genkey -validity 3650 -keyalg RSA -sigalg SHA256withRSA -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry"

这里要特别注意 -keyalg RSA -sigalg SHA256withRSA 这两个属性,后面要将jks转换成bks才能在Android平台使用,没有这两个属性会导致handshake的时候报“Possible no handshake recieved!”,说明签名校验不通过。
github上的Issue说明

将jks签名文件转换成bks,因为Android只能读取bks格式的签名秘钥,转换方法有好几种,这里介绍一个命令行方式的
转换方法

二、程序实现

java后台

        WebSocketImpl.DEBUG = true;
        ChatServer chatServer = new ChatServer(9089); // 这里可以自定义端口,默认80
        logger.info("charServer port:" + chatServer.getPort());

        // load up the key store
        String STORETYPE = "JKS";
        String KEYSTORE = "/Users/randysu/keystore.jks";
        String STOREPASSWORD = "storepassword"; // 之前在制作keystore.jks时指定的store密码
        String KEYPASSWORD = "keypassword"; // 之前在制作keystore.jks时指定的key密码

        KeyStore ks = KeyStore.getInstance( STORETYPE );
        File kf = new File( KEYSTORE );
        ks.load( new FileInputStream( kf ), STOREPASSWORD.toCharArray() );

        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init( ks, KEYPASSWORD.toCharArray() );
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
        tmf.init( ks );

        SSLContext sslContext = null;
        sslContext = SSLContext.getInstance( "TLS" );
        sslContext.init( kmf.getKeyManagers(), tmf.getTrustManagers(), null );

        // DefaultSSLWebSocketServerFactory  这里用的是默认的加密仓库,默认允许加载所有的签名方式,也可以自定义加密仓库,移除不能校验通过的签名方式
//        SSLEngine engine = sslContext.createSSLEngine();
//        List<String> ciphers = new ArrayList<String>( Arrays.asList(engine.getEnabledCipherSuites()));
//        ciphers.remove("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
//        List<String> protocols = new ArrayList<String>( Arrays.asList(engine.getEnabledProtocols()));
//        protocols.remove("SSLv3");
//        CustomSSLWebSocketServerFactory factory = new CustomSSLWebSocketServerFactory(sslContext, protocols.toArray(new String[]{}), ciphers.toArray(new String[]{}));
//        chatserver.setWebSocketFactory(factory);

        chatServer.setWebSocketFactory( new DefaultSSLWebSocketServerFactory( sslContext ) );

        chatServer.start();

Android端

        // load up the key store
        String STORETYPE = "bks";
        String KEYSTORE = Environment.getExternalStorageDirectory().getPath() + File.separator + "keystore.bks";
        String STOREPASSWORD = "storepassword";
        String KEYPASSWORD = "keypassword";

        KeyStore ks = KeyStore.getInstance(STORETYPE);
        File kf = new File(KEYSTORE);
        ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray());

        KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
        kmf.init(ks, KEYPASSWORD.toCharArray());
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
        tmf.init(ks);

        SSLContext sslContext = null;
        sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

        SSLSocketFactory factory = sslContext.getSocketFactory();

        URI socketUri = new URI("wss://192.168.1.230:9089/server");
        SSLWebSocketClient socketClient = new SSLWebSocketClient(socketUri, mWsListener);
        socketClient.setSocket(factory.createSocket());
        socketClient.connect();
//        socketClient.connectBlocking();  // 阻塞当前线程直至后台返回连接成功或失败

SSLWebSocketClient 继承 WebSocketClient(Java-WebSocketde的对象),mWsListener是一个连接状态的回调接口,在里面处理连接状态。

mWsListener = new WsListener() {
            @Override
            public void onConnected(ServerHandshake handshakedata) {
                //连接成功
                Logger.i("websoeket opened connection");
               
            }

            @Override
            public void onTextMessage(String message) {
                //服务端消息来了
                Logger.i("websoeket received:" + message);
                
            }

            @Override
            public void onDisconnected(int code, String reason, boolean remote) {
                //连接断开,remote判定是客户端断开还是服务端断开
                Logger.i("websoeket Connection closed by " + (remote ? "remote server" : "us"));
                
            }

            @Override
            public void onConnectError(Exception ex) {
                // 连接错误
                Logger.i("websoeket error:" + ex);
                
            }
        };
    }

java后台向Android发送消息

WebSocket对象的send(String str)方法

Android端向java后台发送消息

SSLWebSocketClient的send(String str)方法

这里要注意一点,Android定义的证书格式是“ X509”,Java后台定义的证书格式是“ SunX509”,如果Android定义“SunX509”会报错,同理也不能在Java后台定义“X509”。

三、实测结果

屏幕快照 2018-06-27 下午4.27.24.png

推荐一本书《HTTPS权威指南》
Done!

上一篇下一篇

猜你喜欢

热点阅读