背景: Java マイクロサービスには https アクセスと WebSocket アクセスが含まれます。https インターフェイスが wss リクエストにアクセスすると、https は wss にアクセスできるため、エラーが報告されます。
Alibaba Cloud から無料の証明書を申請した後、nginx の設定方法、netty アクセス証明書などのさまざまなチュートリアルを検索します。たくさんの寄り道を経て、ようやく一つのことを乗り越えることができました。
重要なポイント: 1. netty が使用されるため、nginx が wss を構成する方法は機能しません。netty を起動するには、netty の起動方法に証明書を配置する必要があります。
2. オンライン チュートリアルのほとんどは、pkcs12 証明書の生成方法です。ただし、netty は pkcs8 バージョンのみをサポートしているため、pkcs12 を生成してから pkcs8 に転送する必要があります。
1. Alibaba Cloud は無料の証明書を申請します
2. PEM 証明書とキー証明書を生成する
1. Nginx タイプの pem 証明書をダウンロードします。
2. Tomcat タイプの pfx 証明書をダウンロードし、cmd コマンドを使用して jdk パスの下 (またはフォルダーに直接) でserver.key ファイルを生成し、コマンドを入力します。パスワードは pfx-password.txt に一緒に含まれます。
openssl pkcs12 -in *******.pfx -nocerts -nodes -out server.key
3. netty は pkcs8 のみをサポートするため、server.key は OpenSSl を通じて pkcs8 バージョンを生成します。
openssl をインストールする
Windows コンピューターは、コンパイル済みの openssl
OpenSSL for Windowsをダウンロードします。
また
OpenSSL 公式ダウンロード - コードオフ
次に、インストール パスを環境変数に追加します
cmd コマンドは、server8.key を生成します。
openssl pkcs8 -topk8 -inform PEM -in server.key -outform pem -nocrypt -out server8.key
3. コードを書く
次の 2 つの場所を追加するだけです。
1. 起動の start メソッドにより、server8.key および pem ファイルの読み取りが増加します。
グローバル変数
private SslContext sslContext;//netty 配置ssl证书、WSS地址访问。备注:netty 仅仅使用pkcs8的证书,需要使用openssl转
証明書を main/resources に配置し、ClassPathResource で読み取ります。
//ssl证书配置--放到项目中
ClassPathResource pem = new ClassPathResource("service.sv3d.cn.pem");
ClassPathResource key = new ClassPathResource("server8.key");
this.sslContext = SslContextBuilder.forServer(pem.getInputStream(),key.getInputStream()).build();
後で交換しやすいように証明書を外に置きます。結局のところ、無料の証明書は年に 1 回適用され、InputStream で読み取られます。
//ssl证书配置--放到外部
InputStream pemInputStream = new FileInputStream("E:\\zhengshu\\9760642_service.sv3d.cn_nginx\\9760642_service.sv3d.cn.pem"); /// 证书存放地址
InputStream keyInputStream = new FileInputStream("E:\\zhengshu\\9760642_service.sv3d.cn_tomcat\\server8.key"); /// 证书存放地址
this.sslContext = SslContextBuilder.forServer(pemInputStream,keyInputStream).build();
2. initChannel メソッドにより ssl 検証が追加されます
ch.pipeline().addLast(sslContext.newHandler(ch.alloc()));
3. すべてのコードについて、同様の変更された部分を見てください。
/**
* 启动
* @throws InterruptedException
*/
private void start() throws InterruptedException {
try {
port = Integer.parseInt(optionService.getByKey(OptionKey.SocketPort.getKey()).getValue());
String wsFlag = nacosConfigProp.getWsFlag();
//ssl证书配置--放到外部
//InputStream pemInputStream = new FileInputStream("E:\\zhengshu\\9760642_service.sv3d.cn_nginx\\9760642_service.sv3d.cn.pem"); /// 证书存放地址
//InputStream keyInputStream = new FileInputStream("E:\\zhengshu\\9760642_service.sv3d.cn_tomcat\\server8.key"); /// 证书存放地址
//this.sslContext = SslContextBuilder.forServer(pemInputStream,keyInputStream).build();
//ssl证书配置--放到项目中
ClassPathResource pem = new ClassPathResource("service.sv3d.cn.pem");
ClassPathResource key = new ClassPathResource("server8.key");
this.sslContext = SslContextBuilder.forServer(pem.getInputStream(),key.getInputStream()).build();
} catch (Exception e) {
e.printStackTrace();
}
//数据量上来时设置线程池
/*bossGroup = new NioEventLoopGroup(2,new DefaultThreadFactory("server1",true));
workGroup = new NioEventLoopGroup(4,new DefaultThreadFactory("server2",true));*/
// 获取Reactor线程池
bossGroup = new NioEventLoopGroup();
workGroup = new NioEventLoopGroup();
// 服务端启动辅助类,用于设置TCP相关参数
ServerBootstrap bootstrap = new ServerBootstrap();
// bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之前的读写操作,设置为主从线程模型
bootstrap.group(bossGroup,workGroup);
//禁用nagle算法
bootstrap.childOption(ChannelOption.TCP_NODELAY, true);
//bootstrap.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(1024*1024, 40*1024*1024));
//当设置为true的时候,TCP会实现监控连接是否有效,当连接处于空闲状态的时候,超过了2个小时,
//本地的TCP实现会发送一个数据包给远程的 socket,如果远程没有发回响应,TCP会持续尝试11分钟,
//知道响应为止,如果在12分钟的时候还没响应,TCP尝试关闭socket连接。
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
// 设置NIO类型的channel,设置服务端NIO通信类型
bootstrap.channel(NioServerSocketChannel.class);
// 设置监听端口
bootstrap.localAddress(new InetSocketAddress(port));
// 连接到达时会创建一个通道,设置ChannelPipeline,也就是业务职责链,由处理的Handler串联而成,由从线程池处理
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
// 添加处理的Handler,通常包括消息编解码、业务处理,也可以是日志、权限、过滤等
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 流水线管理通道中的处理程序(Handler),用来处理业务
//=============================增加心跳支持============================
//添加ssl 访问
ch.pipeline().addLast(sslContext.newHandler(ch.alloc()));
// webSocket协议本身是基于http协议的,所以这边也要使用http编解码器
ch.pipeline().addLast(new HttpServerCodec());
ch.pipeline().addLast(new ObjectEncoder());
//ch.pipeline().addLast(myMessageDncoder);
//ch.pipeline().addLast(myMessageEncoder);
// 以块的方式来写的处理器,分块向客户端写数据,防止发送大文件时导致内存溢出,channel.write(new ChunkedFile(new File("...")))
ch.pipeline().addLast(new ChunkedWriteHandler());
//ch.pipeline().addLast(new MyServerChunkHandler());
/*
说明:
1、http数据在传输过程中是分段的,HttpObjectAggregator可以将多个段聚合
2、这就是为什么,当浏览器发送大量数据时,就会发送多次http请求
*/
//需要放到HttpServerCodec后面
ch.pipeline().addLast(new HttpObjectAggregator(65535));//10kb?
//websocket数据压缩扩展,当添加这个的时候,WebSocketServerProtocolHandler第三个参数需要设置成true
ch.pipeline().addLast(new WebSocketServerCompressionHandler());
/*
说明:
1、对应webSocket,它的数据是以帧(frame)的形式传递
2、浏览器请求时 ws://localhost:58080/xxx 表示请求的uri
3、核心功能是将http协议升级为ws协议,保持长连接
*/
//对客户端,如果在60秒内没有向服务端发送心跳,就主动断开
//三个参数分别为读/写/读写的空闲,我们只针对读写空闲检测
//ch.pipeline().addLast(new IdleStateHandler(10,11,12,TimeUnit.SECONDS));
//ch.pipeline().addLast(heartBeatHandler);
// 自定义的handler,处理业务逻辑
//ch.pipeline().addLast(webSocketHandler);
//ch.pipeline().addLast(userLoginRespHandler);
ch.pipeline().addLast(textWebSocketHandler);
ch.pipeline().addLast(binaryWebSocketFrameHandler);
// 服务器端向外暴露的 web socket 端点,当客户端传递比较大的对象时,maxFrameSize参数的值需要调大
ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, webSocketProtocol, true, 10485760));//10mb?
}
});
// 配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功
ChannelFuture channelFuture = bootstrap.bind().sync();
log.info("Server started and listen on:{}",channelFuture.channel().localAddress());
// 对关闭通道进行监听
channelFuture.channel().closeFuture().sync();
}
4. 動作するかどうかを確認するには、Postman を使用するか、インターフェイスを自分で作成します。
設定後、wsが使えないことが分かり、現在調査中ですが、方法があればメッセージを残してお互いに学び合うことができます。以下は他の人がダウンロードしたケースを私のケースに変更したもので、サーバーが 2 つありますので、参考にしてください。
WebsocketServer1 が私の解決策です。記事の証明書に従って証明書がダウンロードされ、生成されます。WebsocketServer1 のメイン メソッドを開始します。postman でアクセスできますが、それにバインドされている HTML を使用することはできません。私のプロジェクトでは可能です。
もう1つの方法は、WebsocketServerがキーを生成してからパスワードを設定する方法で、試してみましたが、生成されるパスワードが毎回一致しないので諦めました。