004. Eureka服务注册与欺骗你的EurekaServer

声明

不要使用此方法进行违法操作,本文只提供学习Eureka相关知识,涉及实验纯属个人娱乐。

前言

众所周知,在SpringCloud中使用的是json+http进行信息的交互,那么我们猜想,client端在启动时会将自己的信息以json方式发送给Server端,Server端接受后将信息存放到Cache中,那么等于注册了一个服务到Server端;再近一步,我们还可以一直发送/health的心跳检测,那么这个服务会一直存在于Eureka维护的核心容器中。这还不是最骚的,我们还可以直接强制下线某个微服务,然后自己"冒名顶替",这个黑户服务通过ip转发实现劫持。。。
所以你的微服务架构可千万不要"裸奔",安全第一位。

核心知识点

EurekaClient启动

EurekaClient客户端启动时会注入一些外部Bean:spring.factories文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.loadbalancer.LoadBalancerEurekaAutoConfiguration

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration

我们暂时关注EurekaClientAutoConfiguration,EurekaClientAutoConfiguration会注入一些Bean与Configuration,无论是热加载还是非热加载(@ConditionalOnRefreshScope与@ConditionalOnMissingRefreshScope先不多说)都会创建一个CloudEurekaClient对象。

// 例如:@ConditionalOnMissingRefreshScope
		@Bean(destroyMethod = "shutdown")
		@ConditionalOnMissingBean(value = EurekaClient.class,
				search = SearchStrategy.CURRENT)
		public EurekaClient eurekaClient(ApplicationInfoManager manager,
				EurekaClientConfig config) {
			return new CloudEurekaClient(manager, config, this.optionalArgs,
					this.context);
		}

这里面ApplicationInfoManager 包含了实例启动的config与当前实例的基本信息

		@Bean
		@ConditionalOnMissingBean(value = ApplicationInfoManager.class,
				search = SearchStrategy.CURRENT)
		@org.springframework.cloud.context.config.annotation.RefreshScope
		@Lazy
		public ApplicationInfoManager eurekaApplicationInfoManager(
				EurekaInstanceConfig config) {
			InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
			return new ApplicationInfoManager(config, instanceInfo);
		}

InstanceInfo包含了当前启动实例所有核心信息。
回到上面new的CloudEurekaClient,此对象在生成时提供方法反射获取HTTPclient:

    EurekaHttpClient getEurekaHttpClient() {
        if (this.eurekaHttpClient.get() == null) {
            try {
                Object eurekaTransport = this.eurekaTransportField.get(this);
                Field registrationClientField = ReflectionUtils.findField(eurekaTransport.getClass(), "registrationClient");
                ReflectionUtils.makeAccessible(registrationClientField);
                this.eurekaHttpClient.compareAndSet((Object)null, (EurekaHttpClient)registrationClientField.get(eurekaTransport));
            } catch (IllegalAccessException var3) {
                log.error("error getting EurekaHttpClient", var3);
            }
        }

        return (EurekaHttpClient)this.eurekaHttpClient.get();
    }

EurekaHttpClient功能强大(相当于注册发现的implement),他可以帮助Client进行注册,发现,取消,移除,状态修改,服务发现(全量,增量)等基础功能实现,在他的实现类中我们可以看到进行的http处理。

public interface EurekaHttpClient {
    EurekaHttpResponse<Void> register(InstanceInfo var1);

    EurekaHttpResponse<Void> cancel(String var1, String var2);

    EurekaHttpResponse<InstanceInfo> sendHeartBeat(String var1, String var2, InstanceInfo var3, InstanceStatus var4);

    EurekaHttpResponse<Void> statusUpdate(String var1, String var2, InstanceStatus var3, InstanceInfo var4);

    EurekaHttpResponse<Void> deleteStatusOverride(String var1, String var2, InstanceInfo var3);

    EurekaHttpResponse<Applications> getApplications(String... var1);

    EurekaHttpResponse<Applications> getDelta(String... var1);

    EurekaHttpResponse<Applications> getVip(String var1, String... var2);

    EurekaHttpResponse<Applications> getSecureVip(String var1, String... var2);

    EurekaHttpResponse<Application> getApplication(String var1);

    EurekaHttpResponse<InstanceInfo> getInstance(String var1, String var2);

    EurekaHttpResponse<InstanceInfo> getInstance(String var1);

    void shutdown();
}

EurekaDiscoveryClientConfiguration注入的Bean当然还包括服务注册的Bean(调用EurekaHttpClient.register的操作)

	@Bean
	public EurekaServiceRegistry eurekaServiceRegistry() {
		return new EurekaServiceRegistry();
	}
	
		@Bean
	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
	@ConditionalOnProperty(
			value = "spring.cloud.service-registry.auto-registration.enabled",
			matchIfMissing = true)
	public EurekaAutoServiceRegistration eurekaAutoServiceRegistration(
			ApplicationContext context, EurekaServiceRegistry registry,
			EurekaRegistration registration) {
		return new EurekaAutoServiceRegistration(context, registry, registration);
	}

EurekaServiceRegistry包含的方法就是register,deregister,setStatus,getStatus。
DiscoveryClient会调用上面我们的httpclient

    boolean register() throws Throwable {
        logger.info("DiscoveryClient_{}: registering service...", this.appPathIdentifier);

        EurekaHttpResponse httpResponse;
        try {
            httpResponse = this.eurekaTransport.registrationClient.register(this.instanceInfo);
        } catch (Exception var3) {
            logger.warn("DiscoveryClient_{} - registration failed {}", new Object[]{this.appPathIdentifier, var3.getMessage(), var3});
            throw var3;
        }

        if (logger.isInfoEnabled()) {
            logger.info("DiscoveryClient_{} - registration status: {}", this.appPathIdentifier, httpResponse.getStatusCode());
        }

        return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
    }

开始实验

实验一,模拟注册一个微服务

为了方便EurekaHttpClient我们统一参照org.springframework.cloud.netflix.eureka.http.RestTemplateEurekaHttpClient的代码

    public EurekaHttpResponse<Void> register(InstanceInfo info) {
        String urlPath = this.serviceUrl + "apps/" + info.getAppName();
        HttpHeaders headers = new HttpHeaders();
        headers.add("Accept-Encoding", "gzip");
        headers.add("Content-Type", "application/json");
        ResponseEntity<Void> response = this.restTemplate.exchange(urlPath, HttpMethod.POST, new HttpEntity(info, headers), Void.class, new Object[0]);
        return EurekaHttpResponse.anEurekaHttpResponse(response.getStatusCodeValue()).headers(headersOf(response)).build();
    }

我们使用的url为http://localhost:7001/eureka/apps/PROVIDER-PAYMENT8002;
通过抓包获取到报文结构:
EurekaClient注册报文
我们使用此报文

{
    "instance": {
        "instanceId": "provider-payment8002",
        "hostName": "192.168.0.106",
        "app": "PROVIDER-PAYMENT8002",
        "ipAddr": "192.168.0.106",
        "status": "UP",
        "overriddenStatus": "UNKNOWN",
        "port": {
            "$": 8002,
            "@enabled": "true"
        },
        "securePort": {
            "$": 443,
            "@enabled": "false"
        },
。。。
        "homePageUrl": "http://192.168.0.106:8002/",
        "statusPageUrl": "http://192.168.0.106:8002/actuator/info",
        "healthCheckUrl": "http://192.168.0.106:8002/actuator/health",
        "vipAddress": "provider-payment8002",
        "secureVipAddress": "provider-payment8002",
        "isCoordinatingDiscoveryServer": "false",
        "lastUpdatedTimestamp": "1584033235520",
        "lastDirtyTimestamp": "1584033236309"
    }
}

post请求
已经注册成功

实验二,下线一个微服务
    public EurekaHttpResponse<Void> cancel(String appName, String id) {
        String urlPath = this.serviceUrl + "apps/" + appName + '/' + id;
        ResponseEntity<Void> response = this.restTemplate.exchange(urlPath, HttpMethod.DELETE, (HttpEntity)null, Void.class, new Object[0]);
        return EurekaHttpResponse.anEurekaHttpResponse(response.getStatusCodeValue()).headers(headersOf(response)).build();
    }

这里的id是什么呢?DiscoveryClient中有,即instanceInfo.getId(),默认是微服务的小写:

    void unregister() {
        if (this.eurekaTransport != null && this.eurekaTransport.registrationClient != null) {
            try {
                logger.info("Unregistering ...");
                EurekaHttpResponse<Void> httpResponse = this.eurekaTransport.registrationClient.cancel(this.instanceInfo.getAppName(), this.instanceInfo.getId());
                logger.info("DiscoveryClient_{} - deregister  status: {}", this.appPathIdentifier, httpResponse.getStatusCode());
            } catch (Exception var2) {
                logger.error("DiscoveryClient_{} - de-registration failed{}", new Object[]{this.appPathIdentifier, var2.getMessage(), var2});
            }
        }

    }

所以我们的url是:http://localhost:7001/eureka/apps/PROVIDER-PAYMENT8002/provider-payment8002
delete请求。
服务下线

实验三,服务劫持

将生产的8001下线操作http://localhost:7001/eureka/apps/PROVIDER-PAYMENT8001/provider-payment8001,然后启动一个脚本定时任务,先发送请求将自己注册上Eureka,并且每30s发送健康检查,发送请求,请求可以被此第三方服务正常消费,即实验成功。
脚本服务

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值