| 知乎专栏 |
![]() |
微服务安全吗?其实存在很多隐患,常规的做法是将微服务置于私有局域网中,通过网关报漏服务。如果破坏者一旦进入了你的私有局域网中,微服务是及其危险的。
配置中心的安全隐患
配置中心有以下几种安全隐患
配置有泄漏敏感信息的隐患,你的配置中心是不是也这样?
iMac:workspace neo$ curl http://localhost:8888/netkiller-dev-master.json
{"sms":{"gateway":{"url":"https://sms.netkiller.cn/v1","username":"netkiller","password":"123456"}}}
给配置中心增加SSL和HTTP认证,可以让配置中心更安全。
iMac:resources neo$ curl -i -k https://config:s3cr3t@localhost:8888/netkiller-dev.json
HTTP/2 200
set-cookie: JSESSIONID=9E77660C8DC7669121C8D122A48D8737; Path=/; Secure; HttpOnly
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
cache-control: no-cache, no-store, max-age=0, must-revalidate
pragma: no-cache
expires: 0
strict-transport-security: max-age=31536000 ; includeSubDomains
x-frame-options: DENY
content-type: application/json
content-length: 100
date: Mon, 07 Sep 2020 08:24:39 GMT
{"sms":{"gateway":{"url":"https://sms.netkiller.cn/v1","username":"netkiller","password":"123456"}}}
我们将 HTTP2 SSL 应用在配置中心后,就不担心配置文件被嗅探器抓到。
注册中心一不小心就被公网IP报漏出去,甚至有被恶意注册的风险。
注册中心有以下几种安全隐患
你的注册中心是不是这样的?
iMac:workspace neo$ curl http://localhost:8761/eureka/apps
<applications>
<versions__delta>1</versions__delta>
<apps__hashcode>UP_1_</apps__hashcode>
<application>
<name>WEBFLUX</name>
<instance>
<instanceId>192.168.3.85:webflux</instanceId>
<hostName>192.168.3.85</hostName>
<app>WEBFLUX</app>
<ipAddr>192.168.3.85</ipAddr>
<status>UP</status>
<overriddenstatus>UNKNOWN</overriddenstatus>
<port enabled="true">8080</port>
<securePort enabled="false">443</securePort>
<countryId>1</countryId>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwn</name>
</dataCenterInfo>
<leaseInfo>
<renewalIntervalInSecs>30</renewalIntervalInSecs>
<durationInSecs>90</durationInSecs>
<registrationTimestamp>1599106511367</registrationTimestamp>
<lastRenewalTimestamp>1599106931380</lastRenewalTimestamp>
<evictionTimestamp>0</evictionTimestamp>
<serviceUpTimestamp>1599106511367</serviceUpTimestamp>
</leaseInfo>
<metadata>
<management.port>8080</management.port>
</metadata>
<homePageUrl>http://192.168.3.85:8080/</homePageUrl>
<statusPageUrl>http://192.168.3.85:8080/actuator/info</statusPageUrl>
<healthCheckUrl>http://192.168.3.85:8080/actuator/health</healthCheckUrl>
<vipAddress>webflux</vipAddress>
<secureVipAddress>webflux</secureVipAddress>
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
<lastUpdatedTimestamp>1599106511368</lastUpdatedTimestamp>
<lastDirtyTimestamp>1599106511299</lastDirtyTimestamp>
<actionType>ADDED</actionType>
</instance>
</application>
</applications>
经过安全加固后
Eureka Web 界面进入需要输入用户名和密码,HTTP2 SSL 加密传输页面内容。
iMac:resources neo$ curl -k https://eureka:s3cr3t@localhost:8761/eureka/apps
<applications>
<versions__delta>1</versions__delta>
<apps__hashcode>UP_2_</apps__hashcode>
<application>
<name>MICROSERVICE-RESTFUL</name>
<instance>
<instanceId>192.168.3.85:microservice-restful:8081</instanceId>
<hostName>192.168.3.85</hostName>
<app>MICROSERVICE-RESTFUL</app>
<ipAddr>192.168.3.85</ipAddr>
<status>UP</status>
<overriddenstatus>UNKNOWN</overriddenstatus>
<port enabled="true">8081</port>
<securePort enabled="false">443</securePort>
<countryId>1</countryId>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwn</name>
</dataCenterInfo>
<leaseInfo>
<renewalIntervalInSecs>30</renewalIntervalInSecs>
<durationInSecs>90</durationInSecs>
<registrationTimestamp>1599532959290</registrationTimestamp>
<lastRenewalTimestamp>1599533499404</lastRenewalTimestamp>
<evictionTimestamp>0</evictionTimestamp>
<serviceUpTimestamp>1599532959290</serviceUpTimestamp>
</leaseInfo>
<metadata>
<management.port>8081</management.port>
</metadata>
<homePageUrl>http://192.168.3.85:8081/</homePageUrl>
<statusPageUrl>http://192.168.3.85:8081/actuator/info</statusPageUrl>
<healthCheckUrl>http://192.168.3.85:8081/actuator/health</healthCheckUrl>
<vipAddress>microservice-restful</vipAddress>
<secureVipAddress>microservice-restful</secureVipAddress>
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
<lastUpdatedTimestamp>1599532959291</lastUpdatedTimestamp>
<lastDirtyTimestamp>1599532959204</lastDirtyTimestamp>
<actionType>ADDED</actionType>
</instance>
</application>
<application>
<name>OPENFEIGN</name>
<instance>
<instanceId>192.168.3.85:openfeign:8088</instanceId>
<hostName>192.168.3.85</hostName>
<app>OPENFEIGN</app>
<ipAddr>192.168.3.85</ipAddr>
<status>UP</status>
<overriddenstatus>UNKNOWN</overriddenstatus>
<port enabled="true">8088</port>
<securePort enabled="false">443</securePort>
<countryId>1</countryId>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwn</name>
</dataCenterInfo>
<leaseInfo>
<renewalIntervalInSecs>30</renewalIntervalInSecs>
<durationInSecs>90</durationInSecs>
<registrationTimestamp>1599533216972</registrationTimestamp>
<lastRenewalTimestamp>1599533517001</lastRenewalTimestamp>
<evictionTimestamp>0</evictionTimestamp>
<serviceUpTimestamp>1599533216972</serviceUpTimestamp>
</leaseInfo>
<metadata>
<management.port>8088</management.port>
</metadata>
<homePageUrl>http://192.168.3.85:8088/</homePageUrl>
<statusPageUrl>http://192.168.3.85:8088/actuator/info</statusPageUrl>
<healthCheckUrl>http://192.168.3.85:8088/actuator/health</healthCheckUrl>
<vipAddress>openfeign</vipAddress>
<secureVipAddress>openfeign</secureVipAddress>
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
<lastUpdatedTimestamp>1599533216972</lastUpdatedTimestamp>
<lastDirtyTimestamp>1599533216920</lastDirtyTimestamp>
<actionType>ADDED</actionType>
</instance>
</application>
</applications>
Eureka Client 的安全配置与Eureka Server/Config Server 类似
Eureka 客户端有以下几种安全隐患
我们给 Eureka Client 增加 HTTP/2 SSL 然后再注册到 Eureka Server,我通常会关闭 Eureka Client 端口,只保留 SSL 端口。
iMac:Architect neo$ curl -k https://eureka:s3cr3t@localhost:8761/eureka/apps
<applications>
<versions__delta>1</versions__delta>
<apps__hashcode>UP_2_</apps__hashcode>
<application>
<name>MICROSERVICE-RESTFUL</name>
<instance>
<instanceId>192.168.3.85:microservice-restful:8081</instanceId>
<hostName>192.168.3.85</hostName>
<app>MICROSERVICE-RESTFUL</app>
<ipAddr>192.168.3.85</ipAddr>
<status>UP</status>
<overriddenstatus>UNKNOWN</overriddenstatus>
<port enabled="false">8081</port>
<securePort enabled="true">8081</securePort>
<countryId>1</countryId>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwn</name>
</dataCenterInfo>
<leaseInfo>
<renewalIntervalInSecs>30</renewalIntervalInSecs>
<durationInSecs>90</durationInSecs>
<registrationTimestamp>1599547853553</registrationTimestamp>
<lastRenewalTimestamp>1599548033559</lastRenewalTimestamp>
<evictionTimestamp>0</evictionTimestamp>
<serviceUpTimestamp>1599547853554</serviceUpTimestamp>
</leaseInfo>
<metadata>
<management.port>8081</management.port>
</metadata>
<homePageUrl>http://192.168.3.85:8081/</homePageUrl>
<statusPageUrl>http://192.168.3.85:8081/actuator/info</statusPageUrl>
<healthCheckUrl>http://192.168.3.85:8081/actuator/health</healthCheckUrl>
<secureHealthCheckUrl>https://192.168.3.85:8081/actuator/health</secureHealthCheckUrl>
<vipAddress>microservice-restful</vipAddress>
<secureVipAddress>microservice-restful</secureVipAddress>
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
<lastUpdatedTimestamp>1599547853554</lastUpdatedTimestamp>
<lastDirtyTimestamp>1599547853483</lastDirtyTimestamp>
<actionType>ADDED</actionType>
</instance>
</application>
<application>
<name>OPENFEIGN</name>
<instance>
<instanceId>192.168.3.85:openfeign:8088</instanceId>
<hostName>192.168.3.85</hostName>
<app>OPENFEIGN</app>
<ipAddr>192.168.3.85</ipAddr>
<status>UP</status>
<overriddenstatus>UNKNOWN</overriddenstatus>
<port enabled="true">8088</port>
<securePort enabled="true">8088</securePort>
<countryId>1</countryId>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwn</name>
</dataCenterInfo>
<leaseInfo>
<renewalIntervalInSecs>30</renewalIntervalInSecs>
<durationInSecs>90</durationInSecs>
<registrationTimestamp>1599547953476</registrationTimestamp>
<lastRenewalTimestamp>1599547953476</lastRenewalTimestamp>
<evictionTimestamp>0</evictionTimestamp>
<serviceUpTimestamp>1599547953476</serviceUpTimestamp>
</leaseInfo>
<metadata>
<management.port>8088</management.port>
</metadata>
<homePageUrl>http://192.168.3.85:8088/</homePageUrl>
<statusPageUrl>http://192.168.3.85:8088/actuator/info</statusPageUrl>
<healthCheckUrl>http://192.168.3.85:8088/actuator/health</healthCheckUrl>
<secureHealthCheckUrl>https://192.168.3.85:8088/actuator/health</secureHealthCheckUrl>
<vipAddress>openfeign</vipAddress>
<secureVipAddress>openfeign</secureVipAddress>
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
<lastUpdatedTimestamp>1599547953476</lastUpdatedTimestamp>
<lastDirtyTimestamp>1599547953435</lastDirtyTimestamp>
<actionType>ADDED</actionType>
</instance>
</application>
</applications>
从上面配置中可以看到 port 已经禁用,也就意味着无法再通过 http:// 访问,securePort 是启用状态,只接受 https:// 访问。
<port enabled="false">8081</port>
<securePort enabled="true">8081</securePort>
最好还要设置防火墙,只允许 Eureka Server 才能访问 Eureka Client。防止通过其他服务做为跳板,进入局域网,直接访问 Eureka Client。
最近在群里有人问关闭分布式事务的话题,详细听了他们需求后。我呵呵一笑,大约在15年前我就遇到过这种问题。
起因是这样的,这是一个电商系统,架构师给出的架构是这样的:
用户中心:负责用户注册,登录,用户信息,钱包管理…… 商品中心:负责商品的管理,包括展示,价格和库存管理…… 广告中心:负责商品推广,促销…… 物流中心:负责订单的物流…… 等等中心:负责等等……
每个中心都有一个独立域名例如:
user.domain.com product.domain.com ad.domain.com search.domain.com m.domain.com ……
这种架构设计会存在一个问题,用户每下一个订单,都需要连接多个中心,做一连串调用,最终完成下订单这个功能。因为用户可能操作过程中终止购物流程,或者不可抗因素导致流程无法继续。为此需要设计了一种分布式事务系统,用来解决事务回滚的问题。
所谓的分布式事务,是指跨服务器实现数据库生成与回滚操作,例如:用户购物,浏览商品,添加购物车,选择物流方式…… 这些数据产生在不同服务器上,如果用户取消订单,数据将依次反向回滚。
无独有偶,另一个跨境电商公司的同事也遇到了这种问题,苦苦找不到解决方案,想起了我,询问我的意见。
有时候你会发现,人们会陷入思维边界的陷阱,全力以赴在错误的方向上,无法自拔。
首先,划分中心的架构思维,之所以会出现这种划分方法,我认为跟我们的教育方式有关,导致了多数人都会沿用这种思维定式。
其次,分布式服务的确能解决他们遇到的问题,能想到分布式事务,证明他们智商没有问题。但分布式事务不是最优解,是最差解决方案。
最后,出现了南辕北辙,在错误方向的道路上越走越远。
大约在15年前我们也遇到了这个问题,幸好我及时出手纠正了架构设计的误区,从而没有走上分布式事务之路。
那时还没有微服务的概念,也没有容器技术,我们主要使用物理服务器,在服务器上运行多个实例。从BAT高薪挖来的架构师的思路跟上面一样,将应用划分成各种中心,并且要求每个中心都部署在独立物理机上。划分中心这种方式也与当时的开发模式有关,采用敏捷开发,分成多个小组,每个小组负责一个中心,小组间定义好信通接口,然后所有小组马力全开,活就一起开干了。现在想想简单又粗暴,就如人体器官一样,五脏六腑的联系不是通过一条神经实现的,他们的联系十分复杂。所以我们不能单独思考每个中心,然后就认为把它们合起来就是一个整体。
如果再继续下去,我们一定会去研发分布式事务。
此时有一个更好的机会等着我,于是我提出了离职申请,反正是准备离开了,也不怕得罪人,我想我应该在离职之前把这些问题跟公司说一下。
我向公司反映了目前面临的所有问题,并且提出了两个概念:
上面提出的两点,直到今天也仍然适用,例如在微服务的拆分中。在我的职业生涯中,这两个概念始终在指导我的团队。下面我详细说明两个概念怎样应用到实际的工作中。
我们还以电商系统举例,用户下单购物的工作流,如果是按照中心划分,流程可能是这样的:
用户 —> 商品中心(浏览) —> 搜索中心(过滤)—> 用户中心(添加购物车)—> 物流中心 (物流方式) —> 结算中心(支付结算/扣积分)—> 商品中心(扣库存)—> 用户中心 (完成)
数据流在,商品中心,搜索中心,用户中心…… 服务器中不断传递,网络延迟,网络超时,网络故障等等任何错误都可能影响用户体验。
如果是运行在一个实例中呢?确切的说,我们需要让工作流运行在一个服务器上,一个CPU、内存和硬盘上。这样就没有分布式事务的需求了,数据库的事务处理解决了所有的问题,就这么简单!!!
基于这种法则,我们将几套工作流归类,放在一个实例中,放在今天就是微服务。同样微服务的拆分也尽量满足一套工作流在一个微服务客户端上,避免请求过程出现,一个微服务调用另一个微服务的情况。
![]() |
Restful 的通信安全有很多中解决方案,例如
HTTP Basic Auth 认证
Cooke / Session 认证
Token 认证
Oauth / OpenID
等等,每一种方案都很成熟,这里不依依解释,如果不了解,请去搜索引擎查找相关资料。这里我谈谈在实施微服务项目中的心得,首先项目采用 Spring cloud 方案,Spring cloud 有自己的RestController 控制器,我们需要遵循他的规范开发,这就限制了很多传统的认证加密方法不能应用到 Spring cloud中。
例如传统restful 使用 POST 方式提交,POST 数据格式如下:
name=Neo&age=23&md5=xxxxxxx
然后做 token 校验。
而 Spring cloud 使用 raw 格式的数据做POST提交,例如
@RequestMapping(value = "/member/create", method = RequestMethod.POST) public void create(@RequestBody Member member)
我们不想在Spring框架上做额外的改动,又想解决信息的安全问题。
这个方案简单,实施起来最为方便,因为项目比较紧急,所以就采用了这个方案,这个方案既可以在运维方处理,也可以在开发方处理,对于 Spring boot 只需引入Spring Security 简单配置,立即生效。
实现方式请参考: Spring boot with Spring security
上面的方案适合在防火墙内部的服务器间通信,如果跨机房或者在广域网上就不在安全了,通过嗅探器抓包,包括 http basic auth 的用户和密码,以及接口数据没有安全可言。 为Web 服务器增加 SSL 证书,可以解决信息安全问提。
证书可以使用CA机构颁发的证书,也可以自己生成证书。
证书可以配置在Web服务器上如Nginx, 实现方式请参考: http://www.netkiller.cn/www/nginx/conf.html#http2 《Netkiller Web 手札》
也可以配置在 Spring boot 中, 实现方式请参考: Spring boot with HTTPS SSL
这个方案可以满足绝大部分用户的需求。
由于需要为手机端提供 restful 服务,之前的方式已经不能满足我们的需求,之前的方式更适合提供私有服务,不适合提供公共服务。所谓私有服务是指它的使用范围限制在企业内部,或者事业部间共享服务,总的来说可以通过防火墙控制服务区域。
对于公共服务 OpenID/Oauth 更适合,我们不关心用户地理位,终端设备的情况。实现方式请参考: Spring boot with Oauth2
SSL 双向认证
HTTP Basic Auth 认证
Oauth2 认证
这是我们最终的方案,双向认证是服务器与客户端两端都需要证书才能通信。
App(IOS/Android) --> SSL 双向认证 --> SLB/Proxy --> Feign Client
file:///Users/neo/tmp/spring/security/oauth2.html#oauth2.jwt