@Slf4j
public class SslUtil {
public static SslContext sslContext() {
SslProvider provider = SslProvider.isAlpnSupported(SslProvider.OPENSSL) ? SslProvider.OPENSSL : SslProvider.JDK;
log.info("provider:{}", provider);
SelfSignedCertificate ssc = null;
try {
ssc = new SelfSignedCertificate();
} catch (CertificateException e) {
throw new RuntimeException(e);
}
ApplicationProtocolConfig applicationProtocolConfig = new ApplicationProtocolConfig(
ApplicationProtocolConfig.Protocol.ALPN,
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
ApplicationProtocolNames.HTTP_2,
ApplicationProtocolNames.HTTP_1_1);
SslContext sslContext = null;
try {
sslContext = SslContextBuilder
.forServer(ssc.certificate(), ssc.privateKey())
.sslProvider(provider)
.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
.applicationProtocolConfig(applicationProtocolConfig)
.build();
} catch (SSLException e) {
throw new RuntimeException(e);
}
return sslContext;
}
}
注意:io.netty.handler.ssl.util.SelfSignedCertificate 只用于测试,不要用于生产环境!!!
public class Http2ServerResponseHandler extends ChannelDuplexHandler {
static final ByteBuf RESPONSE_BYTES = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Hello World", CharsetUtil.UTF_8));
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof Http2Headersframe) {
Http2Headersframe msgHeader = (Http2Headersframe) msg;
if (msgHeader.isEndStream()) {
ByteBuf content = ctx.alloc().buffer();
content.writeBytes(RESPONSE_BYTES.duplicate());
Http2Headers headers = new DefaultHttp2Headers().status(HttpResponseStatus.OK.codeAsText());
ctx.write(new DefaultHttp2Headersframe(headers).stream(msgHeader.stream()));
ctx.write(new DefaultHttp2Dataframe(content, true).stream(msgHeader.stream()));
}
} else {
super.channelRead(ctx, msg);
}
}
}
@Slf4j
public class Http2Server {
private NioEventLoopGroup bossGroup;
private NioEventLoopGroup workerGroup;
public synchronized void start(int port) {
bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.option(ChannelOption.SO_BACKLOG, 1024)
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new Http2ServerInitializer());
//
ChannelFuture channelFuture = bootstrap.bind(port).sync();
log.info("http2 server started on port:{}", port);
} catch (Exception e) {
log.error("Http2ServerBootstrap-->", e);
close();
}
}
public static class Http2ServerInitializer extends ChannelInitializer {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
SslHandler sslHandler = SslUtil.sslContext().newHandler(channel.alloc());
pipeline.addLast("sslHandler", sslHandler);
pipeline.addLast("applicationProtocolNegotiationHandler", new MyAppProtocolNegotiationHandler());
}
}
//根据请求的协议添加对应的处理器
public static class MyAppProtocolNegotiationHandler extends ApplicationProtocolNegotiationHandler {
public MyAppProtocolNegotiationHandler() {
super(ApplicationProtocolNames.HTTP_2);
}
protected void configurePipeline(ChannelHandlerContext ctx, String protocol) {
if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {
Http2frameCodec http2frameCodec = Http2frameCodecBuilder.forServer().build();
ctx.pipeline().addLast(http2frameCodec);
ctx.pipeline().addLast(new Http2ServerResponseHandler());
} else {
throw new IllegalStateException("Protocol: " + protocol + " not supported");
}
}
}
public synchronized void close() {
if (bossGroup != null) {
bossGroup.shutdownGracefully();
}
if (workerGroup != null) {
workerGroup.shutdownGracefully();
}
log.info("destroy netty server thread");
}
}
public static void main(String[] args) {
Http2Server http2Server = new Http2Server();
http2Server.start(8443);
}
测试http2服务
curl -k -v --http2 https://127.0.0.1:8443
测试http1.1服务
curl -k -v --http1.1 https://127.0.0.1:8443使用 jdk 提供的SslProvider
使用jdk提供的SslProvider,但是jdk8不支持ALPN,出现如下异常,只能使用高版本的jdk。
示例使用的是jdk17可以支持ALPN,但是运行时会出现如下异常
Caused by: java.security.cert.CertificateException: No provider succeeded to generate a self-signed certificate. See debug log for the root cause. at io.netty.handler.ssl.util.SelfSignedCertificate.(SelfSignedCertificate.java:249) ~[netty-handler-4.1.70.Final.jar:4.1.70.Final] at io.netty.handler.ssl.util.SelfSignedCertificate. (SelfSignedCertificate.java:166) ~[netty-handler-4.1.70.Final.jar:4.1.70.Final] at io.netty.handler.ssl.util.SelfSignedCertificate. (SelfSignedCertificate.java:115) ~[netty-handler-4.1.70.Final.jar:4.1.70.Final] at io.netty.handler.ssl.util.SelfSignedCertificate. (SelfSignedCertificate.java:90) ~[netty-handler-4.1.70.Final.jar:4.1.70.Final] at com.bruce.netty.http2.handler.SslUtil.sslContext(SslUtil.java:24) ~[classes/:na] ... 21 common frames omitted Suppressed: java.lang.NoClassDefFoundError: org/bouncycastle/jce/provider/BouncyCastleProvider at io.netty.handler.ssl.util.BouncyCastleSelfSignedCertGenerator. (BouncyCastleSelfSignedCertGenerator.java:43) ~[netty-handler-4.1.70.Final.jar:4.1.70.Final] at io.netty.handler.ssl.util.SelfSignedCertificate. (SelfSignedCertificate.java:240) ~[netty-handler-4.1.70.Final.jar:4.1.70.Final] ... 25 common frames omitted Caused by: java.lang.ClassNotFoundException: org.bouncycastle.jce.provider.BouncyCastleProvider at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) ... 27 common frames omitted Caused by: java.lang.IllegalAccessError: class io.netty.handler.ssl.util.OpenJdkSelfSignedCertGenerator (in unnamed module @0x5e316c74) cannot access class sun.security.x509.X509CertInfo (in module java.base) because module java.base does not export sun.security.x509 to unnamed module @0x5e316c74 at io.netty.handler.ssl.util.OpenJdkSelfSignedCertGenerator.generate(OpenJdkSelfSignedCertGenerator.java:52) ~[netty-handler-4.1.70.Final.jar:4.1.70.Final] at io.netty.handler.ssl.util.SelfSignedCertificate. (SelfSignedCertificate.java:246) ~[netty-handler-4.1.70.Final.jar:4.1.70.Final] ... 25 common frames omitted
该问题乍一看以为是缺少org.bouncycastle.jce.provider.BouncyCastleProvider导致的异常,当添加了如下依赖后确实也可以解决问题。
org.bouncycastle bcpkix-jdk15on 1.70
但实际原因并不是缺少该依赖导致的,看最后一个Caused by:
because module java.base does not export sun.security.x509 to unnamed module @0x5e316c74
实际原因:jdk17因为模块化并没有在 java.base中导出sun.security.x509包,没有权限访问sun.security.x509.X509CertInfo导致无法实例化。
解决办法:不需要org.bouncycastle:bcpkix-jdk15on依赖,在启动时添加如下参数即可
--add-opens java.base/sun.security.x509=ALL-UNNAMED使用OPENSSL提供的SslProvider
添加如下依赖依赖,OPENSSL不需要依赖jdk提供了ALPN支持,因此在jdk8,jdk17上同样可以运行。
io.netty netty-tcnative-boringssl-static
当切换到jdk17时,再次执行则会出现上面所提到的异常,具体原因及解决方案上面已经说明
Caused by: java.lang.IllegalAccessError: class io.netty.handler.ssl.util.OpenJdkSelfSignedCertGenerator (in unnamed module @0x5e316c74) cannot access class sun.security.x509.X509CertInfo (in module java.base) because module java.base does not export sun.security.x509 to unnamed module @0x5e316c74 at io.netty.handler.ssl.util.OpenJdkSelfSignedCertGenerator.generate(OpenJdkSelfSignedCertGenerator.java:52) ~[netty-handler-4.1.70.Final.jar:4.1.70.Final] at io.netty.handler.ssl.util.SelfSignedCertificate.(SelfSignedCertificate.java:246) ~[netty-handler-4.1.70.Final.jar:4.1.70.Final] ... 25 common frames omitted
但为什么加了org.bouncycastle:bcpkix-jdk15on依赖同样可以解决问题呢?
源码见:io.netty.handler.ssl.util.SelfSignedCertificate#SelfSignedCertificate(String, java.security.SecureRandom, int, Date, Date, String)
原因是Netty优先使用的使用Bouncy Castle生成自签名证书。当不存在时再使用OpenJDK提供的sun.security.x509生成自签名证书。所以使用Bouncy Castle之后就不会走到下面逻辑,不会访问创建X509CertInfo对象,所以正常执行.



