netty是一个非常优秀的nio框架,就不多介绍啦,这里主要介绍如何在netty中开启ssl。
这里使用的版本是 netty4 ,netty4内部已经实现了标准的 jdk ssl 以及openssl。很方便的就能使用。
ssl的一些介绍,为何要用ssl就不累述了,百度一大堆。咱们讲究实战,直接上代码吧:
一、首先肯定要导入相关的jar:
io.netty
netty-all
4.1.36.Final
io.netty
netty-tcnative
2.0.25.Final
runtime
io.netty
netty-tcnative-boringssl-static
2.0.25.Final
runtime
二、提供本人使用的一个示例的几个类吧。直接看代码:
配置类:
/*
* 文件名:SSLConfig.java
* 版权:Copyright by www.poly.com
* 描述:
* 修改人:gogym
* 修改时间:2019年7月13日
* 跟踪单号:
* 修改单号:
* 修改内容:
*/
package org.gogym.gim.socket.tcp.ssl;
public class SSLConfig {
// 是否开启ssl
private boolean isSSL = false;
// 是否开启双向验证
private boolean needClientAuth = false;
// 密匙库地址
private String pkPath;
// 签名证书地址
private String caPath;
// 证书密码
private String passwd;
public SSLConfig isSSL(boolean isSSL) {
this.isSSL = isSSL;
return this;
}
public SSLConfig needClientAuth(boolean needClientAuth) {
this.needClientAuth = needClientAuth;
return this;
}
public SSLConfig pkPath(String pkPath) {
this.pkPath = pkPath;
return this;
}
public SSLConfig caPath(String caPath) {
this.caPath = caPath;
return this;
}
public SSLConfig passwd(String passwd) {
this.passwd = passwd;
return this;
}
public boolean isSSL() {
return isSSL;
}
public boolean isNeedClientAuth() {
return needClientAuth;
}
public String getPkPath() {
return pkPath;
}
public String getCaPath() {
return caPath;
}
public String getPasswd() {
return passwd;
}
}
关键的一个类,sslcontext工厂类,用来创建ssl上下文对象,非常重要,已经添加客户端和服务器端,jdk和openssl的配置,直接按需调用即可
/*
* 文件名:SSLContextFactory.java
* 版权:Copyright by www.poly.com
* 描述:
* 修改人:gogym
* 修改时间:2019年7月13日
* 跟踪单号:
* 修改单号:
* 修改内容:
*/
package org.gogym.gim.socket.tcp.ssl;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManagerFactory;
import org.gogym.getty.buffer.ByteBufAllocator;
import org.gogym.getty.handler.ssl.SslContext;
import org.gogym.getty.handler.ssl.SslContextBuilder;
import org.gogym.getty.handler.ssl.SslHandler;
import org.gogym.getty.handler.ssl.SslProvider;
/**
*
* 〈一句话功能简述〉 〈功能详细描述〉
*
* @author gogym
* @version 2019年7月13日
* @see SSLContextFactory
* @since
*/
public class SSLContextFactory {
private static final String PROTOCOL = "TLS";
private static SSLContext SERVER_CONTEXT;// 服务器安全套接字协议
private static SslContext openSslContext;
private static SSLContext CLIENT_CONTEXT;// 客户端安全套接字协议
private static SslContext openSslClientContext;
public static SSLContext getServerContext(String pkPath, String passwd) {
if (SERVER_CONTEXT != null)
return SERVER_CONTEXT;
InputStream in = null;
try {
// 密钥管理器
KeyManagerFactory kmf = null;
if (pkPath != null) {
// 密钥库KeyStore
KeyStore ks = KeyStore.getInstance("JKS");
// 加载服务端证书
in = new FileInputStream(pkPath);
// 加载服务端的KeyStore ;sNetty是生成仓库时设置的密码,用于检查密钥库完整性的密码
ks.load(in, passwd.toCharArray());
kmf = KeyManagerFactory.getInstance("SunX509");
// 初始化密钥管理器
kmf.init(ks, passwd.toCharArray());
}
// 获取安全套接字协议(TLS协议)的对象
SERVER_CONTEXT = SSLContext.getInstance(PROTOCOL);
// 初始化此上下文
// 参数一:认证的密钥 参数二:对等信任认证 参数三:伪随机数生成器 。 由于单向认证,服务端不用验证客户端,所以第二个参数为null
SERVER_CONTEXT.init(kmf.getKeyManagers(), null, null);
} catch (Exception e) {
throw new Error("Failed to initialize the server-side SSLContext",
e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return SERVER_CONTEXT;
}
public static SslContext getOpenSslServerContext(String pkPath,
String passwd) {
if (openSslContext != null) {
return openSslContext;
}
InputStream in = null;
try {
// 密钥管理器
KeyManagerFactory kmf = null;
if (pkPath != null) {
// 密钥库KeyStore
KeyStore ks = KeyStore.getInstance("JKS");
// 加载服务端证书
in = new FileInputStream(pkPath);
// 加载服务端的KeyStore ;sNetty是生成仓库时设置的密码,用于检查密钥库完整性的密码
ks.load(in, passwd.toCharArray());
kmf = KeyManagerFactory.getInstance("SunX509");
// 初始化密钥管理器
kmf.init(ks, passwd.toCharArray());
}
openSslContext = SslContextBuilder.forServer(kmf)
.sslProvider(SslProvider.OPENSSL).build();
return openSslContext;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
}
return null;
}
public static SSLContext getClientContext(String pkPath, String passwd) {
if (CLIENT_CONTEXT != null)
return CLIENT_CONTEXT;
InputStream tIN = null;
try {
// 信任库
TrustManagerFactory tf = null;
if (pkPath != null) {
// 密钥库KeyStore
KeyStore tks = KeyStore.getInstance("JKS");
// 加载客户端证书
tIN = new FileInputStream(pkPath);
tks.load(tIN, passwd.toCharArray());
tf = TrustManagerFactory.getInstance("SunX509");
// 初始化信任库
tf.init(tks);
}
CLIENT_CONTEXT = SSLContext.getInstance(PROTOCOL);
// 设置信任证书
CLIENT_CONTEXT.init(null,
tf == null ? null : tf.getTrustManagers(), null);
} catch (Exception e) {
throw new Error("Failed to initialize the client-side SSLContext");
} finally {
if (tIN != null) {
try {
tIN.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return CLIENT_CONTEXT;
}
public static SslContext getOpenSslClientContext(String pkPath,
String passwd) {
if (openSslClientContext != null) {
return openSslClientContext;
}
InputStream tIN = null;
try {
// 信任库
TrustManagerFactory tf = null;
if (pkPath != null) {
// 密钥库KeyStore
KeyStore tks = KeyStore.getInstance("JKS");
// 加载客户端证书
tIN = new FileInputStream(pkPath);
tks.load(tIN, passwd.toCharArray());
tf = TrustManagerFactory.getInstance("SunX509");
// 初始化信任库
tf.init(tks);
}
openSslClientContext = SslContextBuilder.forClient()
.sslProvider(SslProvider.OPENSSL).trustManager(tf).build();
return openSslClientContext;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (tIN != null) {
try {
tIN.close();
} catch (IOException e) {
e.printStackTrace();
}
tIN = null;
}
}
return null;
}
/**
*
* Description: 生成sslContext
*
* @param pkPath
* @param caPath
* @param passwd
* @return
* @see
*/
public static SSLContext getServerContext(String pkPath, String caPath,
String passwd) {
if (SERVER_CONTEXT != null)
return SERVER_CONTEXT;
InputStream in = null;
InputStream tIN = null;
try {
// 密钥管理器
KeyManagerFactory kmf = null;
if (pkPath != null) {
KeyStore ks = KeyStore.getInstance("JKS");
in = new FileInputStream(pkPath);
ks.load(in, passwd.toCharArray());
kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, passwd.toCharArray());
}
// 信任库
TrustManagerFactory tf = null;
if (caPath != null) {
KeyStore tks = KeyStore.getInstance("JKS");
tIN = new FileInputStream(caPath);
tks.load(tIN, passwd.toCharArray());
tf = TrustManagerFactory.getInstance("SunX509");
tf.init(tks);
}
SERVER_CONTEXT = SSLContext.getInstance(PROTOCOL);
// 初始化此上下文
// 参数一:认证的密钥 参数二:对等信任认证 参数三:伪随机数生成器 。 由于单向认证,服务端不用验证客户端,所以第二个参数为null
// 单向认证?无需验证客户端证书
if (tf == null) {
SERVER_CONTEXT.init(kmf.getKeyManagers(), null, null);
}
// 双向认证,需要验证客户端证书
else {
SERVER_CONTEXT.init(kmf.getKeyManagers(),
tf.getTrustManagers(), null);
}
} catch (Exception e) {
throw new Error("Failed to initialize the server-side SSLContext",
e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
if (tIN != null) {
try {
tIN.close();
} catch (IOException e) {
e.printStackTrace();
}
tIN = null;
}
}
return SERVER_CONTEXT;
}
public static SslContext getOpenSslServerContext(String pkPath,
String caPath, String passwd) {
if (openSslContext != null)
return openSslContext;
InputStream in = null;
InputStream tIN = null;
try {
// 密钥管理器
KeyManagerFactory kmf = null;
if (pkPath != null) {
// 密钥库KeyStore
KeyStore ks = KeyStore.getInstance("JKS");
// 加载服务端证书
in = new FileInputStream(pkPath);
// 加载服务端的KeyStore ;sNetty是生成仓库时设置的密码,用于检查密钥库完整性的密码
ks.load(in, passwd.toCharArray());
kmf = KeyManagerFactory.getInstance("SunX509");
// 初始化密钥管理器
kmf.init(ks, passwd.toCharArray());
}
// 信任库
TrustManagerFactory tf = null;
if (caPath != null) {
KeyStore tks = KeyStore.getInstance("JKS");
tIN = new FileInputStream(caPath);
tks.load(tIN, passwd.toCharArray());
tf = TrustManagerFactory.getInstance("SunX509");
tf.init(tks);
}
openSslContext = SslContextBuilder.forServer(kmf).trustManager(tf)
.sslProvider(SslProvider.OPENSSL).build();
return openSslContext;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
if (tIN != null) {
try {
tIN.close();
} catch (IOException e) {
e.printStackTrace();
}
tIN = null;
}
}
return null;
}
public static SSLContext getClientContext(String pkPath, String caPath,
String passwd) {
if (CLIENT_CONTEXT != null)
return CLIENT_CONTEXT;
InputStream in = null;
InputStream tIN = null;
try {
KeyManagerFactory kmf = null;
if (pkPath != null) {
KeyStore ks = KeyStore.getInstance("JKS");
in = new FileInputStream(pkPath);
ks.load(in, passwd.toCharArray());
kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, passwd.toCharArray());
}
TrustManagerFactory tf = null;
if (caPath != null) {
KeyStore tks = KeyStore.getInstance("JKS");
tIN = new FileInputStream(caPath);
tks.load(tIN, passwd.toCharArray());
tf = TrustManagerFactory.getInstance("SunX509");
tf.init(tks);
}
CLIENT_CONTEXT = SSLContext.getInstance(PROTOCOL);
// 初始化此上下文
// 参数一:认证的密钥 参数二:对等信任认证 参数三:伪随机数生成器 。 由于单向认证,服务端不用验证客户端,所以第二个参数为null
CLIENT_CONTEXT.init(kmf.getKeyManagers(), tf.getTrustManagers(),
null);
} catch (Exception e) {
throw new Error("Failed to initialize the client-side SSLContext");
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
if (tIN != null) {
try {
tIN.close();
} catch (IOException e) {
e.printStackTrace();
}
tIN = null;
}
}
return CLIENT_CONTEXT;
}
public static SslContext getOpenSslClientContext(String pkPath,
String caPath, String passwd) {
if (openSslClientContext != null) {
return openSslClientContext;
}
InputStream in = null;
InputStream tIN = null;
try {
// 密钥管理器
KeyManagerFactory kmf = null;
if (pkPath != null) {
// 密钥库KeyStore
KeyStore ks = KeyStore.getInstance("JKS");
// 加载服务端证书
in = new FileInputStream(pkPath);
// 加载服务端的KeyStore ;sNetty是生成仓库时设置的密码,用于检查密钥库完整性的密码
ks.load(in, passwd.toCharArray());
kmf = KeyManagerFactory.getInstance("SunX509");
// 初始化密钥管理器
kmf.init(ks, passwd.toCharArray());
}
// 信任库
TrustManagerFactory tf = null;
if (caPath != null) {
KeyStore tks = KeyStore.getInstance("JKS");
tIN = new FileInputStream(caPath);
tks.load(tIN, passwd.toCharArray());
tf = TrustManagerFactory.getInstance("SunX509");
tf.init(tks);
}
openSslClientContext = SslContextBuilder.forClient()
.sslProvider(SslProvider.OPENSSL).keyManager(kmf)
.trustManager(tf).build();
return openSslClientContext;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
if (tIN != null) {
try {
tIN.close();
} catch (IOException e) {
e.printStackTrace();
}
tIN = null;
}
}
return null;
}
/**
*
* Description:
*
* @return
* @see
*/
public static SSLEngine getSslServerEngine(SSLConfig sslConfig) {
SSLEngine sslEngine = null;
if (sslConfig.isNeedClientAuth()) {
sslEngine = getServerContext(sslConfig.getPkPath(),
sslConfig.getCaPath(), sslConfig.getPasswd())
.createSSLEngine();
} else {
sslEngine = getServerContext(sslConfig.getPkPath(),
sslConfig.getPasswd()).createSSLEngine();
}
sslEngine.setUseClientMode(false);
sslEngine.setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1",
"TLSv1.2" });
// false为单向认证,true为双向认证
sslEngine.setNeedClientAuth(sslConfig.isNeedClientAuth());
return sslEngine;
}
public static SSLEngine getOpenSslServerEngine(SSLConfig sslConfig,
ByteBufAllocator alloc) {
SSLEngine sslEngine = null;
if (sslConfig.isNeedClientAuth()) {
sslEngine = getOpenSslServerContext(sslConfig.getPkPath(),
sslConfig.getCaPath(), sslConfig.getPasswd()).newEngine(
alloc);
} else {
sslEngine = getOpenSslServerContext(sslConfig.getPkPath(),
sslConfig.getPasswd()).newEngine(alloc);
}
sslEngine.setUseClientMode(false);
sslEngine.setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1",
"TLSv1.2" });
// false为单向认证,true为双向认证
sslEngine.setNeedClientAuth(sslConfig.isNeedClientAuth());
return sslEngine;
}
public static SSLEngine getSslClientEngine(String pkPath, String caPath,
String passwd, boolean isNeedClientAuth) {
SSLEngine sslEngine = null;
if (isNeedClientAuth) {
sslEngine = getClientContext(pkPath, caPath, passwd)
.createSSLEngine();
} else {
sslEngine = getClientContext(pkPath, passwd).createSSLEngine();
}
sslEngine.setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1",
"TLSv1.2" });
sslEngine.setUseClientMode(true);
return sslEngine;
}
public static SSLEngine getOpenSslClientEngine(String pkPath,
String caPath, String passwd, ByteBufAllocator alloc,
boolean isNeedClientAuth) {
SSLEngine sslEngine = null;
if (isNeedClientAuth) {
sslEngine = getOpenSslClientContext(pkPath, caPath, passwd)
.newEngine(alloc);
} else {
sslEngine = getOpenSslClientContext(pkPath, passwd)
.newEngine(alloc);
}
sslEngine.setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1",
"TLSv1.2" });
sslEngine.setUseClientMode(true);
return sslEngine;
}
/**
*
* Description:
*
* @return
* @see
*/
public static SslHandler getSslHandler(SSLConfig sslConfig) {
if (sslConfig != null && sslConfig.isSSL()) {
return new SslHandler(getSslServerEngine(sslConfig));
} else {
return null;
}
}
public static SslHandler getOpenSslHandler(SSLConfig sslConfig,
ByteBufAllocator alloc) {
if (sslConfig != null && sslConfig.isSSL()) {
return new SslHandler(getOpenSslServerEngine(sslConfig, alloc));
} else {
return null;
}
}
}
三、使用很简单,按netty的标准用法就好:
SslHandler sslHandler = SSLContextFactory.getOpenSslHandler(
new SslConfig(), ch.alloc());
if (sslHandler != null) {
pipeline.addLast("ssl", sslHandler);
//添加一个提示成功的类
pipeline.addLast(new SslCheckServerHandler());
}
/*
* 文件名:SslCheckServerHandler.java
* 描述:
* 修改人:gogym
* 修改时间:2019年7月16日
* 跟踪单号:
* 修改单号:
* 修改内容:
*/
package org.gogym.gim.socket.tcp.ssl;
import java.nio.ByteBuffer;
import org.gogym.getty.channel.Channel;
import org.gogym.getty.channel.ChannelHandlerContext;
import org.gogym.getty.channel.SimpleChannelInboundHandler;
import org.gogym.getty.handler.ssl.SslHandler;
import org.gogym.getty.util.concurrent.Future;
import org.gogym.getty.util.concurrent.GenericFutureListener;
public class SslCheckServerHandler extends
SimpleChannelInboundHandler {
@Override
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
// Once session is secured, send a greeting and register the channel to
// the global channel
// list so the channel received the messages from others.
ctx.pipeline().get(SslHandler.class).handshakeFuture()
.addListener(new GenericFutureListener>() {
@Override
public void operationComplete(Future future)
throws Exception {
if (future.isSuccess()) {
System.out.println("握手成功");
} else {
System.out.println("握手失败");
}
}
});
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuffer msg)
throws Exception {
}
}
完