Spring Boot 实现https ssl免密登录(X.509 pki登录)

要让项目实现ssl免密登录,首先需要开启https
所以先从Spring Boot如何开启https说起。

创建服务端证书

为了开启https,我们需要一份证书。

实际开发中,会在网上申请一个机构颁发的证书。这里为了方便,我会使用openssl指令自己生成一个证书来使用。

openssl req -x509 -sha256 -days 3650 -newkey rsa:4096 -keyout rootCA.key -out rootCA.crt

image.png

所有的密码都是123456,然后根据提示输入相关信息就好,如果嫌麻烦也可以直接回车跳过。

这样我们就得到了证书rootCA.crt和私钥rootCA.key

要在Spring Boot中实现服务器端X.509身份验证,还需要给我们的服务端也生成一个证书。

openssl req -new -newkey rsa:4096 -keyout localhost.key -out localhost.csr

同样,密码是123456,文件名localhost可以自行修改。

接下来就是用rootCA给我们的服务端证书做签名了,在此之前,我们先写一个配置文件,里面写有一些基本的配置

vi conf.config
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost

其中DNS.1的值就是你的域名,比如www.segmentfault.comlocalhost等等。如果这里填错了,访问网站时,浏览器会提示网站不安全。

然后给服务端证书签名,会提示你输入rootCA的密码

openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in localhost.csr -out localhost.crt -days 365 -CAcreateserial -extfile conf.config

成功后,让我们查看一下证书的信息

openssl x509 -in localhost.crt -text

image.png

最后再将签名证书和私钥打包到PKCS文件中

openssl pkcs12 -export -out localhost.p12 -name "localhost" -inkey localhost.key -in localhost.crt

这条指令会要你先输入localhost.key的密码,然后再要你定义localhost.p12的密码。localhost.p12这个密码一定要记住,因为在Spring的配置文件中有用到。
另外需要特别注意的是,Spring配置文件中server.ssl.keyAlias的值,就是指令中的localhost(-name "localhost")

Spring Boot开启https

localhost.p12复制到resources目录下之后编译项目

image.png

修改application.properties文件

server.port=8888
server.ssl.key-store=classpath:localhost.p12
server.ssl.key-store-password=123456
server.ssl.keyStoreType=PKCS12
server.ssl.keyAlias=localhost

chrome://settings/security中,选择受信任的根证书颁发机构导入rootCA.crt

image.png

这时启动项目,就可以使用https访问网站了,而且浏览器提示网站是安全的。

image.png

创建信托证书

信托证书中会存有信任的外部实体的证书
这里我们只要将rootCA.crt添加进去就可以了

keytool -import -trustcacerts -noprompt -alias ca -ext san=dns:localhost,ip:127.0.0.1 -file rootCA.crt -keystore localhost.jks

然后将localhost.jks添加到项目中,并修改配置文件

image.png

application.properties添加:

server.ssl.trust-store=classpath:localhost.jks
server.ssl.trust-store-password=123456
server.ssl.client-auth=need

注意:此时由于添加了server.ssl.client-auth=need,因为没有添加个人证书,所以这个时候刷新页面,项目会无法访问,如果想要同时兼任普通登录,可以将need改成want,但是want只会在第一次访问页面时才会向客户索取个人证书

image.png

创建客户端证书

现在创建一个客户端的证书(个人证书),步骤和服务端的差不多一样。

openssl req -new -newkey rsa:4096 -nodes -keyout shurlormes.key -out shurlormes.csr

image.png

在生成客户端证书时,那些信息不建议跳过,因为在后续的步骤中,会获取其中的信息用以登录。比如我在Common Name处填写的信息,就是等下用来登录的用户名。

接下来用RootCA给客户端证书签名

openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in shurlormes.csr -out shurlormes.crt -days 365 -CAcreateserial

然后再将签名证书和私钥打包到PKCS文件中

openssl pkcs12 -export -out shurlormes.p12 -name "shurlormes" -inkey shurlormes.key -in shurlormes.crt

最后在chrome://settings/security选择个人证书shurlormes.p12导入,期间会要你输入它的密码。

PW}D(IN0%Q{[{G0X43KJWE3.png

2DLC301CH$(W9Y54@[P[FI0.png

ORX86_DV8RC9K7DM~MK[2MB.png

这时候刷新页面,浏览器就会弹出一个对话框,让你选择个人认证了。

image.png

Spring Boot获取个人证书信息

恭喜你,到了这一步,pki登录已经完成了99%了。接下来就是通过 request 获取证书信息,然后处理字符串,拿到用户名做登录即可。

@RequestMapping("/login")
public String login(HttpServletRequest request) {
    X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
    if(certs != null) {
        X509Certificate gaX509Cert = certs[0];
        String dn = gaX509Cert.getSubjectDN().toString();
        System.out.println("个人证书信息:" + dn);
        String username = "";
        String[] dnArray = dn.split(",");
        for (String dnItem : dnArray) {
            String[] dnInfo = dnItem.split("=");
            String key = dnInfo[0];
            String value = dnInfo[1];
            if("cn".equalsIgnoreCase(key.trim())) {
                username = value;
                break;
            }
        }
        System.out.println("用户名:" + username);

        if(!StringUtils.isEmpty(username)) {
            SecurityContext securityContext = SecurityContextHolder.getContext();
            User userDetails = new User(username, "", Collections.EMPTY_LIST);
            securityContext.setAuthentication(new UsernamePasswordAuthenticationToken(userDetails, "", Collections.EMPTY_LIST));
            return "redirect:/";
        }

    }
    return "login";
}

image.png

image.png

Spring Boot 同时开启http和https

相信大家都发现了,现在项目只能通过https访问,如果用http访问浏览器直接返回Bad request了。

image.png

要同时开启httpshttp,只需添加一个TomcatConfig就可以

@Configuration
public class TomcatHttpConfig {
    @Bean
    public TomcatServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        tomcat.addAdditionalTomcatConnectors(initiateHttpConnector());
        return tomcat;
    }

    private Connector initiateHttpConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        connector.setPort(9999);
        connector.setSecure(false);
        return connector;
    }
}

这时候启动项目,注意看控制台打印的信息。

image.png

说明已经成功启动http在端口9999https8888,页面也可以成功访问了。

image.png

Spring Boot http自动跳转https

上面我们已经可以同时访问httphttps,但如果我要访问http的时候,自动跳转的https呢?
只需要在上面的基础上稍微改改就可以了。

@Configuration
public class TomcatHttpConfig {
    @Bean
    public TomcatServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
            @Override
            protected void postProcessContext(Context context) {
                SecurityConstraint securityConstraint = new SecurityConstraint();
                securityConstraint.setUserConstraint("CONFIDENTIAL");
                SecurityCollection collection = new SecurityCollection();
                collection.addPattern("/*");
                securityConstraint.addCollection(collection);
                context.addConstraint(securityConstraint);
            }
        };
        tomcat.addAdditionalTomcatConnectors(initiateHttpConnector());
        return tomcat;
    }

    private Connector initiateHttpConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        connector.setPort(9999);
        connector.setSecure(false);
        connector.setRedirectPort(8888);
        return connector;
    }
}

踩坑总结

  1. 把服务端证书p12文件添加到项目resources后,记得rebuild项目,否则targetclasses中没有生成证书文件,会导致项目启动失败。
  2. application.properties中的server.ssl.keyAlias需要和生成p12文件的-name一致,否则也会导致项目无法启动。
  3. 如果要指定域名,需要修改conf.confg中的DNS.1,否则浏览器会提示网站不安全。

代码地址

https://github.com/Shurlormes...

参考资料

https://www.baeldung.com/x-50...

279 声望
7 粉丝
0 条评论
推荐阅读
刨根问底 Redis, 面试过程真好使
充满寒气的互联网如何在面试中脱颖而出,平时积累很重要,八股文更不能少!下面带来的这篇 Redis 问答希望能够在你的 offer 上增添一把🔥。

菜农曰16阅读 841

封面图
Java 编译器 javac 及 Lombok 实现原理解析
javac 是 Java 代码的编译器12,初学 Java 的时候就应该接触过。本文整理一些 javac 相关的高级用法。Lombok 库,大家平常一直在使用,但可能并不知道实现原理解析,其实 Lombok 实现上依赖的是 Java 编译器的注...

nullwy10阅读 5.9k

与RabbitMQ有关的一些知识
工作中用过一段时间的Kafka,不过主要还是RabbitMQ用的多一些。今天主要来讲讲与RabbitMQ相关的一些知识。一些基本概念,以及实际使用场景及一些注意事项。

lpe2348阅读 1.8k

封面图
spring boot 锁
由于当前的项目中由于多线程操作同一个实体,会出现数据覆盖的问题,后保存的实体把先保存的实体的数据给覆盖了。于是查找了锁的实现的几种方式。但写到最后发现,其实自己可以写sql 更新需要更新的字段即可,这...

weiewiyi3阅读 9.1k

Git操作不规范,战友提刀来相见!
年终奖都没了,还要扣我绩效,门都没有,哈哈。这波骚Git操作我也是第一次用,担心闪了腰,所以不仅做了备份,也做了笔记,分享给大家。问题描述小A和我在同时开发一个功能模块,他在优化之前的代码逻辑,我在开...

王中阳Go5阅读 1.8k评论 2

封面图
Redis 发布订阅模式:原理拆解并实现一个消息队列
“65 哥,如果你交了个漂亮小姐姐做女朋友,你会通过什么方式将这个消息广而告之给你的微信好友?““那不得拍点女朋友的美照 + 亲密照弄一个九宫格图文消息在朋友圈发布大肆宣传,暴击单身狗。”像这种 65 哥通过朋...

码哥字节5阅读 1.1k

封面图
NB的Github项目,看到最后一个我惊呆了!
最近看到不少好玩的、实用的 Github 项目,就来给大家推荐一把。中国制霸生成器最近在朋友圈非常火的一个小网站,可以在线标记 居住、短居、游玩、出差、路过 标记后可生成图片进行社区分享,标记过的信息会记录...

艾小仙5阅读 1.5k评论 1

279 声望
7 粉丝
宣传栏