基于Netty实现安全认证的WebSocket(wss)服务端

1.Netty服务端

服务端代码参考【基于Netty实现WebSocket服务端-CSDN博客】中的两种方式都可以;这里用的是第一种简单方式。

新增如下逻辑:添加SSLHandler

SSLContext sslContext = SslUtil.createSSLContext("JKS",
    "D:\\workSpace\\daydayup\\cert\\wss2\\mystore.jks", "1234567");
    // SSLEngine 此类允许使用ssl安全套接层协议进行安全通信
    SSLEngine engine = sslContext.createSSLEngine();
    engine.setUseClientMode(false);
    pipeline.addLast(new SslHandler(engine)); // 设置SSL

SslUtil的代码:

import org.springframework.core.io.DefaultResourceLoader;

import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;

public class SslUtil {

    public static SSLContext createSSLContext(String type, String path, String sslPassword) throws Exception {
        DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
        InputStream inputStream = new FileInputStream(path);
        char[] passArray = sslPassword.toCharArray();
        SSLContext sslContext = SSLContext.getInstance("SSLv3"); // 这里TLS或者SSLv3都可以
        KeyStore ks = KeyStore.getInstance("JKS");
        // 加载keytool 生成的文件
        ks.load(inputStream, passArray);
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, passArray);
        sslContext.init(kmf.getKeyManagers(), null, null);
        inputStream.close();
        return sslContext;
    }
}

完整的服务端代码如下:

MyTextWebSocketFrameHandler的代码和 【基于Netty实现WebSocket服务端-CSDN博客】中的完全一样。

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;

/**
 *
 * 实现长链接 客户端与服务端;
 */
public class SimpleWssChatServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class).
            // 在 bossGroup 增加一个日志处理器
                handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {

                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        SSLContext sslContext = SslUtil.createSSLContext("JKS",
                            "D:\\workSpace\\daydayup\\cert\\wss2\\mystore.jks", "1234567");
                        // SSLEngine 此类允许使用ssl安全套接层协议进行安全通信
                        SSLEngine engine = sslContext.createSSLEngine();
                        engine.setUseClientMode(false);
                        pipeline.addLast(new SslHandler(engine)); // 设置SSL

                        // 基于http协议的长连接 需要使用http协议的解码 编码器
                        pipeline.addLast(new HttpServerCodec());
                        // 以块的方式处理
                        pipeline.addLast(new ChunkedWriteHandler());
                        /**
                         * http数据传输过程中是分段, HttpObjectAggregator 将多个段聚合起来
                         * 当浏览器发起大量数据的时候,会发起多次http请求
                         */
                        pipeline.addLast(new HttpObjectAggregator(8192));
                        /**
                         * 对于websocket是以frame的形式传递
                         * WebSocketFrame
                         *  浏览器 ws://localhost:7000/ 不在是http协议
                         *  WebSocketServerProtocolHandler 将http协议升级为ws协议 即保持长链接
                         */
                        pipeline.addLast(new WebSocketServerProtocolHandler("/helloWs"));

                        // 自定义handler专门处理浏览器请求
                        pipeline.addLast(new MyTextWebSocketFrameHandler());

                    }
                });
            ChannelFuture channelFuture = serverBootstrap.bind(7070).sync();
            channelFuture.channel().closeFuture().sync();

        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

2.使用jdk工具keytool生成证书

证书处理步骤如下,缺一不可

#2.1生成秘钥对
keytool -genkey -alias server2 -keyalg RSA -validity 365 -keystore D:\workSpace\daydayup\cert\wss2\mystore.jks -storepass 1234567

#2.2导入证书
keytool  -alias server2 -exportcert  -keystore D:\workSpace\daydayup\cert\wss2\mystore.jks -file D:\workSpace\daydayup\cert\wss2\mystore.cer -storepass 1234567
#2.3信任证书

双机上面生成的证书文件mystore.cer,

可以看到证书此时不受信任,点击【安装证书】

选择存储位置为【本地计算机】后,

 选择证书存储为【受信任的根证书颁发机构】,完成即可。

再次双击原证书,可以看到证书已经受信任了

3.使用JavaScript客户端进行测试:

JavaScript客户端代码,也是和 【基于Netty实现WebSocket服务端-CSDN博客】中基本一样,只是把服务端地址的协议头修改为wss。

socket = new WebSocket("wss://localhost:7070/helloWs");

启动服务端,然后启动客户端,连接和接发数据都正常。

windows下的wss访问到此就可以拉。

4.下面列一些在开发过程中遇到的问题:

4.1生成mystore.jks后,不导入证书,直接启动服务端使用;

或者导入证书后不手动信任,客户端进行连接时候,会报错

io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
	at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:459)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1359)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:935)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:138)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:645)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysPlain(NioEventLoop.java:545)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:499)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:459)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
	at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:138)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
	at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
	at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
	at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:365)
	at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:293)
	at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:204)
	at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
	at java.base/sun.security.ssl.SSLEngineImpl.decode(SSLEngineImpl.java:736)
	at java.base/sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:691)
	at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:506)
	at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:482)
	at java.base/javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:679)
	at io.netty.handler.ssl.SslHandler$SslEngineType$3.unwrap(SslHandler.java:292)
	at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1247)
	at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1158)
	at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1193)
	at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489)
	at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428)
	... 16 more

4.2 客户端连接服务端的地中,协议头还是ws

异常发生:io.netty.handler.ssl.NotSslRecordException: not an SSL/TLS record: 

参考:netty整合websocket支持自签证书出现netty websocket ssl Received fatal alert: certificate_unknown_alert certificate unknown-CSDN博客

netty做服务端支持ssl协议实现websocket的wss协议(客户端为浏览器)_netty websocket ssl-CSDN博客

  • 26
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值