美文网首页Spring
Feign支持Https协议

Feign支持Https协议

作者: 王勇1024 | 来源:发表于2019-07-19 18:15 被阅读2次

    背景

    最近在实现一个远程构建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
    下面对各个类做一下简单介绍:

    • Feign.Builder:是feign.Client的建造者,用于建造feign.Client实例;
    • feign.Client:feign客户端,用于提交Http/Https请求;
    • Logger.Level:当前FeignClient日志输出级别;文章末尾有详细描述;
    • SSLSocketFactory:SSLSocket工厂;SSLSocket扩展了Socket并提供使用SSL或TLS协议的安全套接字。这种套接字是正常的流套接字,但是它们在基础网络传输协议(如TCP)上添加了安全保护层。
    • HostnameVerifier:实现主机名验证功能;在握手期间,如果URL的主机名和服务器的标识主机名不匹配,则验证机制可以回调此接口实现程序来确定是否应该允许此连接,如果回调内实现不恰当,默认接受所有域名,则有安全风险;
    • NoopHostnameVerifier:是HostnameVerifier的实现类之一,作用是关闭主机名验证功能,并且永远不会抛出SSLException。
    @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:记录请求与响应的头和正文及元数据。

    相关文章

      网友评论

        本文标题:Feign支持Https协议

        本文链接:https://www.haomeiwen.com/subject/hcnslctx.html