知乎专栏 |
![]() |
微服务安全吗?其实存在很多隐患,常规的做法是将微服务置于私有局域网中,通过网关报漏服务。如果破坏者一旦进入了你的私有局域网中,微服务是及其危险的。
配置中心的安全隐患
配置中心有以下几种安全隐患
配置有泄漏敏感信息的隐患,你的配置中心是不是也这样?
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