小心,別被eureka坑了


Eureka是Netflix開發的服務發現框架,本身是一個基於REST的服務,主要用於定位運行在AWS域中的中間層服務,以達到負載均衡和中間層服務故障轉移的目的。SpringCloud將它集成在其子項目spring-cloud-netflix中,以實現SpringCloud的服務發現功能。

Eureka包含兩個組件:Eureka Server和Eureka Client。具體怎么部署這里就不說了,直接說問題

Eureka 客戶端注冊時需要配置服務端地址,類似如下配置

eureka:
  instance:
    hostname: hello-service
    prefer-ip-address: true
    instance-id: ${eureka.instance.hostname}:${server.port}
  client:
    register-with-eureka: true 
    fetch-registry: true
    service-url: 
    	defaultZone: "http://localhost:8761/eureka/" 

這種配置后客戶端就會注冊到Eureka注冊中心,在Eureka界面就能看到:

但是這樣把界面暴露到外面,會把注冊信息泄漏,一般公司也不允許暴露沒有安全認證的后台界面

所以嘗試把Eureka界面加密

引入security

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

Eureka服務端增加basic鑒權:

spring:
  application:
    name: eureka-server 
  security: 
    basic:
      enabled: true
    user:
      name: admin
      password: 123456

配置完成后發現現在訪問eureka界面需要用戶名和密碼登錄了

但是登錄進去后發現剛才的hello服務並沒有注冊進來

嘔吼,應該是客戶端沒配置鑒權信息的原因,在官網找到了客戶端鑒權配置方式

https://cloud.spring.io/spring-cloud-netflix/multi/multi__service_discovery_eureka_clients.html

於是在hello服務修改配置如下:

eureka:
  instance:
    hostname: hello-service
    prefer-ip-address: true
    instance-id: ${eureka.instance.hostname}:${server.port}
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://admin:123456@localhost:8761/eureka/

重啟hello服務后,發現還是沒有注冊成功,原來增加basic驗證后,不支持跨域訪問了,我的天,你這個大坑,服務注冊肯定是跨域的了,

於是,迅速增加配置,去掉跨域攔截

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
     // http.csrf().disable();//一種方式直接關閉csrf,另一種配置url后放行
        http.csrf().ignoringAntMatchers("/eureka/**");
        super.configure(http);
    }
}

終於在界面看到可可愛愛的hello服務了,但是呢,還有一個問題,現在不允許這種明文密碼出現在配置或代碼中,怎么辦呢?

首先想到的就是密碼加密, 所以從spring-securiry中找到PasswordEncoderFactories加密

PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("123456")

然后把加密后的結果放到Eureka服務端配置文件中:

security: 
  basic:
    enabled: true 
  user:
    name: admin
    password: '{bcrypt}$2a$10$mhH7ogkRB91YDUO3F883JugDMHz2o6miT95.8ukqEc6Ed4Z2xyHmm' //必須有引號

那Eureka客戶端怎么辦? 同樣道理的嘛

client:
  register-with-eureka: true
  fetch-registry: true
  service-url:
    defaultZone: http://admin:{bcrypt}$2a$10$mhH7ogkRB91YDUO3F883JugDMHz2o6miT95.8ukqEc6Ed4Z2xyHmm@localhost:8761/eureka/

但是呢,啟動直接報錯,害,eureka注冊時並不會解密

解析Eureka服務端地址失敗,即使是加上引號也是報相同錯誤

Eureka客戶端注冊過來的消息,服務端並不會給解密,那怎么辦呢?

從上圖可以看到如果要實現更復雜的需求,需要通過注入clientFilter方式,so,搞起來

@Configuration
@Priority(Integer.MIN_VALUE)
public class UserCilentFilter extends ClientFilter {
    @Override
    public ClientResponse handle(ClientRequest clientRequest) throws ClientHandlerException {
        try {
            String originUrl = clientRequest.getURI().toString();
            if(originUrl.contains("@")){
                return this.getNext().handle(clientRequest);
            }
            String userNameAndPwd = "http://admin"+ jiemi("'{bcrypt}$2a$10$mhH7ogkRB91YDUO3F883JugDMHz2o6miT95.8ukqEc6Ed4Z2xyHmm'");
            String addUserInfoUrl = originUrl.replaceFirst("http://", userNameAndPwd);
            clientRequest.setURI(new URI(addUserInfoUrl));
        } catch (URISyntaxException e) {
            // FIXME: 2021/4/2
        }
        return this.getNext().handle(clientRequest);
    }

    private String jiemi(String pwd) {
        // FIXME: 解密
        return pwd;
    }


    @Bean
    public DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs() {
        DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs = new DiscoveryClient.DiscoveryClientOptionalArgs();
        discoveryClientOptionalArgs.setAdditionalFilters(Collections.singletonList(new UserCilentFilter()));
        return discoveryClientOptionalArgs;
    }
}

這樣寫的思路是讓其他客戶端注冊時去掉用戶名和密碼,然后在自定義過濾器中對沒有用戶名和密碼時補充上basic驗證的用戶名和密碼

然后開始測試,這樣還是不行,其他服務注冊過來時,會被其他安全過濾器攔截都走不到自定義的攔截器就返回鑒權失敗了,即使@Priority(Integer.MIN_VALUE)最高優先級

那是不是可以在更前面的地方進行攔截呢?增加ServletRequest攔截器可行否?

@Configuration
public class ServerRequestAuthFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain filterChain) throws IOException, ServletException {
        //業務實現,根據請求的IP或者參數判斷是否可以執行注冊或者訪問
//        String addUserInfoUrl = originUrl.replaceFirst("http://", "http://admin:123456@");
        filterChain.doFilter(request, response);
    }
}

但是假設這樣修改后,登錄的web界面也會走到這個攔截器,同樣會增加鑒權

也就是說這樣直接增加鑒權,無法區分是其他客戶端注冊還是從界面訪問

也沒有什么太好的辦法了,就直接在security的攔截器中把eureka注冊相關的放掉,不進行鑒權操作

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().antMatchers("/eureka/**").permitAll();
    super.configure(http);
}

這樣設置后除了直接訪問的界面需要鑒權外,其他eureka相關注冊、查詢等不需要鑒權

都這樣分層鑒權操作了,再找下是不是有其他方式達到相同的目的,於是找到

eureka:
  dashboard:
    enabled: false

通過在啟動腳本設置后,效果是類似的,在eureka主界面無法訪問

上面所有的操作都是為了信息安全考慮,還有一個經常忘記需要考慮的組件是Spring Boot Actuator,針對 Spring Boot Actuator 提供的 endpoint,采取以下幾種措施,可以盡可能降低被安全攻擊的風險

  1. 最小粒度暴露 endpoint。只開啟並暴露真正用到的 endpoint,而不是配置:management.endpoints.web.exposure.include=*。
  2. 為 endpoint 配置獨立的訪問端口,從而和 web 服務的端口分離開,避免暴露 web 服務時,誤將 actuator 的 endpoint 也暴露出去。例:management.port=8099。
  3. 引入 spring-boot-starter-security 依賴,為 actuator 的 endpoint 配置訪問控制。
  4. 慎重評估是否需要引入 spring-boot-stater-actuator。以我個人的經驗,我至今還沒有遇到什么需求是一定需要引入spring-boot-stater-actuator 才能解決,如果你並不了解上文所述的安全風險,我建議你先去除掉該依賴。

信息安全已經成為各大公司不得不考慮的問題,所以精准的權限控制也是必不可少的,希望本文對大家在使用SpringCloud相關組件安全控制上有啟發作用。

如果覺得俺寫的還可以,記得點贊,一鍵三連也不介意。

☞☞每周一篇,賽過神仙,看完點贊,養成習慣☜☜


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM