netty客戶端:
package com.lyf.csdn.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* 客戶端:啓動時發送1,服務器返回該整數的平方,添加一個心跳,每隔一段時間發送一個隨機數
*
* @author xuanchi.lyf
*/
public class NettyClient {
private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
public static void main(String[] args) {
EventLoopGroup workerGroup = new NioEventLoopGroup();
DefaultEventExecutorGroup bizGroup = new DefaultEventExecutorGroup(5);
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
IdleStateHandler idleStateHandler = new IdleStateHandler(20, 20, 20,
TimeUnit.SECONDS);
ch.pipeline().addLast(idleStateHandler);
ch.pipeline().addLast(new HeartBeatHandler());
ch.pipeline().addLast("decoder", new NettyDecoder());
ch.pipeline().addLast("encoder", new NettyEncoder());
ch.pipeline().addLast(bizGroup, new ClientHandler());
}
});
ChannelFuture f = b.connect("127.0.0.1", 9876).sync();
f.channel().closeFuture().sync();
} catch (Exception e) {
logger.info(e.getMessage(), e);
}
logger.info("netty tcp connection build success.");
}
}
/**
* 心跳發送[0000]
*/
class HeartBeatHandler extends ChannelInboundHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleState state = ((IdleStateEvent) evt).state();
if (state == IdleState.WRITER_IDLE) {
ctx.channel().writeAndFlush(new Random().nextInt(100));
}
} else {
super.userEventTriggered(ctx, evt);
}
}
}
class ClientHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(ClientHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) {
logger.info("channel active");
ctx.channel().writeAndFlush(1);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
logger.info("channel inActive");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
logger.info("message = {}.", msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.info("throw exception", cause);
}
}
netty服務器端:
package com.lyf.csdn.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
/**
* 服務器端
*
* @author xuanchi.lyf
*/
public class NettyServer {
private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
DefaultEventExecutorGroup bizGroup = new DefaultEventExecutorGroup(50);
try {
ServerBootstrap b = new ServerBootstrap().group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 128)
.option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
.childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)
.childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
.childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
.childOption(ChannelOption.ALLOW_HALF_CLOSURE, Boolean.FALSE)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) {
channel.pipeline().addLast("decoder", new NettyDecoder());
channel.pipeline().addLast("encoder", new NettyEncoder());
channel.pipeline().addLast(bizGroup, new BizHandler());
}
});
ChannelFuture f = b.bind(9876).sync();
f.channel().closeFuture().sync();
} catch (Exception e) {
logger.info(e.getMessage(), e);
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
class NettyDecoder extends LengthFieldBasedFrameDecoder {
NettyDecoder() {
super(1024, 0, 4);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame = (ByteBuf) super.decode(ctx, in);
if (frame == null) {
return null;
}
int dataLength = frame.readInt();
if (dataLength == 0) {
return null;
}
return frame.readInt();
}
}
class NettyEncoder extends MessageToByteEncoder<Serializable> {
@Override
protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) {
out.writeInt(4);
out.writeInt((Integer) msg);
}
}
class BizHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(BizHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) {
logger.info("channel active.");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
int data = (Integer) msg;
ctx.channel().writeAndFlush(data * data);
logger.info("message = {}.", msg);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
logger.info("channel inActive.");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.info("throw exception", cause);
}
}
以上我們可以服務器和客戶端建立連接,正常的收發數據了。現在我們需要給netty加上SSL。其中證書可以是單向認證,也可以雙向認證,兩種方式各有特點。
單向認證是在服務器添加證書,客戶端不添加證書,這樣客戶端需要校驗服務器證書,但是服務器不用校驗客戶端證書,這種情況客戶端是安全的,因爲能夠保證服務器是自己人,但是服務器本身不安全,因爲服務器不知道客戶端證書,沒法校驗。這種場景下主要是針對WEB場景。
雙向認證是服務器端同時校驗客戶端身份信息,在點對點通信場景很重要。雙向任務的問題是客戶端證書過期了,如何更換客戶端證書,這個問題很要命。
二、自簽證書
1、生成服務器端和客戶端keystore
keytool -genkey -alias server -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass mypassword -storepass mypassword -keystore server.jks
keytool -genkey -alias client -keysize 2048 -validity 365 -keyalg RSA -dname "CN=localhost" -keypass mypassword -storepass mypassword -keystore client.jks
其中:
-alias:表示祕鑰對的名稱
-keysize:祕鑰長度,1024已經不安全了,至少2048起步
-keypass、-storepass:密碼,保持一樣就好
-keystore:生成keystore文件名
2、導出證書
keytool -export -alias server -keystore server.jks -storepass mypassword -file server.cer
keytool -export -alias client -keystore client.jks -storepass mypassword -file client.cer
3、將服務器端證書導入客戶端的keystore(供客戶端驗證服務器證書)
keytool -import -trustcacerts -alias server -file server.cer -storepass mypassword -keystore client.jks
4、將客戶端證書導入服務器端的keystore(供服務器端驗證客戶端證書)
keytool -import -trustcacerts -alias client -file client.cer -storepass mypassword -keystore server.jks
三、單向認證
服務器端代碼:
package com.lyf.csdn.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.security.KeyStore;
/**
* 服務器端
*
* @author xuanchi.lyf
*/
public class NettyServer {
private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);
private static SSLEngine sslEngine;
static {
String sChatPath = "/Users/xuanchi.lyf/Desktop/testNetty/server.jks";
sslEngine = ServerSslContextFactory.getServerContext(sChatPath).createSSLEngine();
sslEngine.setUseClientMode(false);
}
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
DefaultEventExecutorGroup bizGroup = new DefaultEventExecutorGroup(50);
try {
ServerBootstrap b = new ServerBootstrap().group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 128)
.option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
.childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)
.childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
.childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
.childOption(ChannelOption.ALLOW_HALF_CLOSURE, Boolean.FALSE)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) {
channel.pipeline().addLast("ssl", new SslHandler(sslEngine));
channel.pipeline().addLast("decoder", new NettyDecoder());
channel.pipeline().addLast("encoder", new NettyEncoder());
channel.pipeline().addLast(bizGroup, new BizHandler());
}
});
ChannelFuture f = b.bind(9876).sync();
f.channel().closeFuture().sync();
} catch (Exception e) {
logger.info(e.getMessage(), e);
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
class ServerSslContextFactory {
private static final String PROTOCOL = "TLS";
private static SSLContext SERVER_CONTEXT;
static SSLContext getServerContext(String pkPath) {
if (SERVER_CONTEXT != null) {
return SERVER_CONTEXT;
}
InputStream inputStream = null;
try {
KeyManagerFactory keyManagerFactory;
if (pkPath != null) {
//密鑰庫KeyStore
KeyStore keyStore = KeyStore.getInstance("JKS");
//加載服務端證書
inputStream = new FileInputStream(pkPath);
//加載服務端的KeyStore ;sNetty是生成倉庫時設置的密碼,用於檢查密鑰庫完整性的密碼
keyStore.load(inputStream, "mypassword".toCharArray());
keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
//初始化密鑰管理器
keyManagerFactory.init(keyStore, "mypassword".toCharArray());
//獲取安全套接字協議(TLS協議)的對象
SERVER_CONTEXT = SSLContext.getInstance(PROTOCOL);
// 初始化此上下文
// 參數一:認證的密鑰
// 參數二:對等信任認證
// 參數三:僞隨機數生成器
// 由於單向認證,服務端不用驗證客戶端,所以第二個參數爲null
SERVER_CONTEXT.init(keyManagerFactory.getKeyManagers(), null, null);
}
} catch (Exception e) {
throw new Error("Failed to initialize the server-side SSLContext", e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return SERVER_CONTEXT;
}
}
class NettyDecoder extends LengthFieldBasedFrameDecoder {
NettyDecoder() {
super(1024, 0, 4);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame = (ByteBuf) super.decode(ctx, in);
if (frame == null) {
return null;
}
int dataLength = frame.readInt();
if (dataLength == 0) {
return null;
}
return frame.readInt();
}
}
class NettyEncoder extends MessageToByteEncoder<Serializable> {
@Override
protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) {
out.writeInt(4);
out.writeInt((Integer) msg);
}
}
class BizHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(BizHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) {
logger.info("channel active.");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
int data = (Integer) msg;
ctx.channel().writeAndFlush(data * data);
logger.info("message = {}.", msg);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
logger.info("channel inActive.");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.info("throw exception", cause);
}
}
客戶端代碼:
package com.lyf.csdn.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* 客戶端
*
* @author xuanchi.lyf
*/
public class NettyClient {
private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
private static SSLEngine sslEngine;
static {
String clientKeystorePath = "/Users/xuanchi.lyf/Desktop/testNetty/client.jks";
sslEngine = ClientSslContextFactory.getClientContext(clientKeystorePath).createSSLEngine();
sslEngine.setUseClientMode(true);
}
public static void main(String[] args) {
EventLoopGroup workerGroup = new NioEventLoopGroup();
DefaultEventExecutorGroup bizGroup = new DefaultEventExecutorGroup(5);
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
IdleStateHandler idleStateHandler = new IdleStateHandler(1, 1, 1,
TimeUnit.SECONDS);
ch.pipeline().addLast("ssl", new SslHandler(sslEngine));
ch.pipeline().addLast("idleCheck", idleStateHandler);
ch.pipeline().addLast("heartbeat", new HeartBeatHandler());
ch.pipeline().addLast("decoder", new NettyDecoder());
ch.pipeline().addLast("encoder", new NettyEncoder());
ch.pipeline().addLast(bizGroup, new ClientHandler());
}
});
ChannelFuture f = b.connect("127.0.0.1", 9876).sync();
f.channel().closeFuture().sync();
} catch (Exception e) {
logger.info(e.getMessage(), e);
}
logger.info("netty tcp connection build success.");
}
}
class ClientSslContextFactory {
private static final String PROTOCOL = "TLS";
private static SSLContext CLIENT_CONTEXT;
static SSLContext getClientContext(String caPath) {
if (CLIENT_CONTEXT != null) {
return CLIENT_CONTEXT;
}
InputStream inputStream = null;
try {
//信任庫
TrustManagerFactory trustManagerFactory = null;
if (caPath != null) {
//密鑰庫KeyStore
KeyStore tks = KeyStore.getInstance("JKS");
//加載客戶端證書
inputStream = new FileInputStream(caPath);
tks.load(inputStream, "mypassword".toCharArray());
trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
// 初始化信任庫
trustManagerFactory.init(tks);
}
CLIENT_CONTEXT = SSLContext.getInstance(PROTOCOL);
//設置信任證書
TrustManager[] trustManagers = trustManagerFactory == null ? null
: trustManagerFactory.getTrustManagers();
CLIENT_CONTEXT.init(null, trustManagers, null);
} catch (Exception e) {
throw new Error("Failed to initialize the client-side SSLContext");
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return CLIENT_CONTEXT;
}
}
/**
* 心跳發送[0000]
*/
class HeartBeatHandler extends ChannelInboundHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleState state = ((IdleStateEvent) evt).state();
if (state == IdleState.WRITER_IDLE) {
ctx.channel().writeAndFlush(new Random().nextInt(100));
}
} else {
super.userEventTriggered(ctx, evt);
}
}
}
class ClientHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(ClientHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) {
logger.info("channel active");
ctx.channel().writeAndFlush(1);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
logger.info("channel inActive");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
logger.info("message = {}.", msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.info("throw exception", cause);
}
}
單向認證,客戶端需要認證服務器身份,此時我們可以構造一個新服務器證書,換上試一下。
io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: General SSLEngine problem
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:141)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:645)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:580)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:497)
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.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
Caused by: javax.net.ssl.SSLHandshakeException: General SSLEngine problem
at sun.security.ssl.Handshaker.checkThrown(Handshaker.java:1478)
at sun.security.ssl.SSLEngineImpl.checkTaskThrown(SSLEngineImpl.java:535)
at sun.security.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:813)
at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:781)
at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:624)
at io.netty.handler.ssl.SslHandler$SslEngineType$3.unwrap(SslHandler.java:292)
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1248)
at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1159)
at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1194)
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489)
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428)
... 16 more
Caused by: javax.net.ssl.SSLHandshakeException: General SSLEngine problem
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1728)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:304)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1514)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1026)
at sun.security.ssl.Handshaker$1.run(Handshaker.java:966)
at sun.security.ssl.Handshaker$1.run(Handshaker.java:963)
at java.security.AccessController.doPrivileged(Native Method)
at sun.security.ssl.Handshaker$DelegatedTask.run(Handshaker.java:1416)
at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1408)
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1316)
... 20 more
Caused by: sun.security.validator.ValidatorException: Certificate signature validation failed
at sun.security.validator.SimpleValidator.engineValidate(SimpleValidator.java:215)
at sun.security.validator.Validator.validate(Validator.java:260)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:281)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:136)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1501)
... 28 more
Caused by: java.security.SignatureException: Signature does not match.
at sun.security.x509.X509CertImpl.verify(X509CertImpl.java:449)
at sun.security.x509.X509CertImpl.verify(X509CertImpl.java:392)
at sun.security.validator.SimpleValidator.engineValidate(SimpleValidator.java:213)
... 33 more
說明客戶端驗證了服務器的證書。
四、雙向認證
服務器端:
package com.lyf.csdn.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.security.KeyStore;
import java.security.SecureRandom;
/**
* 服務器端
*
* @author xuanchi.lyf
*/
public class NettyServer {
private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);
private static SSLEngine sslEngine;
static {
String sChatPath = "/Users/xuanchi.lyf/Desktop/testNetty/server.jks";
sslEngine = ServerSslContextFactory.getServerContext(sChatPath, sChatPath)
.createSSLEngine();
sslEngine.setUseClientMode(false);
sslEngine.setNeedClientAuth(true);
}
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
DefaultEventExecutorGroup bizGroup = new DefaultEventExecutorGroup(50);
try {
ServerBootstrap b = new ServerBootstrap().group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 128)
.option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
.childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)
.childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
.childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
.childOption(ChannelOption.ALLOW_HALF_CLOSURE, Boolean.FALSE)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) {
channel.pipeline().addLast("ssl", new SslHandler(sslEngine));
channel.pipeline().addLast("decoder", new NettyDecoder());
channel.pipeline().addLast("encoder", new NettyEncoder());
channel.pipeline().addLast(bizGroup, new BizHandler());
}
});
ChannelFuture f = b.bind(9876).sync();
f.channel().closeFuture().sync();
} catch (Exception e) {
logger.info(e.getMessage(), e);
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
class ServerSslContextFactory {
private static final String PROTOCOL = "TLS";
private static SSLContext SERVER_CONTEXT;
static SSLContext getServerContext(String pkPath, String caPath) {
if (SERVER_CONTEXT != null) {
return SERVER_CONTEXT;
}
InputStream inputStream1 = null;
InputStream inputStream2 = null;
try {
KeyManagerFactory keyManagerFactory;
if (pkPath != null) {
//密鑰庫KeyStore
KeyStore keyStore = KeyStore.getInstance("JKS");
//加載服務端證書
inputStream1 = new FileInputStream(pkPath);
//加載服務端的KeyStore ;sNetty是生成倉庫時設置的密碼,用於檢查密鑰庫完整性的密碼
keyStore.load(inputStream1, "mypassword".toCharArray());
keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
//初始化密鑰管理器
keyManagerFactory.init(keyStore, "mypassword".toCharArray());
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
//信任庫
TrustManagerFactory trustManagerFactory = null;
if (caPath != null) {
KeyStore tks = KeyStore.getInstance("JKS");
inputStream2 = new FileInputStream(caPath);
tks.load(inputStream2, "mypassword".toCharArray());
trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
trustManagerFactory.init(tks);
}
TrustManager[] trustManagers = trustManagerFactory == null ? null
: trustManagerFactory.getTrustManagers();
SERVER_CONTEXT = SSLContext.getInstance(PROTOCOL);
SERVER_CONTEXT.init(keyManagers, trustManagers, new SecureRandom());
}
} catch (Exception e) {
throw new Error("Failed to initialize the server-side SSLContext", e);
} finally {
if (inputStream1 != null) {
try {
inputStream1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream2 != null) {
try {
inputStream2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return SERVER_CONTEXT;
}
}
class NettyDecoder extends LengthFieldBasedFrameDecoder {
NettyDecoder() {
super(1024, 0, 4);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame = (ByteBuf) super.decode(ctx, in);
if (frame == null) {
return null;
}
int dataLength = frame.readInt();
if (dataLength == 0) {
return null;
}
return frame.readInt();
}
}
class NettyEncoder extends MessageToByteEncoder<Serializable> {
@Override
protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) {
out.writeInt(4);
out.writeInt((Integer) msg);
}
}
class BizHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(BizHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) {
logger.info("channel active.");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
int data = (Integer) msg;
ctx.channel().writeAndFlush(data * data);
logger.info("message = {}.", msg);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
logger.info("channel inActive.");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.info("throw exception", cause);
}
}
客戶端:
package com.lyf.csdn.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* 客戶端
*
* @author xuanchi.lyf
*/
public class NettyClient {
private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
private static SSLEngine sslEngine;
static {
String clientKeystorePath = "/Users/xuanchi.lyf/Desktop/testNetty/client.jks";
sslEngine = ClientSslContextFactory.getClientContext(clientKeystorePath, clientKeystorePath)
.createSSLEngine();
sslEngine.setUseClientMode(true);
}
public static void main(String[] args) {
EventLoopGroup workerGroup = new NioEventLoopGroup();
DefaultEventExecutorGroup bizGroup = new DefaultEventExecutorGroup(5);
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
IdleStateHandler idleStateHandler = new IdleStateHandler(1, 1, 1,
TimeUnit.SECONDS);
ch.pipeline().addLast("ssl", new SslHandler(sslEngine));
ch.pipeline().addLast("idleCheck", idleStateHandler);
ch.pipeline().addLast("heartbeat", new HeartBeatHandler());
ch.pipeline().addLast("decoder", new NettyDecoder());
ch.pipeline().addLast("encoder", new NettyEncoder());
ch.pipeline().addLast(bizGroup, new ClientHandler());
}
});
ChannelFuture f = b.connect("127.0.0.1", 9876).sync();
f.channel().closeFuture().sync();
} catch (Exception e) {
logger.info(e.getMessage(), e);
}
logger.info("netty tcp connection build success.");
}
}
class ClientSslContextFactory {
private static final String PROTOCOL = "TLS";
private static SSLContext CLIENT_CONTEXT;
static SSLContext getClientContext(String pkPath, String caPath) {
if (CLIENT_CONTEXT != null) {
return CLIENT_CONTEXT;
}
InputStream inputStream1 = null;
InputStream inputStream2 = null;
try {
KeyManagerFactory keyManagerFactory;
if (pkPath != null) {
//密鑰庫KeyStore
KeyStore keyStore = KeyStore.getInstance("JKS");
//加載服務端證書
inputStream1 = new FileInputStream(pkPath);
//加載服務端的KeyStore ;sNetty是生成倉庫時設置的密碼,用於檢查密鑰庫完整性的密碼
keyStore.load(inputStream1, "mypassword".toCharArray());
keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
//初始化密鑰管理器
keyManagerFactory.init(keyStore, "mypassword".toCharArray());
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
//信任庫
TrustManagerFactory trustManagerFactory = null;
if (caPath != null) {
KeyStore tks = KeyStore.getInstance("JKS");
inputStream2 = new FileInputStream(caPath);
tks.load(inputStream2, "mypassword".toCharArray());
trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
trustManagerFactory.init(tks);
}
TrustManager[] trustManagers = trustManagerFactory == null ? null
: trustManagerFactory.getTrustManagers();
CLIENT_CONTEXT = SSLContext.getInstance(PROTOCOL);
CLIENT_CONTEXT.init(keyManagers, trustManagers, new SecureRandom());
}
} catch (Exception e) {
throw new Error("Failed to initialize the server-side SSLContext", e);
} finally {
if (inputStream1 != null) {
try {
inputStream1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream2 != null) {
try {
inputStream2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return CLIENT_CONTEXT;
}
}
/**
* 心跳發送[0000]
*/
class HeartBeatHandler extends ChannelInboundHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleState state = ((IdleStateEvent) evt).state();
if (state == IdleState.WRITER_IDLE) {
ctx.channel().writeAndFlush(new Random().nextInt(100));
}
} else {
super.userEventTriggered(ctx, evt);
}
}
}
class ClientHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(ClientHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) {
logger.info("channel active");
ctx.channel().writeAndFlush(1);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
logger.info("channel inActive");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
logger.info("message = {}.", msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.info("throw exception", cause);
}
}
重點理解:CLIENT_CONTEXT.init(keyManagers, trustManagers, new SecureRandom());
第一個參數是是允許對方校驗自己的證書,作用是加密
第二個參數是信任證書列表,作用是鑑權
第三個參數是隨機種子
1、最嚴密的策略是雙方互相校驗對方證書,並且只信任對方證書
2、其次可以雙方互相校驗對方證書,並且信任所有證書
當信任所有證書:
new TrustManager[] {new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}}