0%

springboot单向认证和双向认证

  • 单向认证
    服务端开启 SSL 证书,通过 springboot 或者 nginx 都可以实现, 开启单向认证后访问服务端接口需要使用 https 协议即可。
server:
  port: 8443
  ssl:
    enabled: true
    #服务端证书路径, classpath:local-dev.p12
    key-store: classpath:server.jks
    #证书密码
    key-store-passwd: 123456
    #证书类型
    key-store-type: JKS
  • 双向认证
    springboot 服务端除了开启 SSL 证书,还要开启客户端证书认证 client-auth: need, 需要将客户端证书导入到服务端信任库中, 否则访问报错, 提示无法建立连接, 不接受您的登录证书, 或者您可能没有提供登录证书 等报错信息.
server:
  port: 8443
  ssl:
    enabled: true
    #服务端证书路径, classpath:local-dev.p12
    key-store: classpath:server.jks
    #证书密码
    key-store-passwd: 123456
    #证书类型
    key-store-type: JKS
    #是否需要进行认证,可选: need/want/none
    client-auth: need
    #可信任的客户端证书, classpath:local-dev.p12
    trust-store: classpath:server.jks
    #密码,即步骤一中输入的密码
    trust-store-password: 123456
    trust-store-type: JKS

使用 keytool 创建证书

# 创建 test_server, 如果是单向认证创建完 test_server.jks 并配置正确即可.
keytool -genkeypair -alias test_server -keypass 123456 -storepass 123456  -dname "C=CN,ST=JS,L=NJ,O=test,OU=dev,CN=test.server.cn" -keyalg RSA -keysize 20
48 -validity 3650 -keystore test_server.jks

# 双向认证还需要进行以下步骤
# 使用 keytool 创建 test_client 证书库
keytool -genkeypair -alias test_client -keypass 123456 -storepass 123456  -dname "C=CN,ST=JS,L=NJ,O=test,OU=dev,CN=test.client.cn" -keyalg RSA -keysize 20
48 -validity 3650 -keystore test_client.jks
# 从证书库中导出客户端证书, 注意:加上 -rfc 选项输出PEM编码格式的证书, 否则导入服务端信任库会报错.
keytool -exportcert -keystore test_client.jks -rfc -file test_client.cer -alias test_client -storepass 123456
# 将客户端证书导入到服务端信任库
keytool -importcert -keystore test_server.jks -file test_client.cer -alias test_client -storepass 123456 -noprompt

在信任库中, 导入后的证书为 trustedCertEntry 实体类型,而私钥证书为 PrivateKeyEntry.

上述操作完成后, 就可以通过客户端证书调用服务端接口,但是想要服务端调用客户端,需要按照上述步骤将服务端证书导入到客户端信任库.

  • keytool 查看证书库详情
keytool -list -keystore test_server.jks -storepass 123456
# 加 -v 查看详情
keytool -list -v -keystore test_server.jks -storepass 123456
  • keytool 查看证书详情
keytool -printcert -file test_client.cer

openssl 创建自签名证书

# 创建私钥(.key)
openssl genrsa -out my.key 2048
# 基于私钥(.key)创建证书签名请求(.csr)
openssl req -new -key my.key -out my.csr -subj "/C=CN/ST=shanghai/L=shanghai/O=example/OU=it/CN=domain1/CN=domain2"
# (可选)直接同时生成私钥(.key)和证书签名请求(.csr)
openssl req -new -newkey rsa:2048 -nodes -keyout my.key -out my.csr -subj "/C=CN/ST=shanghai/L=shanghai/O=example/OU=it/CN=domain1/CN=domain2"
# 使用自己的私钥(.key)签署自己的证书签名请求(.csr),生成自签名证书(.crt)
openssl x509 -req -in my.csr -out my.crt -signkey my.key -days 3650
# (可选)直接同时生成私钥(.key)和自签名证书(.crt)
openssl req -x509 -newkey rsa:2048 -nodes -keyout my.key -out my.crt -days 3650  -subj "/C=CN/ST=shanghai/L=shanghai/O=example/OU=it/CN=domain1/CN=domain2"

openssl 创建私有CA签发的证书

# 生成CA私钥(ca.key)和CA自签名证书(ca.crt)
openssl req -x509 -newkey rsa:2048 -nodes -keyout ca.key -out ca.crt -days 3650  -subj "/C=CN/ST=shanghai/L=shanghai/O=example/OU=it/CN=domain1/CN=domain2"
# 生成Server端私钥(server.key)和证书签名请求(server.csr)
openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr -subj "/C=CN/ST=shanghai/L=shanghai/O=example/OU=it/CN=domain1/CN=domain2"
# 使用CA证书(ca.crt)与密钥(ca.key)签署服务器的证书签名请求(server.csr),生成私有CA签名的服务器证书(server.crt)
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650
# 验证server.crt是否真得是由ca签发的, 结果显示 ok
openssl verify -CAfile ca.crt server.crt

openssl创建的证书导入java证书信任库

keytool -importcert -keystore test_server.jks -file openssl_client.crt -alias test_client -storepass 123456 -noprompt

证书格式转换(参考链接)

  • jks / pkcs12 格式转换
# jks 转 p12
keytool -importkeystore -srckeystore server.keystore -destkeystore server.p12 -srcalias serverkey -destalias serverkey \
    -srcstoretype jks -deststoretype pkcs12 -srcstorepass 111111 -deststorepass 111111  -destkeypass  111111 -noprompt
# p12 转 jks 同理
keytool -importkeystore -srckeystore server.p12 -destkeystore server.keystore \
-srcstoretype pkcs12 -deststoretype jks -srcalias server -destalias server \
-deststorepass 111111 -srcstorepass 111111
  • Nginx 证书 转 JKS
    Java 通常使用 JKS 作为证书存储格式,而Nginx往往采用 PEM 证书格式
# pem证书和私钥合成p12,注意定义-name 选项,这将作为keystore识别实体的参数
openssl pkcs12 -export -in server.crt -inkey server.key -passin pass:111111 -password pass:111111 -name server -out server.p12
# p12 证书转jks 证书
keytool -importkeystore -srckeystore server.p12 -destkeystore server.keystore \
-srcstoretype pkcs12 -deststoretype jks -srcalias server -destalias server \
-deststorepass 111111 -srcstorepass 111111

如果 p12 文件中未指定实体名称,使用 keytool 转换时则不需提供 srcalias/destalias 参数,而输出的 keystore 实体名称默认为 1

  • JKS 证书 转 Nginx证书
# jks 证书转p12
keytool -importkeystore -srckeystore server.keystore  -destkeystore server.p12 \
-srcstoretype jks -deststoretype pkcs12 -srcalias server -destalias server \
-deststorepass 111111 -destkeypass 111111 -srcstorepass 111111
# p12 证书提取pem证书和私钥
openssl pkcs12 -in server.p12 -clcerts -nokeys -password pass:111111 -out server.crt
openssl pkcs12 -in server.p12  -nocerts -password pass:111111 -passout pass:111111 -out server.key

postman 双向认证测试

  1. 创建服务端信任库
  2. 创建客户端证书
  3. 将客户端证书添加到服务端信任库
  4. postman 设置选择:setting - General - 关闭SSL certificate verification
  5. postman 设置选择:setting - Certificates - Client Certificates - 选择 Add Certificates
  6. 设置地址端口号CAR fileKEY file,证书有密码就输入Passphrase
  7. 测试

# 查看证书过期时间
kubeadm alpha certs check-expiration
# 更新证书
kubeadm alpha certs renew all
# 证书如果没有生效,重启相关服务生效
docker ps | grep -E 'k8s_kube-apiserver|k8s_kube-controller-manager|k8s_kube-scheduler|k8s_etcd_etcd' | awk -F ' ' '{print $1}' | xargs docker restart

说明

双冒号( :: )运算符在 Java 8 中被用作方法引用(method reference),方法引用是与 lambda 表达式相关的一个重要特性。

语法

Kind Example
对静态方法的引用 ContainingClass::staticMethodName
对一个特定对象的实例方法的引用 containingObject::instanceMethodName
对一个任意对象特定类型的实例方法的引用 ContainingType::methodName
对构造函数的引用 ClassName::new

测试

public class MethodReferencesTest {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("java", "python", "go");
        // 静态方法引用
        list.stream().forEach(MethodReferencesTest::staticMethod);

        // 对象方法引用
        list.stream().forEach(new MethodReferencesTest()::method);

        List<String> list1 = Arrays.asList("2", "3", "4");
        list1.stream().map(Integer::valueOf).forEach(MethodReferencesTest::staticMethod);

    }

    private void method(String a) {
        System.out.println(a);
    }

    public static void staticMethod(String a) {
        System.out.println(a);
    }

    public static void staticMethod(int a) {
        System.out.println("int " + a);
    }
}

说明

springboot默认的打包插件

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

及对应的Dockerfile文件

FROM openjdk:8-jdk-alpine
MAINTAINER happywzy
WORKDIR application
EXPOSE 8080
ADD ./target/*.jar ./app.jar
CMD java  -jar app.jar

打包命令

mvn clean package
docker build -t test:1.0.0 .

这种方式打包 springboot 缺点是每次打包 docker 镜像都是全量上传 ja r包,如果 jar 很大会严重影响拉取镜像的速度.

springboot 分层打包

springboot 分层打包需要 springboot 版本大于 2.3.0.RELEASE.

开启

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <layers>
                    <enabled>true</enabled>
                </layers>
            </configuration>
        </plugin>
    </plugins>
</build>

再次使用 mvn clean package 命令打包,可以使用 java 包分析工具查看

java -Djarmode=layertools -jar target/test-0.0.1-SNAPSHOT.jar list

改造原有的 Dockerfile

FROM openjdk:8-jdk-alpine as builder
WORKDIR application
ADD ./target/*.jar ./app.jar
RUN java -Djarmode=layertools -jar app.jar extract

FROM openjdk:8-jdk-alpine
MAINTAINER happywzy

WORKDIR application

COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./

EXPOSE 8080

ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

这个 dockerfile 表示先进行一次临时镜像构建标记为 builder,并加载一次全量 jar 包,然后执行 java -Djarmode=layertools -jar app.jar extract 命令将 jar 包分解为分层打包目录,再次构建一个新镜像,按照list 的目录顺序分批将分层目录加载到 docker 镜像中. 再次执行构建命令

docker build -t test:1.0.0 .

docker镜像分析

可以使用 docker inspectdocker historydive 等工具对比全量打包和分层打包的区别.

说明

docker 镜像分析工具有 docker 自带的 docker inspectdocker history,对于具体每一层(layers)组成可以使用 dive 工具. 地址:https://github.com/wagoodman/dive,该工具主要用于探索 docker 镜像层内容以及发现减小 docker 镜像大小的方法.

docker安装

docker pull wagoodman/dive

使用

# windows 下 docker.sock 路径可以使用 -v //var/run/docker.sock:/var/run/docker.sock
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive:latest <dive arguments...>
# 示例
docker run --rm -it -v //var/run/docker.sock:/var/run/docker.sock wagoodman/dive:latest test:v1.2

二进制安装

# centos
curl -OL https://github.com/wagoodman/dive/releases/download/v0.9.2/dive_0.9.2_linux_amd64.rpm
rpm -i dive_0.9.2_linux_amd64.rpm
# windows
go get github.com/wagoodman/dive

使用

dive <your-image> --source <source>
# or 
dive <source>://<your-image>