使用HTTPS和OAuth 2.0保护服务到服务的Spring微服务

“我喜欢编写身份验证和授权代码。” 〜从来没有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。 单击完成

结果应类似于下面的屏幕截图。

安全服务

创建另一个用于生产的应用程序。 我给我的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

如果您尚未安装Maven,则可以使用SDKMAN进行安装 sdk install maven

该过程完成后,使用Docker Compose启动所有应用程序{config,discovery,school-service和school-ui}。 如果尚未安装,请参阅安装Docker Compose

docker-compose up -d

您可以使用Kitematic查看每个应用程序启动时的日志。

在您喜欢的浏览器中导航到http://localhost:8080 。 完成后,您应该可以登录并查看学校课程列表。

安全服务

Spring Security和OAuth 2.0

此示例使用Okta的Spring Boot Starter ,它是Spring Security之上的薄层。 Okta入门程序简化了配置,并在访问令牌中进行了观众验证。 它还允许您指定将用于创建Spring Security授权的声明。

docker-compose.yml文件不会将school-service公开给外界。 它通过不指定ports

school-ui项目有一个SchoolController类,该类使用Spring的RestTemplateschool-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命令以为localhost127.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访问它。

安全服务

打开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.p12localhost.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访问令牌。

例子1.向RestTemplate添加一个AccessToken
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);
        };
    }
}
  1. OAuth2AuthorizedClientService依赖项添加到构造函数
  2. OAuth2AuthenticationToken注入listClasses()方法
  3. authentication创建OAuth2AuthorizedClient
  4. 从授权客户端获取访问令牌
  5. 将访问令牌添加到Authorization标头中

而已! 由于school-uischool-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基本身份验证。 为此,您需要在configdiscovery项目中都添加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上找到此示例的源代码。

如果您想进一步探讨这些主题,我想您会喜欢以下博客文章:

这些博客文章有助于使本文中的所有内容都能正常工作:

有问题吗? 在下面的评论中询问他们! 如果您的问题与这篇文章无关,请将其发布到我们的开发者论坛

要获取有关更多技术博客文章的通知, 在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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security 6.0 是一个用于保护应用程序的安全框架,而 OAuth 2.0 是一种用于授权和认证的开放标准。结合使用 Spring Security 6.0 和 OAuth 2.0 可以实现微服务的安全保护。 在微服务架构中,通常会有多个服务相互协作提供功能。为了确保这些服务之间的安全通信和访问控制,可以使用 Spring Security 6.0 提供的 OAuth 2.0 支持。 首先,你需要设置一个认证服务器来处理用户的认证和授权请求。这个服务器可以使用 Spring Security OAuth2 提供的功能来实现。通过配置认证服务器,你可以定义客户端应用程序的注册信息、用户的认证方式(例如用户名密码、社交媒体登录等)以及访问令牌的生成和验证规则。 然后,你需要在你的微服务中配置 Spring Security 6.0 来验证和解析访问令牌。通过将 Spring Security 6.0 集成到你的微服务中,你可以使用 OAuth 2.0 提供的访问令牌来保护你的接口和资源。在每个请求到达微服务之前,Spring Security 6.0 将会验证访问令牌的有效性,并根据配置的访问规则决定是否允许请求继续处理。 需要注意的是,使用 Spring Security 6.0 和 OAuth 2.0 进行微服务的安全保护需要一定的配置和代码编写,具体的实现方式会根据你的项目需求和架构设计而有所不同。你可以查阅相关的文档和教程来了解更多细节,并根据实际情况进行配置和开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值