概要
网络世界 君子还是少数, 为了防范不法分子窃取我们的消息, 一般都会对消息进行加密.
当然这场景相对于Java来说还是少数, 毕竟现在大部分已经上各种云,服务和服务之间通信不需要经过公网
但如果你的通信链路可能需要经过外网, 那么极其建议你套一层安全层!!
TIPS: 鉴于网络安全手段之多, 读者需要自行辨别使用哪一种解决方案, 是黑白名单,接入认证, AES,TLS,UP, 还是CA
一般而言, 离公网越近, 防范手段越多, 离私网越近, 防范手段越少
接下来将展示如何使用Nettyx来让你的应用提供对Openssl的支持
先引入Nettyx的依赖
请从maven中央仓获取{lastest.version},最新版本号
<dependency>
<groupId>io.github.fbbzl</groupId>
<artifactId>nettyx</artifactId>
<version>{lastest.version}</version>
</dependency>
前言
使用OpenSsl的 懵逼点 其实是在如何生成证书上, 如果是大型企业,证书的颁发会有专门的安全部门管理, 但像一些小项目, 创业公司等可能并没有, 需要开发人员自己生成配套证书, 这一点新手可能会摸不着头脑.
开始
首先确认生成证书的操作系统是否已经安装Openssl,大部分Linux发行版都是自带OpenSsl的, 安装部分不做介绍, 各位大聪明自行解决,只要确保生成证书的机器上安装完Openssl工具即可
OK,我们继续往下
Nettyx提供了一键初始化Openssl的脚本, 在源码同级src目录下存在一个support目录,其中的openssl子目录便存放了该脚本, 使用时必须去gitee和github上下载源码,而不是引入依赖这么简单, 后续会考虑提供api来调用生成
源码地址:
gitee: https://gitee.com/fbbzl/nettyx
github: https://github.com/fbbzl/nettyx
接下来将介绍support/openssl/linux目录
- ./ca: 存放了生成ca证书的脚本 openssl_init_ca_root.sh, 用于生成根证书
- ./client: 存放了客户端的三个证书脚本 openssl_client_do_sign.sh, openssl_client_init.sh, openssl_client_pkcs8_key.sh. 分别是用来签名的脚本, 初始化客户端证书的脚本, 转pkcs8格式证书的脚本,读者可以根据自己的需求修改脚本内的一些值, 比如证书有效期
- ./cnf: 存放了openssl的配置文件openssl.cnf
- ./server: 存放了服务端的三个证书脚本 openssl_server_do_sign.sh,openssl_server_init.sh,openssl_server_pkcs8_key.sh三个证书和客户端功能一样
- ./openssl_auto_all.sh: 一键初始化 [所有] 证书, 会生成整套的证书,包括CARoot, Client和Server的,如果是首次生成建议使用此脚本
- ./openssl_auto_client.sh: 一键初始化 [客户端] 并用root证书进行签名, 客户端用
- ./openssl_auto_server: 一键初始化 [服务端] 并用root证书进行签名, 服务端用
- ./openssl_env.sh: 脚本环境变量配置, 不用的脚本用户, 所在的环境不同, 环境变量也可能会不一样, 在读者执行脚本之前,请先设置此脚本中的值, 必须配置!!!
- ./openssl_init_dir.sh: 初始化openssl工作目录, 如果是第一次生成openssl证书, 需要执行此脚本
虽然我每个脚本功能都介绍了一遍, 但是实际读者只需要关心./openssl_auto_all.sh即可, 唯一要注意的是所有脚本用户必须配置./openssl_env.sh!!!因为这是脚本的执行环境配置, 所有用户都不一样, , 整个生成证书的过程中需要人机交互, 根据控制台提示输入指定信息即可,例如需要输入公司名称,组织名称, 地域信息,城市省份什么的,按照实际信息填写即可, 证书密码别输错了
假设你现在已经生成好证书了, 继续往下看
OpenSslContextFactory
证书生成之后的使用反而是很简单的,只需要在应用中配置 根证书和自端签名后的证书, 密码即可, Nettyx提供了OpenSslContextFactory来获取一个SslContext, 尽量复用SslContext,它是Netty实现SSL的关键!!!
以下将展示如何初始化一个OpenSslContextFactory
@Configuration
public class SslConfiguration {
// 这里只是简单展示如何配置, 其实不建议将配置放入java代码, 如何将ssl的配置放入yml,properties配置文件, 相信各位大聪明知道如何解决, 在此不做讲解
@Bean
public OpenSslContextFactory sslContextFactory() {
OpenSslConfig openSslConfig = new OpenSslConfig();
// 必填 根证书
openSslConfig.setRoot('root 证书的绝对路径, 例如: /etc/openssl/root');
// 必填 自端证书, 如果是客户端则配置客户端证书路径, 如果是服务端则配置服务端证书路径
openSslConfig.setCert('自端证书的绝对路径,client配client自己的, server端则配server自己的,例如/etc/openssl/client/xx_client.crt');
// 必填 jks file就是带pkcs8的文件
openSslConfig.setKey('pkcs8格式的key文件');
// 非必填 pkcs8文件的密码, 如果没有则不用配置
openSslConfig.setKeyPass('key文件密码');
return new OpenSslContextFactory(openSslConfig);
}
}
在完成了OpenSslContextFactory的构建之后, 离成功只差一步了, 接下来通过下图中的API获取SslContext, 客户端和服务端依然是不一样的
首先看下客户端的Channel初始化器如何使用
@Slf4j
@RequiredArgsConstructor
public class TestChannelInitializer<C extends Channel> extends ChannelInitializer<C> {
// 通过构造参数传入sslFactory即可, 不要把ChannelInitializer变成bean, 如果ChannelInitializer的初始化有重复代码
// 可以尝试使用Factory模式, 比如建一个ChannelInitializerFactory组件等等
private final OpenSslContextFactory sslFactory;
@Override
protected void initChannel(C channel) throws SSLException {
// 建议使用 spring的配置bean来配置openssl的证书的目录, 这里为了演示, 直接new
// 客户端和服务端不同的是, 客户端可能会有多个, 如果多个客户端使用同一份客户端签名证书, 这虽然降低了证书管理和应用部署的难度, 但是安全系数降低了,一个被破解就全部都破解, 但是如果多个客户端生成自己各自的签名证书, 安全度提升但是管理和部署的难度又会提升, 怎么说呢, 没有银弹, 还请各位自行斟酌
// 尽量复用SslContext实例, 此处获取的是客户端的sslcontext
SslContext clientSslContext = sslFactory.getClientSslContext();
SslHandler clientSslHandler = clientSslContext.newHandler(channel.alloc());
channel.pipeline().addLast(
// 如果程序 既要接收ssl又要接收非ssl, 可以使用OptionalSslHandler
clientSslHandler
, new StartEndFlagFrameCodec(320, true, wrappedBuffer(new byte[]{(byte) 0x7e}))
, new EscapeCodec(EscapeMap.mapHex("7e", "7d5e"))
, new UserCodec()
, new LoggerHandler(log, INFO));
}
}
接下来是服务端的Channel初始化器, 和客户顿的ssl就只是证书的目录不一样, 其他几乎一样
@Slf4j
public class TestChannelInitializer<C extends Channel> extends ChannelInitializer<C> {
@Override
protected void initChannel(C channel) throws SSLException {
// 建议使用 spring的配置bean, 这里为了演示, 直接new
OpenSslConfig serverSslConfig = new OpenSslConfig();
serverSslConfig.setCert("/usr/local/yourapp/ssl/server/xxx.cer");
serverSslConfig.setKey("/usr/local/yourapp/ssl/server/xxx.key");
serverSslConfig.setKeyPass("a$5sdf2@##$");
serverSslConfig.setRoot("/usr/local/yourapp/ssl/root/yyy.cer");
// 尽量复用SslContext实例, 此处获取的是客户端的sslcontext
SslContext serverSslContext = new OpenSslContextFactory(serverSslConfig).getServerSslContext();
SslHandler serverSslHandler = serverSslContext.newHandler(channel.alloc());
channel.pipeline().addLast(
// 如果应用既要接收ssl又要接收非ssl, 可以使用OptionalSslHandler,它会自动判断是否是sslmessage的
serverSslHandler
, new StartEndFlagFrameCodec(320, true, wrappedBuffer(new byte[]{(byte) 0x7e}))
, new EscapeCodec(EscapeMap.mapHex("7e", "7d5e"))
, new UserCodec()
, new LoggerHandler(log, INFO));
}
}
至此我们完成了 客户端 和 服务端 的Ssl配置, 此时应用就具备ssl通信的能力了