Rest接口加Http单向认证

背景:

  接到一个需求,客户要求某个模块的rest接口都得通过https访问,客户提供证书。

步骤:

Server端证书生成

  刚开始还没拿到客户的证书,所以通过jdk自带的keytools自己先生成了一个证书用来测试,同事称这个为自签名。

  C:Program FilesJavajdk1.8.0_191jreinkeytool.exe 也就是jdk这个自带的程序, 说说怎么用吧。楼主用的windows操作系统

  到JDK的JRE的bin目录下,执行以下命令生成服务端sslServer.p12文件

  keytool -genkey -v -alias sslServer -keyalg RSA -storetype PKCS12 -keystore F:httpsDemosslServer.p12

  注意这个-alias很重要,是证书的别名,后面用得到。sslServer.p12就是生成的自签名证书了,后面需要用客户的证书替换。

  

  看了一些博客的介绍,这个名字和姓氏得是主机的ip,不然会有问题,本着不踩坑的思想,照做就行。当然这里的密码是肯定要记住的。。

Spring配置

将sslServer.p12放在resource目录下,与application.properties同级

# 单向认证开启(客户端校验服务端证书即可)

server.ssl.key-store=classpath:sslServer.p12

server.ssl.key-store-password=server

server.ssl.key-alias=sslServer

server.ssl.keyStoreType=JKS

 

其中,server.ssl.key-store,server.ssl.key-store-password,server.ssl.key-alias需要根据客户提供的证书进行配置,

server.ssl.key-store对应证书位置,目前是用自生成的签名,部署时需要替换客户证书

server.ssl.key-store-password对应证书密码

server.ssl.key-alias对应生成证书时的证书别名

keyStoreType固定为JKS

 

到这里为止,服务端做https的认证就已经结束了,很简单吧。可以使用postman测试一波,记得关掉setting里的ssl认证,设置成off

 

客户端怎么认证

由于楼主的项目是微服架构,本身也会调到这个应用的服务,因此所有调到的地方都得特殊处理下。

https认证分两种,本文只介绍单向认证,即客户端无条件信任服务端的证书,因为我的需求就只要单向

RestTemplate配置

@Configuration

public class RestTemplateConfiguration {

 

    @Value("${readTimeout}")

    private String readTimeout;

 

    @Value("${connectionTimeout}")

    private String connectionTimeout;

 

    @Bean("restTemplate")

    public RestTemplate createRestTemplate() {

 

        ObjectMapper objectMapper = new ObjectMapper();

        SimpleModule simpleModule = new SimpleModule();

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        objectMapper.setDateFormat(dateFormat);

        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);

        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);

        objectMapper.registerModule(simpleModule);

 

        RestTemplate restTemplate = new RestTemplate();

        restTemplate.setRequestFactory(simpleClientHttpRequestFactory());

        List<HttpMessageConverter<?>> converters = new ArrayList<>();

        MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();

        jsonConverter.setObjectMapper(objectMapper);

        converters.add(jsonConverter);

        restTemplate.setMessageConverters(converters);

        return restTemplate;

    }

 

    private ClientHttpRequestFactory simpleClientHttpRequestFactory() {

        SslClientHttpRequestFactory factory = new SslClientHttpRequestFactory();

        factory.setConnectTimeout(Integer.parseInt(connectionTimeout));

        factory.setReadTimeout(Integer.parseInt(readTimeout));

        return factory;

}

 

自定义http请求类

public class SslClientHttpRequestFactory extends SimpleClientHttpRequestFactory {

    /**

     * 区分https请求

     * @param connection

     * @param httpMethod

     * @throws IOException

     */

    @Override

    protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {

        if (connection instanceof HttpsURLConnection) {

            prepareHttpsConnection((HttpsURLConnection) connection);

        }

        super.prepareConnection(connection, httpMethod);

    }

    private void prepareHttpsConnection(HttpsURLConnection connection) {

        connection.setHostnameVerifier(new SkipHostnameVerifier());

        try {

            connection.setSSLSocketFactory(createSslSocketFactory());

        }

        catch (Exception ex) {

            // Ignore

        }

    }

    private SSLSocketFactory createSslSocketFactory() throws Exception {

        SSLContext context = SSLContext.getInstance("TLS");

        context.init(null, new TrustManager[]{new SkipX509TrustManager()}, new SecureRandom());

        return context.getSocketFactory();

    }

    /**

     * 忽略hostName校验,允许ip代替域名

     */

    private static class SkipHostnameVerifier implements HostnameVerifier {

        @Override

        public boolean verify(String s, SSLSession sslSession) {

            return true;

        }

    }

    /**

     * 信任所有证书

     */

    private static class SkipX509TrustManager implements X509TrustManager {

        @Override

        public X509Certificate[] getAcceptedIssuers() {

            return null;

        }

        @Override

        public void checkClientTrusted(X509Certificate[] chain, String authType) {

        }

        @Override

        public void checkServerTrusted(X509Certificate[] chain, String authType) {

        }

    }

}

然后统一用这个restTemplate去调那个模块的https的服务就ok了,restTemplate怎么调不介绍,基本操作。

.crt和.key导成.p12格式的证书

你以为开发完了?没这么简单。

上面说到我们的证书使用的是自签名证书,但客户提供的证书可能并不是你想的.p12格式的,可能是一个.crt格式的证书加上一个.key格式的私钥。

这时候需要将.crt和.key导成.p12格式的证书。这里就要用到openssl工具了,怎么下载安装不介绍。

执行以下openssl指令导出.p12格式的证书

openssl pkcs12 -export -in test.crt -inkey test.key -passin pass:123456 -password pass:123456 -name server -out server.p12

其中test.crt为客户提供的crt证书
test.key 为客户提供的私钥文件
-passin pass:123456代表给私钥设置密码123456
-password pass:123456 代表给p12文件设置密码123456
-name server 代表导出的证书别名为server
server.p12 为导出的证书文件

则对应的服务器端配置如下
server.ssl.enabled=true
server.ssl.key-store=classpath:server.p12
server.ssl.key-store-password=123456
server.ssl.key-alias=server
server.ssl.key-store-type=JKS

https服务端和客户端的单向认证到此为止。

 

原文地址:https://www.cnblogs.com/sulishihupan/p/13170621.html