Spring

Feign支持Https协议

2019-07-19  本文已影响2人  王勇1024

背景

最近在实现一个远程构建Docker镜像的功能。用户在前端页面触发镜像构建后,后端服务调用远程服务执行构建脚本。之后,后端服务循环访问 DockerHub 的REST接口(/v2/{image}/tags/list),判断目标镜像是否已经生成,从而更新前端构建状态。
后端服务使用Feign访问 DockerHub,DockerHub 接口使用的Https协议。

代码实现

FeignClient定义

@FeignClient注解中指定feign client自定义配置。自定义配置中可以重写 feign.Client、feign.codec.Decoder、feign.codec.Encoder、feign.Contract 的实现方式。这些配置仅对当前的FeignClient有效。

@FeignClient(name = "docker-client", configuration = FeignHttpsConfig.class, url = "https://docker2.yidian.com:5000")
public interface DockerClient {

    @GetMapping("/v2/{image}/tags/list")
    ImageTags getTags(@PathVariable("image") String image);
}

FeignHttpsConfig定义

在FeignHttpsConfig中重写了Feign.Builderfeign.ClientLogger.Level 的配置。其中指定了feign.Client 所使用的SSLSocketFactoryHostnameVerifier
下面对各个类做一下简单介绍:

@Configuration
public class FeignHttpsConfig {

    @Bean
    public Feign.Builder feignBuilder() {
        final Client trustSSLSockets = client();
        return Feign.builder().client(trustSSLSockets);
    }

    @Bean
    public Client client(){
        return new Client.Default(
                TrustingSSLSocketFactory.get(), new NoopHostnameVerifier());
    }

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }

}

NoopHostnameVerifier定义

位于org.apache.http.conn.ssl包中

public class NoopHostnameVerifier implements HostnameVerifier {

    public static final NoopHostnameVerifier INSTANCE = new NoopHostnameVerifier();

    @Override
    public boolean verify(final String s, final SSLSession sslSession) {
        return true;
    }

    @Override
    public final String toString() {
        return "NO_OP";
    }

}

TrustingSSLSocketFactory定义

public class TrustingSSLSocketFactory extends SSLSocketFactory
        implements X509TrustManager, X509KeyManager {

    private static final Map<String, SSLSocketFactory> sslSocketFactories =
            new LinkedHashMap<String, SSLSocketFactory>();
    private static final char[] KEYSTORE_PASSWORD = "password".toCharArray();
    private final static String[] ENABLED_CIPHER_SUITES = {"TLS_RSA_WITH_AES_256_CBC_SHA"};
    private final SSLSocketFactory delegate;
    private final String serverAlias;
    private final PrivateKey privateKey;
    private final X509Certificate[] certificateChain;

    private TrustingSSLSocketFactory(String serverAlias) {
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(new KeyManager[] {this}, new TrustManager[] {this}, new SecureRandom());
            this.delegate = sc.getSocketFactory();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        this.serverAlias = serverAlias;
        if (serverAlias.isEmpty()) {
            this.privateKey = null;
            this.certificateChain = null;
        } else {
            try {
                KeyStore keyStore =
                        loadKeyStore(TrustingSSLSocketFactory.class.getResourceAsStream("/keystore.jks"));
                this.privateKey = (PrivateKey) keyStore.getKey(serverAlias, KEYSTORE_PASSWORD);
                Certificate[] rawChain = keyStore.getCertificateChain(serverAlias);
                this.certificateChain = Arrays.copyOf(rawChain, rawChain.length, X509Certificate[].class);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static SSLSocketFactory get() {
        return get("");
    }

    public synchronized static SSLSocketFactory get(String serverAlias) {
        if (!sslSocketFactories.containsKey(serverAlias)) {
            sslSocketFactories.put(serverAlias, new TrustingSSLSocketFactory(serverAlias));
        }
        return sslSocketFactories.get(serverAlias);
    }

    static Socket setEnabledCipherSuites(Socket socket) {
        SSLSocket.class.cast(socket).setEnabledCipherSuites(ENABLED_CIPHER_SUITES);
        return socket;
    }

    private static KeyStore loadKeyStore(InputStream inputStream) throws IOException {
        try {
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(inputStream, KEYSTORE_PASSWORD);
            return keyStore;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            inputStream.close();
        }
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return ENABLED_CIPHER_SUITES;
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return ENABLED_CIPHER_SUITES;
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose)
            throws IOException {
        return setEnabledCipherSuites(delegate.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException {
        return setEnabledCipherSuites(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return setEnabledCipherSuites(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
            throws IOException {
        return setEnabledCipherSuites(delegate.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
            throws IOException {
        return setEnabledCipherSuites(delegate.createSocket(address, port, localAddress, localPort));
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }

    @Override
    public void checkClientTrusted(X509Certificate[] certs, String authType) {}

    @Override
    public void checkServerTrusted(X509Certificate[] certs, String authType) {}

    @Override
    public String[] getClientAliases(String keyType, Principal[] issuers) {
        return null;
    }

    @Override
    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
        return null;
    }

    @Override
    public String[] getServerAliases(String keyType, Principal[] issuers) {
        return null;
    }

    @Override
    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
        return serverAlias;
    }

    @Override
    public X509Certificate[] getCertificateChain(String alias) {
        return certificateChain;
    }

    @Override
    public PrivateKey getPrivateKey(String alias) {
        return privateKey;
    }
}

日志级别

  1. NONE:不记录 (DEFAULT);
  2. BASIC,:仅记录请求方式和URL及响应的状态代码与执行时间;
  3. HEADERS:日志的基本信息与请求及响应的头;
  4. FULL:记录请求与响应的头和正文及元数据。
上一篇下一篇

猜你喜欢

热点阅读