Feign支持Https協(xié)議

背景

最近在實現(xiàn)一個遠程構建Docker鏡像的功能。用戶在前端頁面觸發(fā)鏡像構建后,后端服務調(diào)用遠程服務執(zhí)行構建腳本雕拼。之后垮卓,后端服務循環(huán)訪問 DockerHub 的REST接口(/v2/{image}/tags/list),判斷目標鏡像是否已經(jīng)生成裙犹,從而更新前端構建狀態(tài)。
后端服務使用Feign訪問 DockerHub,DockerHub 接口使用的Https協(xié)議鸟蟹。

代碼實現(xiàn)

FeignClient定義

@FeignClient注解中指定feign client自定義配置。自定義配置中可以重寫 feign.Client使兔、feign.codec.Decoder建钥、feign.codec.Encoder、feign.Contract 的實現(xiàn)方式虐沥。這些配置僅對當前的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.Client欲险、Logger.Level 的配置镐依。其中指定了feign.Client 所使用的SSLSocketFactoryHostnameVerifier
下面對各個類做一下簡單介紹:

  • Feign.Builder:是feign.Client的建造者天试,用于建造feign.Client實例槐壳;
  • feign.Client:feign客戶端,用于提交Http/Https請求喜每;
  • Logger.Level:當前FeignClient日志輸出級別务唐;文章末尾有詳細描述雳攘;
  • SSLSocketFactory:SSLSocket工廠;SSLSocket擴展了Socket并提供使用SSL或TLS協(xié)議的安全套接字枫笛。這種套接字是正常的流套接字吨灭,但是它們在基礎網(wǎng)絡傳輸協(xié)議(如TCP)上添加了安全保護層。
  • HostnameVerifier:實現(xiàn)主機名驗證功能刑巧;在握手期間喧兄,如果URL的主機名和服務器的標識主機名不匹配,則驗證機制可以回調(diào)此接口實現(xiàn)程序來確定是否應該允許此連接海诲,如果回調(diào)內(nèi)實現(xiàn)不恰當繁莹,默認接受所有域名,則有安全風險特幔;
  • NoopHostnameVerifier:是HostnameVerifier的實現(xiàn)類之一咨演,作用是關閉主機名驗證功能,并且永遠不會拋出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及響應的狀態(tài)代碼與執(zhí)行時間;
  3. HEADERS:日志的基本信息與請求及響應的頭拍嵌;
  4. FULL:記錄請求與響應的頭和正文及元數(shù)據(jù)遭赂。
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市横辆,隨后出現(xiàn)的幾起案子撇他,更是在濱河造成了極大的恐慌,老刑警劉巖狈蚤,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件困肩,死亡現(xiàn)場離奇詭異,居然都是意外死亡脆侮,警方通過查閱死者的電腦和手機锌畸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來靖避,“玉大人潭枣,你說我怎么就攤上這事』媚螅” “怎么了盆犁?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長篡九。 經(jīng)常有香客問我蚣抗,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任翰铡,我火速辦了婚禮,結果婚禮上讽坏,老公的妹妹穿的比我還像新娘锭魔。我一直安慰自己,他們只是感情好路呜,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布迷捧。 她就那樣靜靜地躺著,像睡著了一般胀葱。 火紅的嫁衣襯著肌膚如雪漠秋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天抵屿,我揣著相機與錄音庆锦,去河邊找鬼。 笑死轧葛,一個胖子當著我的面吹牛搂抒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播尿扯,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼求晶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了衷笋?” 一聲冷哼從身側響起芳杏,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辟宗,沒想到半個月后爵赵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡慢蜓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年亚再,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晨抡。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡氛悬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出耘柱,到底是詐尸還是另有隱情如捅,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布调煎,位于F島的核電站镜遣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏士袄。R本人自食惡果不足惜悲关,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一谎僻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寓辱,春花似錦艘绍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至这敬,卻和暖如春航夺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背崔涂。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工阳掐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人堪伍。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓锚烦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親帝雇。 傳聞我的和親對象是個殘疾皇子涮俄,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351