“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。
如果您使用Spring Boot,Spring Cloud和Spring Cloud Config,则只需最少的代码即可构建微服务架构。 将所有内容打包到Docker容器中,即可使用Docker Compose运行所有内容。 如果您在服务之间进行通信,则可以通过不在docker-compose.yml
文件中公开其端口来确保服务的安全性。
但是,如果有人不小心暴露了微服务应用程序的端口会怎样? 他们仍然安全还是任何人都可以访问他们的数据?
在本文中,我将向您展示如何使用HTTPS和OAuth 2.0保护服务到服务的通信。
使用Spring Boot,Spring Cloud和Spring Cloud Config开发微服务堆栈
我将简化使用Spring Boot,Spring Cloud和Spring Cloud Config构建完整的微服务堆栈的过程。 我的好友拉斐尔(Raphael)写了一篇文章,介绍如何构建Spring微服务并将其Docker化用于生产 。 您可以使用他的示例应用程序作为起点。 克隆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 Connect应用程序,一个用于开发,一个用于生产。 如果没有完成上述教程,则需要在Okta上创建每个应用程序。
在Okta上创建OpenID Connect应用程序
您可以注册一个免费的开发人员帐户 ,该帐户最多可以有0个月活跃用户,费用为$ 0。 对于这个例子来说应该足够了。
为什么选择Okta? 因为编写身份验证很无聊。 Okta具有身份验证和用户管理API,可让您更快地开发应用。 我们的API和SDK使您可以在几分钟内轻松地进行身份验证,管理和保护用户安全。
创建帐户后,在Okta的信息中心(“ 应用程序” >“ 添加应用程序” )中创建一个新的Web应用程序 。 给应用程序起一个您将记住的名称,复制现有的登录重定向URI,并使其使用HTTPS。 单击完成 。
结果应类似于下面的屏幕截图。
![安全服务](https://i-blog.csdnimg.cn/blog_migrate/864b2ae2fc10aafd8c18fd16f70e786c.png)
创建另一个用于生产的应用程序。 我给我的Prod Microservices
。
在您克隆的项目中,修改config/school-ui.properties
以使用开发应用程序中的设置。
okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId={devClientId}
okta.oauth2.clientSecret={devClientId}
当使用Maven单独运行应用程序时,将使用这些设置。 在Docker Compose上运行时使用生产设置。 修改config-data/school-ui-production.properties
以使用生产应用程序中的设置。
okta.oauth2.clientId={prodClientId}
okta.oauth2.clientSecret={prodClientId}
您可以在spring.profiles.active
docker-compose.yml
看到spring.profiles.active
打开生产配置文件:
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:8080
Docker Compose从应用程序上方的目录中运行,并从config-data
目录读取其数据。 因此,您需要将这些属性文件复制到此目录中。 从该项目的根目录运行以下命令。
cp config/*.properties config-data/.
使用Docker Compose启动Spring微服务堆栈
该项目在其根目录中有一个聚合器pom.xml
,使您可以使用一个命令来构建所有项目。 运行以下Maven命令为每个项目构建,测试和构建Docker映像。
mvn clean install
该过程完成后,使用Docker Compose启动所有应用程序{config,discovery,school-service和school-ui}。 如果尚未安装,请参阅安装Docker Compose 。
docker-compose up -d
您可以使用Kitematic查看每个应用程序启动时的日志。
在您喜欢的浏览器中导航到http://localhost:8080
。 完成后,您应该可以登录并查看学校课程列表。
![安全服务](https://i-blog.csdnimg.cn/blog_migrate/a786ac950a356099430caf117f84c504.png)
Spring Security和OAuth 2.0
此示例使用Okta的Spring Boot Starter ,它是Spring Security之上的薄层。 Okta入门程序简化了配置,并在访问令牌中进行了观众验证。 它还允许您指定将用于创建Spring Security授权的声明。
docker-compose.yml
文件不会将school-service
公开给外界。 它通过不指定ports
。
school-ui
项目有一个SchoolController
类,该类使用Spring的RestTemplate
与school-service
进行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>>() {});
}
您会注意到此类的端点上存在安全性,但是服务之间不存在安全性。 我将在下面的步骤中向您展示如何解决该问题。
首先,公开school-service
的端口,以模拟有人用粗俗的方式进行配置。 在docker-compose.yml
更改school-service
配置以公开其端口。
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
。 kes! 😱
在继续下一部分之前, 请确保关闭所有Docker容器。
docker-compose down
HTTPS无处不在!
HTTPS代表“安全” HTTP。 HTTPS连接经过加密,其内容比HTTP连接难读得多。 近年来,即使在开发过程中,在所有地方都使用HTTPS的趋势已经发生了很大的变化。 使用HTTPS时可能会遇到一些问题,因此尽早发现它们是很好的。
让我们加密是一个提供免费HTTPS证书的证书颁发机构。 它还具有用于自动续订的API。 简而言之,它使HTTPS变得如此简单,没有理由不使用它! 有关如何将certbot
与“让我们加密”一起使用以生成证书的说明,请参阅将社交登录名添加到您的JHipster应用程序 。
我也鼓励您签出Spring Boot Starter ACME 。 这是一个Spring Boot模块,它使用Let's Encrypt和自动证书管理环境(ACME)协议简化了生成证书的过程。
使用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
使用Spring Boot的HTTPS
Spring Boot不支持带有PEM扩展名的证书,但是您可以将其转换为Spring Boot支持的PKCS12
扩展名。 您可以使用OpenSSL将证书和私钥转换为PKCS12。 这对于“加密我们生成的证书”也是必要的。
运行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
访问它。
![安全服务](https://i-blog.csdnimg.cn/blog_migrate/8c4004960bfa2c61bbbaa7240ade9e9c.png)
打开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
项目中的application.properties
没有指定端口。 您需要添加server.port=8080
。
此时,您应该能够通过在每个项目中(在单独的终端窗口中)运行以下命令来启动所有应用程序。
source ../https.env
./mvnw spring-boot:start
在https://localhost:8080
确认所有工作正常。 然后使用killall java
杀死所有内容。
结合使用HTTPS和Docker Compose
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}
您可以通过运行hostname
来获取{yourHostname}
的值。
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
以启用Eureka注册启用HTTPS来重建所有Docker映像。 然后开始一切。
docker-compose up -d
现在,您的所有应用程序都在带有HTTPS的Docker中运行! 在https://localhost:8080
证明。
如果您的应用程序无法启动或无法彼此通信,请确保您的主机名与
.env
主机名匹配。
您可以进一步提高安全性:使用OAuth 2.0保护您的学校服务API。
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://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId=TEST
spring.cloud.discovery.enabled=false
spring.cloud.config.discovery.enabled=false
spring.cloud.config.enabled=false
然后修改ServiceApplicationTests.java
以加载此文件以用于测试属性:
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
您可以在Okta上创建一个使用客户端凭据的服务应用程序,但是这篇文章已经足够复杂了。 有关该方法的更多信息,请参阅使用Spring Boot和OAuth 2.0进行安全的服务器到服务器通信 。
您需要做的最后一步是修改SchoolController
(在school-ui
项目中),以向其对school-server
的请求中添加OAuth 2.0访问令牌。
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.stereotype.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) { (1)
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) { (2)
OAuth2AuthorizedClient authorizedClient =
this.authorizedClientService.loadAuthorizedClient(
authentication.getAuthorizedClientRegistrationId(),
authentication.getName()); (3)
OAuth2AccessToken accessToken = authorizedClient.getAccessToken(); (4)
restTemplate.getInterceptors().add(getBearerTokenInterceptor(accessToken.getTokenValue())); (5)
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);
};
}
}
- 将
OAuth2AuthorizedClientService
依赖项添加到构造函数 - 将
OAuth2AuthenticationToken
注入listClasses()
方法 - 从
authentication
创建OAuth2AuthorizedClient
- 从授权客户端获取访问令牌
- 将访问令牌添加到
Authorization
标头中
而已! 由于school-ui
和school-service
使用相同的OIDC应用程序设置,因此服务器将识别并验证访问令牌(也是JWT),并允许访问。
此时,您可以选择使用./mvnw spring-boot:run
或Docker Compose单独运行所有应用程序。 后一种方法只需要几个命令。
mvn clean install
docker-compose down
docker-compose up -d
使用HTTP Basic Auth与Eureka和Spring Cloud Config进行安全的微服务通信
为了进一步提高微服务,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进行类似的调整。
增强您对Spring微服务,Docker和OAuth 2.0的了解
本教程向您展示了如何确保微服务体系结构中的服务到服务通信是安全的。 您学习了如何在所有地方使用HTTPS以及如何使用OAuth 2.0和JWT锁定API。
您可以在oktadeveloper / okta-spring-microservices-https-example上的GitHub上找到此示例的源代码。
如果您想进一步探讨这些主题,我想您会喜欢以下博客文章:
- 构建Spring微服务并对其进行Dockerize生产
- 使用Spring Boot为Microbrews构建微服务架构
- 使用Spring Boot 2.0和OAuth 2.0构建并保护微服务
- 使用OAuth 2.0和JHipster开发微服务架构
- 使用Spring Boot和OAuth 2.0进行安全的服务器到服务器通信
这些博客文章有助于使本文中的所有内容都能正常工作:
有问题吗? 在下面的评论中询问他们! 如果您的问题与这篇文章无关,请将其发布到我们的开发者论坛 。
要获取有关更多技术博客文章的通知, 请在Twitter上关注我们@oktadev或订阅我们的YouTube频道 。
“具有HTTPS和OAuth 2.0的安全的服务到服务的Spring微服务”最初于2019年3月7日发布在Okta开发者博客上。
“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。
翻译自: https://www.javacodegeeks.com/2019/03/secure-service-spring-microservices.html