初見spring cloud(1):Eureka集群中心+服務(wù)注冊/發(fā)現(xiàn)+security安全驗(yàn)證(配置+少部分源碼查找)

前言

??本文記錄了最近幾天在下對(duì)于spring cloud的摸索使用階段所遇到的一些坑,以及坑的解決過程褥琐。過程中參考了包括spring官方的文檔锌俱,和各路大神們的blog等等,參考的部分我會(huì)使用直接上原鏈接的方式而不是大段地引用過來敌呈。
??目前是只搭建好了后端的分布式環(huán)境贸宏,但是網(wǎng)關(guān)暫時(shí)還沒加進(jìn)去,同時(shí)前后端交互的問題在下也還沒有解決(嚶嚶嚶前端除了JS之外瓦塔西瓦完全不懂desu~)磕洪,找前端小伙伴問了問說是最近比較流行vue.js(學(xué)前端是不可能的吭练,這輩子都不想再碰前端;真香N鱿浴)鲫咽,后續(xù)看緣分能不能出(二)吧。
??本文基于 spring boot 2.1.5.RELEASE 版本
??那么下面開始我的踩坑記錄谷异。

How to begin?

??這一節(jié)是專門寫給萌新看的浑侥。
??眾所周知,石原里美是我老婆(大霧)咳咳晰绎,應(yīng)該說寓落,眾所周知,spring 全家桶系列現(xiàn)在已經(jīng)可以很方便地用spring boot集成各種組件了荞下,除了一些參數(shù)的配置之外開發(fā)者可以說是非常省事伶选。
??想要開始搭建一個(gè)spring boot項(xiàng)目史飞,有以下兩種方式:

  1. 通過官方提供的 start.spring.io 頁面,直接通過簡單的選項(xiàng)即可生成一個(gè)完整的項(xiàng)目壓縮包仰税,下載解壓后可以直接用IDE打開构资。
    過程就不展開了,完全傻瓜式操作陨簇,請(qǐng)自行摸索吐绵。
  2. 如果你是用的intelj idea的話,終極版用戶也可以直接new project的時(shí)候使用里面的spring initializer河绽;社區(qū)版用戶則可以下載一個(gè)名為spring assistant插件己单。用法與1基本一致。

??在本文中所提到的各項(xiàng)功能會(huì)應(yīng)用到不同的依賴耙饰,生成項(xiàng)目的時(shí)候需要在dependencies里選的纹笼,現(xiàn)在這里列舉一下,后面講不同模塊的時(shí)候也會(huì)再說明:

  • Eureka server 中心+安全驗(yàn)證:Cloud Discovery -> Eureka Server 苟跪,Security -> Security
  • Eureka client端的服務(wù)發(fā)現(xiàn)與注冊+安全驗(yàn)證:Cloud Discovery -> Eureka Discovery 廷痘,Security -> Security ,Web -> Web
  • Ribbon 負(fù)載均衡與遠(yuǎn)程調(diào)用:Cloud Routing -> Ribbon
  • Feign 負(fù)載均衡與遠(yuǎn)程調(diào)用:Cloud Routing -> Feign

(P.S.使用網(wǎng)頁生成壓縮包的小伙伴請(qǐng)忽略箭頭前的部分,直接在Denpendencies上寫出包的名字就ok了)

Eureka Server高可用集群中心的搭建

中心搭建需要依賴 Cloud Discovery -> Eureka Server 這個(gè)包
mvn build成功后在main類加入注解@EnableEurekaServer
just like this

@SpringBootApplication
@EnableEurekaServer
@ComponentScan("com.borris")
public class SpringCloudTiyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudTiyApplication.class, args);
    }
}

如此即可在啟動(dòng)的時(shí)候自動(dòng)化配置eureka server

集群中心的搭建參考官方文檔的此處:
spring-cloud-eureka-server-peer-awareness
但實(shí)際配置時(shí)不能完全照搬文檔上的內(nèi)容件已,這么幾個(gè)注意點(diǎn):

spring:
  profiles: center3
  application:
    name: eureka-center

eureka:
  instance:
    hostname : localhost:7001
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/
  server:
    #設(shè)置健康節(jié)點(diǎn)檢測間隔(ms)
    eviction-interval-timer-in-ms : 10000

如上笋额,文檔中hostname用的是域名,如果本地也想用域名作為配置的話需要修改hosts文件篷扩,不想改Host的話直接寫ip:port這樣的形式就好了
然后配置defaultZone的時(shí)候兄猩,把多個(gè)server的url寫上去,形式為
http://hostname/eureka
即可瞻惋,中間以逗號(hào)隔開

配置成功之后厦滤,當(dāng)中心啟動(dòng)起來時(shí)會(huì)看到頁面上顯示以下內(nèi)容:


配置成功效果圖

如上圖,配置成功后會(huì)看到DS Replicas中顯示集群的hostname歼狼,
同時(shí)會(huì)顯示已注冊的Application掏导,名稱為上面的spring.application.name
下面那塊則不需要過多關(guān)注,defaultZone配置好了會(huì)顯示一樣的內(nèi)容的羽峰,但真正上兩塊才是真正注冊成功的顯示

根據(jù)文檔中所說趟咆,多個(gè)yml文件可以合并在同一個(gè)文件里,中間用【---】分隔開梅屉,以spring.profiles屬性作為區(qū)分值纱,啟動(dòng)時(shí)在program arguments加入

--spring.profiles.active=profileName

即可啟動(dòng)多個(gè)實(shí)例,而不需要每個(gè)實(shí)例改文件內(nèi)容(方便本地測試用)

server端還可開啟一個(gè)節(jié)點(diǎn)健康監(jiān)測的選項(xiàng)坯汤,如上的
eureka.server.eviction-interval-timer-in-ms
屬性虐唠,例如我這里設(shè)置的是每10s(10000ms)檢查一下節(jié)點(diǎn)健康,當(dāng)檢查到節(jié)點(diǎn)工作狀態(tài)不正常會(huì)自動(dòng)從列表上刪除

Eureka Client搭建方法

Eureka Client端需要依賴 Cloud Discovery -> Eureka Discovery 惰聂,Web -> Web 兩塊
client端需要在main類加入注解@EnableDiscoveryClient疆偿,跟上面一樣就不重復(fù)貼代碼了

client端的配置方式基本上與server端類似其實(shí)咱筛,請(qǐng)查看下面的配置

eureka:
  client:
    #表示eureka client間隔多久去拉取服務(wù)器注冊信息,默認(rèn)為30秒
    registry-fetch-interval-seconds : 5
    service-url:
      defaultZone : http://localhost:7001/eureka/
  instance:
    #心跳間隔
    lease-renewal-interval-in-seconds : 5
    #心跳停止后的節(jié)點(diǎn)過期時(shí)間
    lease-expiration-duration-in-seconds : 10
    instance-id : localhost:${server.port}

參考上面的注釋和屬性,在client端的角度上來說杆故,defaultZone就是它們的注冊節(jié)點(diǎn)迅箩,基本上來說,如果server中心的配置和工作都是正常的話处铛,那么client只注冊單個(gè)server饲趋,server集群會(huì)自動(dòng)把所有的注冊信息復(fù)制到其他的endpoint上,因此也可以通過client端的注冊狀態(tài)撤蟆,來驗(yàn)證server集群的配置是否正確

client注冊效果圖

如上圖所示奕塑,雖然我的client的url只配置了7001這一臺(tái)server,但是因?yàn)閟erver是集群工作的枫疆,所以我可以登陸7002和7003也同樣看到上圖中test-client的注冊信息

但是保險(xiǎn)起見爵川,為了避免單個(gè)endpoint注冊時(shí)發(fā)生節(jié)點(diǎn)宕機(jī)或其他的風(fēng)險(xiǎn)敷鸦,實(shí)際生產(chǎn)上運(yùn)用的時(shí)候還是將所有endpoint的配置都配全比較好息楔,多個(gè)url之間用逗號(hào)隔開,如下:

defaultZone : http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/

另外有一個(gè)很奇怪的點(diǎn)扒披,目前觀察發(fā)現(xiàn)注冊的url默認(rèn)是綁定到了 /eureka 上面去值依,我覺得應(yīng)該是可以提供修改的property的,但實(shí)際上我查找了文檔以及各路大神的blog碟案,發(fā)現(xiàn)貌似是定死了不能改的愿险,我只發(fā)現(xiàn)了一個(gè)文檔上一個(gè)疑似的

eureka.instance.namespace eureka Get the namespace used to find properties. Ignored in Spring Cloud.

看這個(gè)property的描述,【獲取用于查找屬性的命名空間价说,但在spring cloud中被忽略】
另外還在源碼中找到了以下內(nèi)容:

org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration

    /**
     * Register the Jersey filter.
     * @param eurekaJerseyApp an {@link Application} for the filter to be registered
     * @return a jersey {@link FilterRegistrationBean}
     */
    @Bean
    public FilterRegistrationBean jerseyFilterRegistration(
            javax.ws.rs.core.Application eurekaJerseyApp) {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new ServletContainer(eurekaJerseyApp));
        bean.setOrder(Ordered.LOWEST_PRECEDENCE);
        //使用了一個(gè)常量EurekaConstants.DEFAULT_PREFIX 注冊filter的url攔截
        bean.setUrlPatterns(
                Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));

        return bean;
    }

讓我們再來看看這個(gè)EurekaConstants.DEFAULT_PREFIX 是個(gè)什么來頭
org.springframework.cloud.netflix.eureka.EurekaConstants

    /**
     * Default Eureka prefix.
     */
    public static final String DEFAULT_PREFIX = "/eureka";

emmmmm不大明白為什么eureka的注冊filter要這樣設(shè)計(jì)一個(gè)寫死的url辆亏,但看到這一行我就知道可以死了心了,目前官方不提供修改服務(wù)綁定名的途徑鳖目,如果不想自己重構(gòu)的話就先將就著用吧扮叨。

另外client端多節(jié)點(diǎn)部署的話,可以直接參考server的多節(jié)點(diǎn)搭建领迈,這里就不贅述了彻磁。

加入Security安全驗(yàn)證

spring cloud 支持client與server之間使用安全驗(yàn)證進(jìn)行注冊,在建立server(client不需要)項(xiàng)目的時(shí)候加入Security -> Security 依賴即可狸捅,也可以直接在pom.xml上加上

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

加入后可以在yml或者properties里加入用戶名和密碼

spring:
  security:
    basic:
      #打開安全開關(guān)
      enabled: true
    #用戶名密碼
    user:
      name: name
      password: password

加入這些配置之后重新啟動(dòng)項(xiàng)目衷蜓,訪問中心的時(shí)候就會(huì)出現(xiàn)登陸畫面


登陸畫面

登陸后即可看到前面部分所提起過首頁

client端的配置變化其實(shí)也不大,請(qǐng)參考下面的URL配置:

eureka:
  client:
    #表示eureka client間隔多久去拉取服務(wù)器注冊信息,默認(rèn)為30秒
    registry-fetch-interval-seconds : 5
    service-url:
      defaultZone : http://name:password@localhost:7001/eureka/,http://name:password@localhost:7002/eureka/,http://name:password@localhost:7003/eureka/

如上尘喝,只需要將defaultZone的部分加入name:password在中間即可

特別注意:

目前的eureka client比較坑的一點(diǎn)是磁浇,它會(huì)自動(dòng)化配置CSRF防御機(jī)制,然而eureka client并沒有加入對(duì)其的支持朽褪,根據(jù)spring 文檔(spring-security#csrf)稱

to ensure that you include the CSRF token in all PATCH, POST, PUT, and DELETE methods

根據(jù)此處可以得知置吓,spring security會(huì)對(duì)上述的http method都認(rèn)為是有風(fēng)險(xiǎn)的鳍贾,而如果這些method發(fā)送過程中沒有帶上 CSRF token的話,會(huì)被直接攔截并返回 403 forbidden交洗,下面讓我們來瞅瞅client是怎么給server發(fā)送注冊請(qǐng)求的骑科,client啟動(dòng)過程打出的異常日志中,可以跟蹤到接收403信息的類构拳,往下跟蹤咆爽,在下找到了這個(gè)類:
com.netflix.discovery.shared.transport.jersey.JerseyApplicationClient
我們來看看它的register方法:

    @Override
    public EurekaHttpResponse<Void> register(InstanceInfo info) {
        String urlPath = "apps/" + info.getAppName();
        ClientResponse response = null;
        try {
            Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
            addExtraHeaders(resourceBuilder);
            response = resourceBuilder
                    .header("Accept-Encoding", "gzip")
                    .type(MediaType.APPLICATION_JSON_TYPE)
                    .accept(MediaType.APPLICATION_JSON)
                    .post(ClientResponse.class, info);
            return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
                        response == null ? "N/A" : response.getStatus());
            }
            if (response != null) {
                response.close();
            }
        }
    }

細(xì)心的小伙伴應(yīng)該注意到了,它的請(qǐng)求是POST粗去的置森,而且前面并沒有請(qǐng)求獲取 CSRF token 的步驟斗埂,所以毫不意外地被server給forbidden了。
按理說這個(gè)問題已經(jīng)出現(xiàn)了很久了凫海,因?yàn)槲易钤缈吹接腥擞胹pring boot 1.5.x的版本已經(jīng)有這個(gè)問題呛凶。至于官方為什么不維護(hù)eureka client修復(fù)這個(gè)問題呢?咱也不知道行贪,咱也不敢問……

但是官方還是給出了解決的方法漾稀,具體可以參考 spring cloud issue 2754,里面有大量的討論建瘫,我這邊總結(jié)的解決方案:

  1. 配置一個(gè)@EnableWebSecurity配置類崭捍,繼承org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter,重寫configure方法啰脚。
    重寫有兩種方法殷蛇,方法①如下:
    1). 使CSRF忽略 /eureka/*的所有鏈接
@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().ignoringAntMatchers("/eureka/**");
        super.configure(http);
    }

2). 保持密碼驗(yàn)證的同時(shí)禁用CSRF防御機(jī)制

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //注意,如果直接disable的話會(huì)把安全驗(yàn)證也禁用掉
        http.csrf().disable().authorizeRequests()
        .anyRequest()
        .authenticated()
        .and()
        .httpBasic();
    }
  1. 我個(gè)人覺得其實(shí)還有另一個(gè)方法橄浓,自己重構(gòu)register方法粒梦,使其在發(fā)送注冊請(qǐng)求前先GET獲得一個(gè)CSRF token,后續(xù)再POST注冊請(qǐng)求荸实,但是這個(gè)方法改如何實(shí)現(xiàn)emmmmmm目前還沒有這個(gè)想法去搞(你個(gè)死肥宅就是懶得動(dòng))

使用上面的方法1之后匀们,在下這邊是可以正確注冊上了,如果有哪位小伙伴還是不行的話泪勒,可以留言或者私信我昼蛀,我有空的話一起來看看是什么問題~

本文暫時(shí)到這里,后續(xù)我會(huì)考慮把Feign或者Ribbon這兩種遠(yuǎn)程調(diào)用的方式測試對(duì)比圆存,然后寫初見(2)的總結(jié)叼旋,有余力的話考慮加入Zuul網(wǎng)關(guān)?

那么有緣再會(huì)~

20190606
神驅(qū)一夢
于無月之夜
(P.S.夾個(gè)私貨沦辙,寫結(jié)尾時(shí)的BGM是勾指起誓夫植,在下非常喜歡~ 請(qǐng)務(wù)必吃下這個(gè)安利~)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子详民,更是在濱河造成了極大的恐慌延欠,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,946評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沈跨,死亡現(xiàn)場離奇詭異由捎,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)饿凛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門狞玛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人涧窒,你說我怎么就攤上這事心肪。” “怎么了纠吴?”我有些...
    開封第一講書人閱讀 169,716評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵硬鞍,是天一觀的道長。 經(jīng)常有香客問我戴已,道長固该,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,222評(píng)論 1 300
  • 正文 為了忘掉前任恭陡,我火速辦了婚禮蹬音,結(jié)果婚禮上上煤,老公的妹妹穿的比我還像新娘休玩。我一直安慰自己,他們只是感情好劫狠,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評(píng)論 6 398
  • 文/花漫 我一把揭開白布拴疤。 她就那樣靜靜地躺著,像睡著了一般独泞。 火紅的嫁衣襯著肌膚如雪呐矾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,807評(píng)論 1 314
  • 那天懦砂,我揣著相機(jī)與錄音蜒犯,去河邊找鬼。 笑死荞膘,一個(gè)胖子當(dāng)著我的面吹牛罚随,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播羽资,決...
    沈念sama閱讀 41,235評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼淘菩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了屠升?” 一聲冷哼從身側(cè)響起欣舵,我...
    開封第一講書人閱讀 40,189評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤缨称,失蹤者是張志新(化名)和其女友劉穎偏陪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翰萨,經(jīng)...
    沈念sama閱讀 46,712評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評(píng)論 3 343
  • 正文 我和宋清朗相戀三年糕殉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缨历。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,926評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡糙麦,死狀恐怖辛孵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赡磅,我是刑警寧澤魄缚,帶...
    沈念sama閱讀 36,580評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站焚廊,受9級(jí)特大地震影響冶匹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咆瘟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評(píng)論 3 336
  • 文/蒙蒙 一嚼隘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧袒餐,春花似錦飞蛹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至焰宣,卻和暖如春霉囚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背匕积。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評(píng)論 1 274
  • 我被黑心中介騙來泰國打工盈罐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人闪唆。 一個(gè)月前我還...
    沈念sama閱讀 49,368評(píng)論 3 379
  • 正文 我出身青樓盅粪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親苞氮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子湾揽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評(píng)論 2 361