微服务架构
微服务架构下,服务被拆分为了各个小的单元模块,这些单元模块之间存在各种相互调用的关系。例如用户调用了一个查询订单的功能,订单模块只负责查询订单相关的信息,而如果这次调用里面还需要查用户的信息,再由订单模块负责调用用户模块去查用户信息,如果还需要加上商户信息,则再由订单模块去调用商户模块去查商户信息,最后一起返回给用户。微服务架构下存在很多这种服务之间的相互调用。
那么问题来了,order-service
发起远程调用时如何得知user-service
和merchant-service
的IP地址和端口?user-service
存在多个实例地址时如何选择调用哪一个实例?
Eureka是Spring Cloud
中的服务注册和发现的组件,可以用于解决这些问题。大概和RMI
的思路差不多:
原理大概就是这样,由user-service
在Eureka Server
注册服务,order-service
在Eureka Server
找到user-service
的真实IP和端口进行远程调用。
Eureka Server
Eureka Server
搭建也很简单,首先引入相关的依赖:
1
2
3
4
| <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
|
然后在启动类上面加@EnableEurekaServer
注解。
application.yml
配置:
1
2
3
4
5
6
7
8
9
10
11
| server:
port: 8761
spring:
application:
name: eurekaserver
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://127.0.0.1:8761/eureka/
|
访问http://127.0.0.1:8761/
看到这个界面:
Eureka Client
服务注册
现在要完成user-service
服务提供者:
添加依赖:
1
2
3
4
| <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
|
启动类加上@EnableDiscoveryClient
注解
application.yml
配置:
1
2
3
4
5
6
7
8
9
10
11
| server:
port: 8081
spring:
application:
name: eurekaclient
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/
register-with-eureka: true
fetch-registry: true
|
写一个返回UA的Controller
:
1
2
3
4
5
6
7
8
9
| @RestController
public class TestController {
@RequestMapping("/test")
public String test(@RequestHeader("User-Agent") String ua)
{
return ua;
}
}
|
然后run起来就能注册成功
服务发现
现在要完成order-service
服务消费者
添加Config
类:
1
2
3
4
5
6
7
8
| @Configuration
public class EurekaConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
|
服务发现:
1
2
3
4
5
6
7
8
9
10
11
12
13
| @RestController
public class TestController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/test1")
public String test1()
{
String res = this.restTemplate.getForObject("http://eurekaclient/test", String.class);
return res;
}
}
|
到这里就完成了服务注册与发现,此时访问order-service
的/test1
接口,它就会去Eureka Server
找到user-service
的IP和端口,然后调用user-service
的/test
接口。通过UA判断,确实是Java去请求的user-service
:
微服务架构下假设user-service
更新了新的代码,此时并不需要整个服务重启,而只需要重启user-service
这一个模块。
并且user-service
也可以存在多个实例,由Eureka Server
提供负载均衡。假设user-service
存在三个实例,而其中一个实例挂了,order-service
也可以尝试去调用另外两个实例,使得业务可以不受影响。
假设现在更新了新版本想要灰度发布,也可以注册两个不同版本的user-service
到注册中心。
Eureka Server
添加认证
先不考虑actuator
未授权访问和XStream
反序列化,假设攻击者能直接访问到Eureka Server
(突破边界或者Eureka Server
直接开放在公网),由于Eureka Server
没有身份认证,那么可以直接向Eureka Server
注册服务,找到服务间调用的接口,再尝试去调用对应的服务。所以得考虑给Eureka Server
加上身份认证。
Eureka Server
的身份认证依赖于Spring Security
,参考之前讲过的Spring Security
,加入相关的依赖:
1
2
3
4
5
| <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.2.0</version>
</dependency>
|
修改配置:
1
2
3
4
5
| spring:
security:
user:
name: admin
password: 123456
|
Config
中设置验证逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| @Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http.authorizeHttpRequests(
auth->auth.requestMatchers("/**").authenticated()
);
http.csrf(
csrf ->csrf.ignoringRequestMatchers("/eureka/**")
);
http.httpBasic(Customizer.withDefaults());
return http.build();
}
}
|
主要是关闭csrf
和开启Http Basic Authorization
。
那么Eureka Client
处指定defaultZone
的时候就得加上账号密码了:
1
2
3
4
5
6
| eureka:
client:
service-url:
defaultZone: http://admin:123456@127.0.0.1:8761/eureka/
register-with-eureka: true
fetch-registry: true
|
这样就向Eureka Server
加入了身份认证,可以防止未授权访问。
Reference
Spring Cloud Netflix - Eureka