如何保护Service-to-Service的微服务
点击左上角蓝字,关注“锅外的大佬”
专注分享国外最新技术内容
已构建的微服务架构,如何保证了服务与服务之间(service-to-service)通信的安全性?
你可以通过不在
docker-compose.yml文件中暴露端口,使得服务之间的通信更加安全。但是,如果微服务应用程序的端口意外暴露又会发生什么呢?任何人都可以访问这些数据吗?
未雨绸缪,我将告诉你,如何使用HTTPS和OAuth 2.0保护服务与服务通信。
1.搭建微服务
我将使用Spring Boot,Spring Cloud和Spring Cloud Config快速构建完整的微服务架构。我朋友 Raphael 撰写了一篇关于 如何构建Spring微服务并将它们Docker化(Dockerize)用于生产 的文章。您可以使用他的示例应用程序作为起点。克隆 okta-spring-microservices-docker-example 项目:
git clone https://github.com/oktadeveloper/okta-spring-microservices-docker-example.git spring-microservices-security
cd spring-microservices-security该项目需要Okta上的两个OpenID连接应用程序,一个用于开发(开发App),一个用于生产(生产App)。如果您没有完成上述教程,则需要在Okta上创建每个应用程序。
2. 在Okta上创建OpenID Connect应用程序
您可以注册一个 免费的开发者帐户 ,该帐户可以免费拥有1000个月活跃用户。对此例来说,应该已经足够了。
为什么使用Okta?因为编写身份验证并不有趣。Okta具有身份验证和用户管理API,可让您更快地开发应用程序。Okta的API和SDK使您可以在几分钟内轻松地对用户进行身份验证,管理和保护。
创建帐户后,在Okta的仪表板(应用程序 > 添加应用程序)中创建一个新的Web应用程序。为应用程序指定名称,复制现有的登录重定向URI,并使其使用HTTPS。单击 “完成”。
结果应类似于下面的屏幕截图。
为生产App创建另一个应用。我将其命名为:
Prod Microservices。
在克隆的项目中,修改config / school-ui.properties,获取开发App的设置。
okta.oauth2.issuer=https://okta.okta.com/oauth2/default okta.oauth2.clientId={devClientId} okta.oauth2.clientSecret={devClientId}用 Maven 单独运行应用程序时将使用这些设置。使用
Docker容器运行时将使用生产环境配置。修改
config-data/school-ui-production.properties以从生产App中获取设置。
okta.oauth2.clientId={prodClientId} okta.oauth2.clientSecret={prodClientId}在
docker-compose.yml使用
spring.profiles.active启用生产(prodcution)配置文件 :
school-ui: image: developer.okta.com/microservice-docker-school-ui:0.0.1-SNAPSHOT environment: - JAVA_OPTS= -DEUREKA_SERVER=http://discovery:8761/eureka -Dspring.profiles.active=production restart: on-failure depends_on: - discovery - config ports: - 8080:8080Docker Compose从应用程序上方的目录运行,并从
config-data目录中读取其数据 。因此,您需要将这些属性文件复制到此目录中。在该项目的根目录运行以下命令。
cp config/*.properties config-data/.
3.使用Docker Compose启动微服务
该项目在其根目录下有一个聚合pom.xml,允许您使用一个命令构建所有项目。运行以下 Maven 命令为每个项目构建,测试以及构建Docker镜像。
mvn clean install如果您没有安装 Maven ,可以使用SDKMAN安装它:
sdk install maven
完成此过程后,使用Docker Compose启动所有应用程序{config,discovery,school-service和school-ui}。如果没有安装Docker Compose,请参阅 安装Docker Compose。
docker-compose up -d您可以使用 Kitematic 在启动时查看每个应用的日志。
浏览器访问:
http://localhost:8080。在这样做之后,您应该能够登录并查看学校课程列表.
4.Spring Security和OAuth 2.0
这个例子使用了 Okta的Spring Boot Starter,它是Spring Security上的一个轻量级层。Okta启动器简化了配置并在访问令牌中进行了访问验证。它还允许您指定将用于创建Spring Security权限的声明。
docker-compose.yml文件不会暴露
school-service给外界,通过不指定
ports来做到这一点。
school-ui项目中的
SchoolController类告知与
school-service使用Spring的
RestTemplate。
@GetMapping("/classes") @PreAuthorize("hasAuthority('SCOPE_profile')") public ResponseEntity<List<TeachingClassDto>> listClasses() { return restTemplate .exchange("http://school-service/class", HttpMethod.GET, null, new ParameterizedTypeReference<List<TeachingClassDto>>() {}); }您会注意到此类的端点上存 3ff7 在安全性,但服务之间不存在安全性。我将在下面的步骤中向您展示如何解决这个问题。
首先,暴露
school-service的端口以模拟某人错误植入配置。更改
school-service的
docker-compose.yml配置使其暴露端口。
school-service: image: developer.okta.com/microservice-docker-school-service:0.0.1-SNAPSHOT environment: - JAVA_OPTS= -DEUREKA_SERVER=http://discovery:8761/eureka depends_on: - discovery - config ports: - 8081:8081使用Docker Compose重启所有内容:
docker-compose down docker-compose up -d访问:
http://localhost:8081, 你将无需进行身份验证即可查看数据 !可怕!
在继续下一部分之前,请确保关闭所有Docker容器。
docker-compose down
5.无处不在的HTTPS!
HTTPS代表 “安全” HTTP。HTTPS连接是加密的,其内容比HTTP连接更难以阅读。近年来,即使在开发过程中,也无处不在地使用HTTPS。使用HTTPS运行时可能会遇到一些问题,最好及早发现它们。Let's Encrypt 是一个提供免费HTTPS证书的证书颁发机构。它还具有自动续订的API。简而言之,它使HTTPS变得如此简单,没有理由不使用它!有关 如何使用 Let's Encrypt生成证书的说明, 请参阅 添加社交登录到您的JHipster应用程序,以获取有关如何使用
certbot和Let's Encrypt 生成证书的说明。
我也鼓励您查看 Spring Boot Starter ACME。这是一个Spring Boot模块,可以使用Let’s Encrypt 和自动证书管理环境(ACME)协议简化生成证书。
6.使用mkcert轻松实现本地TLS
我最近发现了一个名为mkcert的工具 ,它允许创建
localhost证书。您可以在macOS上使用Homebrew安装它:
brew install mkcert brew install nss # Needed for Firefox如果您使用的是Linux,则需要先安装
certutil:
sudo apt install libnss3-tools然后使用Linuxbrew 执行
brew install mkcert。Windows用户可以 使用Chocolately或Scoop。
执行以下
mkcert命令为
localhost生成一个证,
127.0.0.1您的计算机的名称,以及
discovery主机(如在
docker-compose.yml中引用)。
mkcert -install mkcert localhost 127.0.0.1 ::1 `hostname` discovery如果生成带有数字的文件,则重命名文件,使其没有数字。
mv localhost+2.pem localhost.pem mv localhost+2-key.pem localhost-key.pem
7.Spring Boot HTTPS
Spring Boot不支持带有 PEM 扩展的证书 ,但您可以将其转换为Spring Boot支持的PKCS12。您可以使用OpenSSL将证书和私钥转换为 PKCS12 。这也是Let's Encrypt生成的证书所必需的。
运行
openssl以转换证书:
openssl pkcs12 -export -in localhost.pem -inkey \ localhost-key.pem -out keystore.p12 -name bootifulsecurity出现提示时指定密码。
在项目的根目录下创建 https.env 文件,并指定以下属性以启用HTTPS。
export SERVER_SSL_ENABLED = true export SERVER_SSL_KEY_STORE = ../keystore.p12 export SERVER_SSL_KEY_STORE_PASSWORD = {yourPassword} export SERVER_SSL_KEY_ALIAS = bootifulsecurity export SERVER_SSL_KEY_STORE_TYPE = PKCS12更新
.gitignore文件,删除
.env文件,使密钥库密码不会在源代码管理中终止。
*.env运行
source https.env设置环境变量。或者,更好的是,将它添加到您的
.bashrc或
.zshrc文件中,以便为每个新shell设置这些变量。当然,您也可以将它们包含在每个应用程序的
application.properties中。但之后您将密钥存储在源代码管理中。如果您没有将此示例检查到源代码管理中,则可以使用以下设置进行复制/粘贴。
server.ssl.enabled=true server.ssl.key-store=../keystore.p12 server.ssl.key-store-password: {yourPassword} server.ssl.key-store-type: PKCS12 server.ssl.key-alias: bootifulsecurity启动
discovery应用:
cd discovery source ../https.env mvn spring-boot:run然后确认您可以访问:
https://localhost:8761。
打开
docker-compose.yml并将所有实例的
http更改为
https。编辑
school-ui/src/main/java/ui/controller/SchoolController.java将
school-service的调用改为使用HTTPS。
return restTemplate .exchange("https://school-service/class", HttpMethod.GET, null, new ParameterizedTypeReference<List<TeachingClassDto>>() {});更新
{config,school-service,school-ui}/src/main/resources/application.properties中使每个实例注册为安全的应用程序。
eureka.instance.secure-port-enabled=true eureka.instance.secure-port=${server.port} eureka.instance.status-page-url=https://${eureka.hostname}:${server.port}/actuator/info eureka.instance.health-check-url=https://${eureka.hostname}:${server.port}/actuator/health eureka.instance.home-page-url=https://${eureka.hostname}${server.port}/此外,更改每个
application.properties(和
bootstrap.yml)中的Eureka地址为:
https://localhost:8761/eureka。
school-ui
工程中的端口号尚未指定,因此需要添加:server.port=8080
.
此时,您应该能够通过在每个项目中运行以下内容(在单独的终端窗口中)来启动所有应用程序。
source ../https.env ./mvnw spring-boot:start确认一切正常
https://localhost:8080。然后杀掉所有进程:
killall java。
8.在Docker Compose中使用HTTPS
Docker不会读取环境变量,它不清楚本地CA(证书颁发机构),也无法将父目录中的文件添加到映像。
要解决此问题,需要复制
keystore.p12和
localhost.pem到每个项目的目录。第一个将用于Spring Boot,第二个将添加到每个映像的Java Keystore。
cp localhost.pem keystore.p12 config/. cp localhost.pem keystore.p12 discovery/. cp localhost.pem keystore.p12 school-service/. cp localhost.pem keystore.p12 school-ui/.然后修改每个项目
Dockerfile,来复制证书并将其添加到其信任库。
FROM openjdk:8-jdk-alpine VOLUME /tmp ADD target/*.jar app.jar ADD keystore.p12 keystore.p12 USER root COPY localhost.pem $JAVA_HOME/jre/lib/security RUN \ cd $JAVA_HOME/jre/lib/security \ && keytool -keystore cacerts -storepass changeit -noprompt \ -trustcacerts -importcert -alias bootifulsecurity -file localhost.pem ENV JAVA_OPTS="" ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]然后Spring Boot和HTTPS的环境变量创建
.env文件。
SERVER_SSL_ENABLED=true SERVER_SSL_KEY_STORE=keystore.p12 SERVER_SSL_KEY_STORE_PASSWORD={yourPassword} SERVER_SSL_KEY_ALIAS=bootifulsecurity SERVER_SSL_KEY_STORE_TYPE=PKCS12 EUREKA_INSTANCE_HOSTNAME={yourHostname}您可以
{yourHostname}通过运行中的
hostname。
Docker Compose有一个“env_file”配置选项,允许您读取此文件以获取环境变量。更新
docker-compose.yml为每个应用程序指定
env_file。
version: '3' services: discovery: env_file: - .env ... config: env_file: - .env ... school-service: env_file: - .env ... school-ui: env_file: - .env ...可以通过在根目录运行:
docker-compose config来确保它正常工作 。
运行
mvn clean install重新构建所有Docker镜像,并启用HTTPS以进行Eureka注册:
docker-compose up -d现在,所有应用都使用HTTPS在Docker中运行!可访问
https://localhost:8080来验证。
If your apps do not start up or can’t talk to each other, make sure your hostname matches what you have in
.env.
还可以进一步提高安全性:使用OAuth 2.0来保护
school-serviceAPI。
9.OAuth 2.0的API安全性
将Okta Spring Boot Starter和Spring Cloud Config添加到
school-service/pom.xml:
<dependency> <groupId>com.okta.spring</groupId> <artifactId>okta-spring-boot-starter</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>然后在
school-service/src/main/java/…/service/configuration创建一个
SecurityConfiguration.java类
package com.okta.developer.docker_microservices.service.configuration; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests().anyRequest().authenticated() .and() .oauth2ResourceServer().jwt(); } }创建
school-service/src/test/resources/test.properties文件并添加属性,使Okta的配置通过,并且在测试时不使用服务发现或配置中心服务。
okta.oauth2.issuer=https://okta.okta.com/oauth2/default okta.oauth2.clientId=TEST spring.cloud.discovery.enabled=false spring.cloud.config.discovery.enabled=false spring.cloud.config.enabled=false然后修改
ServiceApplicationTests.java加载
test properties:
import org.springframework.test.context.TestPropertySource; ... @TestPropertySource(locations="classpath:test.properties") public class ServiceApplicationTests { ... }添加
school-service/src/main/resources/bootstrap.yml允许此实例从Spring Cloud Config读取其配置。
eureka: client: serviceUrl: defaultZone: ${EUREKA_SERVER:https://localhost:8761/eureka} spring: application: name: school-service cloud: config: discovery: enabled: true serviceId: CONFIGSERVER failFast: true然后复制 将
config/school-ui.properties等价复制到
school-service。
cp config/school-ui.properties config/school-service.properties对于Docker Compose,您还需要
config-data/school-service.properties,并添加以下配置:
okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default okta.oauth2.clientId={prodClientId} okta.oauth2.clientSecret={prodClientId}您还需要修改
docker-compose.yml以便
school-service在失败时重新启动。
school-service: ... restart: on-failure需要做的最后一步是修改
SchoolController(在
school-ui项目中),以向其发出的请求添加OAuth 2.0访问令牌。
示例1.将AccessToken添加到RestTemplate
package com.okta.developer.docker_microservices.ui.controller; import com.okta.developer.docker_microservices.ui.dto.TeachingClassDto; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; import org.springframework.http.ResponseEntity; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.stere 8000 otype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.ModelAndView; import java.io.IOException; import java.util.List; @Controller @RequestMapping("/") public class SchoolController { private final OAuth2AuthorizedClientService authorizedClientService; private final RestTemplate restTemplate; public SchoolController(OAuth2AuthorizedClientService clientService, RestTemplate restTemplate) { this.authorizedClientService = clientService; this.restTemplate = restTemplate; } @RequestMapping("") public ModelAndView index() { return new ModelAndView("index"); } @GetMapping("/classes") @PreAuthorize("hasAuthority('SCOPE_profile')") public ResponseEntity<List<TeachingClassDto>> listClasses( @AuthenticationPrincipal OAuth2AuthenticationToken authentication) { OAuth2AuthorizedClient authorizedClient = this.authorizedClientService.loadAuthorizedClient( authentication.getAuthorizedClientRegistrationId(), authentication.getName()); OAuth2AccessToken accessToken = authorizedClient.getAccessToken(); restTemplate.getInterceptors().add(getBearerTokenInterceptor(accessToken.getTokenValue())); return restTemplate .exchange("https://school-service/class", HttpMethod.GET, null, new ParameterizedTypeReference<List<TeachingClassDto>>() {}); } private ClientHttpRequestInterceptor getBearerTokenInterceptor(String accessToken) { return (request, bytes, execution) -> { request.getHeaders().add("Authorization", "Bearer " + accessToken); return execution.execute(request, bytes); }; } }由于
school-ui和
school-service使用相同的OIDC应用程序设置,服务器将识别并验证访问令牌(也是JWT),并允许访问。
此时,您可以选择使用
./mvnw spring-boot:run或Docker Compose 单独运行所有应用程序 。后一种方法只需要几个命令.
mvn clean install docker-compose down docker-compose up -d
10.HTTP Basic Auth安全通信
为了进一步提高微服务,Eureka Server和Spring Cloud Config之间的安全性,您可以添加HTTP基本身份验证。为此,您需要在config和
discovery项目中添加
spring-boot-starter-security依赖 。然后指定
spring.security.user.password并加密它。您可以在Spring Cloud Config的安全文档中了解有关如何执行此操作的更多信息 。
在两个项目中配置Spring Security后,您可以调整URL以在其中包含用户名和密码。例如,
school-ui项目中的
bootstrap.yml:
eureka: client: serviceUrl: defaultZone: ${EUREKA_SERVER:https://username:password@localhost:8761/eureka}还需要对
docker-compose.yml中的URL进行类似的调整。
本教程展示了如何确保您的服务到服务通信在微服务架构中是安全的。了解如何在任何地方使用HTTPS并使用OAuth 2.0和JWT保护API。
您可以在GitHub上的oktadeveloper/okta-spring-microservices-https-example找到此示例的源代码。
出处spring for all 翻译组
8月福利,准时来袭!关注公众号
后台回复:003 ,领取7月翻译集锦~
往期福利:回复 001, 002即可领取
● Spring WebClient vs. RestTemplate
● 为什么选择 Spring 作为 Java 框架?Java有损转换Java有损转换
右上角按钮分享给更多人哦~
来都来了,点个在看再走吧~~~
- 如何在windows NT 下安装jboss为服务?(How to install jboss as Windows NT OS Service?)
- Android开发之如何监听让服务不被杀死(service+broadcast)
- 我的WCF之旅(7):面向服务架构(SOA)和面向对象编程(OOP)的结合——如何实现Service Contract的继承
- 配置安装Apache主服务发生错误:"(OS 5)拒绝访问。 : AH00369: Failed to open the Windows service manager, perh······ "
- windows安装Apache,注册服务时出现“(OS 5)拒绝访问。 : AH00369: Failed to open the WinNT service manager..."
- 如何使用Azure Container Service Engine在Azure中国区部署容器服务(一):DC/OS篇
- HOWTO:如何通过ServiceAddService修改已经存在的服务启动参数
- tomcat6w.exe 运行提示:指定的服务并未以已安装的服务存在 Unable to open the service 'tomcat6'
- windows安装Apache,注册服务出现“(OS 5)拒绝访问。 : AH00369: Failed to open the WinNT service manager..."
- 如何编写systemctl自启动服务 .service文件
- Tomcat提示指定的服务未安装Unable to open the service 'tomcat'
- 如何检查Android后台服务线程(Service类)是否正在运行
- 如何检查后台服务(Android的Service类)是否正在运行?
- 如何删除window service (服务)
- 如何在Mac OS X上创建一个Service服务进程
- WCF分布式开发常见错误(3):客户端调用服务出错:You have tried to create a channel to a service that does not support .Net Framing
- informatica [REP_55102] Failed to connect to repository service如何解决?
- 如何用JavaScript调用Web服务——callService/useService
- The target assembly contains no service types. You may need to adjust the Code Access Security policy of this assembly." 目标程序集不包含服务类型。可能需要调整此程序集的代码访问安全策略。
- 如何在非Activity中启动、绑定Service(服务)