1. 載入ssl證書的工具類
public class SslUtil { private static volatile SSLContext sslContext = null;
// type是PKCS12、path是pfx檔案路徑、password是pfx對應的密碼
public static SSLContext createSSLContext(String type ,String path ,String password) throws Exception { if(null == sslContext){ synchronized (SslUtil.class) { if(null == sslContext){ // 支援JKS、PKCS12(我們專案中用的是阿里雲免費申請的證書,下載tomcat解壓後的pfx檔案,對應PKCS12) KeyStore ks = KeyStore.getInstance(type); // 證書存放地址 InputStream ksInputStream = new FileInputStream(path); ks.load(ksInputStream, password.toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(ks, password.toCharArray()); sslContext = SSLContext.getInstance("TLS"); sslContext.init(kmf.getKeyManagers(), null, null); } } } return sslContext; } }
2. 將SslHandler放在第一個
bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 128) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // SSL處理器 SSLContext sslContext = SslUtil.createSSLContext(BaseGlobal.getCertType(), BaseGlobal.getCertPath(), BaseGlobal.getCertPassword()); SSLEngine sslEngine = sslContext.createSSLEngine(); sslEngine.setNeedClientAuth(false); sslEngine.setUseClientMode(false); pipeline.addLast("sslHandler", new SslHandler(sslEngine)); pipeline.addLast("idleStateHandler", new IdleStateHandler(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds)); ... } });
3. 因為我們專案的特殊性,還需要同時支援TCP、WS協議,所以使用多執行緒載入兩個NettyServer
@EnableCaching @EnableAutoConfiguration @SpringBootApplication(scanBasePackages = "xxx") @MapperScan(basePackages = "xxx") @EnableAsync public class V3xboxApplication { public static void main(String[] args) { SpringApplication.run(V3xboxApplication.class, args); // 啟動服務端(客戶端TCP連線) // 使用執行緒啟動,是因為Netty的sync方法會阻塞執行緒 // 此處不使用執行緒池的原因是這裡只需要一個執行緒,不存線上程的頻繁銷燬建立 NettyServerThread nettyServerThread = new NettyServerThread(); Thread thread1 = new Thread(nettyServerThread); thread1.start(); // 如果設定wss埠的話,則啟動wss處理伺服器 if(StringUtil.isNotEmpty(BaseGlobal.getWssPort())) { NettyWssServerThread sslServerThread = new NettyWssServerThread(); Thread thread2 = new Thread(sslServerThread); thread2.start(); } } }
4. 因為我們需要在程式動態判斷WS還是WSS,所以在nginx的proxy配置了,這樣後臺就可以識別客戶端是https還是http
proxy_set_header scheme $scheme;