在Java生态系统中,您将发现一些用于构建微服务体系结构的根深蒂固的模式。如果您以前使用过Spring,那么springboot和springcloud应该是一个不错的返校节。
在本教程中,我将向您展示如何使用springboot和springcloud构建javamicroservices体系结构。
使用Spring Cloud 和 Spring Boot创建Java微服务
仓库地址:
git clone https://github.com/oktadeveloper/java-microservices-examples.git
cd java-microservices-examples/spring-boot+cloud
在spring boot+cloud目录中,有三个项目:
- 发现服务:Netflix Eureka服务器,用于服务发现。
- 汽车服务:一个简单的汽车服务,它使用Spring数据REST来提供汽车的restapi。
- api网关:一个api网关,它有一个/cool cars端点,与汽车服务对话,过滤掉不酷的汽车(当然,在我看来)。
我使用start.spring.io的restapi和HTTPie创建了所有这些应用程序。
http https://start.spring.io/starter.zip javaVersion==11 \
artifactId==discovery-service name==eureka-service \
dependencies==cloud-eureka-server baseDir==discovery-service | tar -xzvf -
http https://start.spring.io/starter.zip \
artifactId==car-service name==car-service baseDir==car-service \
dependencies==actuator,cloud-eureka,data-jpa,h2,data-rest,web,devtools,lombok | tar -xzvf -
http https://start.spring.io/starter.zip \
artifactId==api-gateway name==api-gateway baseDir==api-gateway \
dependencies==cloud-eureka,cloud-feign,data-rest,web,cloud-hystrix,lombok | tar -xzvf -
使用Java11的Spring微服务
为了使发现服务在Java11上运行,我必须在JAXB上添加一个依赖项。
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
另外两个应用程序在java11上开箱即用,没有任何依赖关系的变化。
Netflix Eureka的Java服务发现
发现服务的配置与大多数Eureka服务器相同。它在其主类和属性上作为@EnableEurekaServerannotation
来设置其端口并关闭发现。
server.port=8761
eureka.client.register-with-eureka=false
car服务和api网关项目以类似的方式配置。两者都定义了一个唯一的名称,并且car服务被配置为在端口8090上运行,这样它就不会与8080冲突。
car-service/src/main/resources/application.properties
:
server.port=8090
spring.application.name=car-service
api-gateway/src/main/resources/application.properties
:
spring.application.name=api-gateway
两个项目中的主类都用@enablescoveryclient
注释。
用Spring Data REST构建Java微服务
car服务提供了一个restapi,允许您创建(创建、读取、更新和删除)汽车。当应用程序使用ApplicationRunner bean加载时,它会创建一组默认的cars。
service/src/main/java/com/example/carservice/CarServiceApplication.java
:
package com.example.carservice;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.stream.Stream;
@EnableDiscoveryClient
@SpringBootApplication
public class CarServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CarServiceApplication.class, args);
}
@Bean
ApplicationRunner init(CarRepository repository) {
return args -> {
Stream.of("Ferrari", "Jaguar", "Porsche", "Lamborghini", "Bugatti",
"AMC Gremlin", "Triumph Stag", "Ford Pinto", "Yugo GV").forEach(name -> {
repository.save(new Car(name));
});
repository.findAll().forEach(System.out::println);
};
}
}
@Data
@NoArgsConstructor
@Entity
class Car {
public Car(String name) {
this.name = name;
}
@Id
@GeneratedValue
private Long id;
@NonNull
private String name;
}
@RepositoryRestResource
interface CarRepository extends JpaRepository<Car, Long> {
}
Spring Cloud + Feign 和 Hystrix在API网关中
Feign使编写javahttp客户机更容易。springcloud使得只需几行代码就可以创建一个假客户机。Hystrix可以为您的外部客户机添加故障转移功能,使它们更具弹性。
api网关使用Feign和Hystrix与下游car服务通信,如果fallback()
方法不可用,则将其故障转移到fallback()
方法。它还公开了一个/cool-cars
端点,它过滤掉了您可能不想拥有的汽车。
api-gateway/src/main/java/com/example/apigateway/ApiGatewayApplication.java
:
package com.example.apigateway;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.Data;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.hateoas.Resources;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Collection;
import java.util.stream.Collectors;
@EnableFeignClients
@EnableCircuitBreaker
@EnableDiscoveryClient
@EnableZuulProxy
@SpringBootApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
@Data
class Car {
private String name;
}
@FeignClient("car-service")
interface CarClient {
@GetMapping("/cars")
@CrossOrigin
Resources<Car> readCars();
}
@RestController
class CoolCarController {
private final CarClient carClient;
public CoolCarController(CarClient carClient) {
this.carClient = carClient;
}
private Collection<Car> fallback() {
return new ArrayList<>();
}
@GetMapping("/cool-cars")
@CrossOrigin
@HystrixCommand(fallbackMethod = "fallback")
public Collection<Car> goodCars() {
return carClient.readCars()
.getContent()
.stream()
.filter(this::isCool)
.collect(Collectors.toList());
}
private boolean isCool(Car car) {
return !car.getName().equals("AMC Gremlin") &&
!car.getName().equals("Triumph Stag") &&
!car.getName().equals("Ford Pinto") &&
!car.getName().equals("Yugo GV");
}
}
运行Java微服务体系结构
如果在不同的终端窗口中使用./mvnw
运行所有这些服务,则可以导航到http://localhost:8761
并查看他们已在Eureka注册。
如果你导航到http://localhost:8080/cool-bars
,您将被重定向到Okta。怎么回事?
使用OAuth 2.0和OIDC保护Java微服务
我已经使用OAuth2.0和OIDC在这个微服务体系结构中配置了安全性。这两者有什么区别?OIDC是OAuth2.0的一个扩展,它提供了标识。它还提供了发现功能,因此可以从单个URL(称为颁发者)发现所有不同的OAuth2.0端点。
我如何为所有这些微服务配置安全性?很高兴你这么问!
我将Okta的Spring Boot starter添加到api网关和汽车服务中的pom.xml中:
<dependency>
<groupId>com.okta.spring</groupId>
<artifactId>okta-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
然后,我在Okta中创建了一个新的OIDC应用程序,配置了授权代码流。如果您想看到一切都在运行,则需要完成以下步骤。
在Okta中创建Web应用程序
登录您的Okta开发者帐户(如果您没有帐户,请注册)。
从“应用程序”页面中,选择“添加应用程序”。
在“创建新应用程序”页面上,选择“Web”。
给你的应用一个难忘的名字,添加http://localhost:8080/login/oauth2/code/okta
作为登录重定向URI,选择Refresh Token(除了授权码之外)并单击Done。
将颁发者(位于API>Authorization Servers下)、客户端ID和客户端机密复制到这两个项目的application.properties
中。
okta.oauth2.issuer=$issuer
okta.oauth2.client-id=$clientId
okta.oauth2.client-secret=$clientSecret
下面的部分中的Java代码已经存在,但是我想我会解释一下,这样您就知道发生了什么。
为OAuth 2.0登录和资源服务器配置Spring安全性
在ApiGatewayApplication.java
中,我添加了Spring安全配置,以启用oauth2.0登录并将网关作为资源服务器启用。
@Configuration
static class OktaOAuth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests().anyRequest().authenticated()
.and()
.oauth2Login()
.and()
.oauth2ResourceServer().jwt();
// @formatter:on
}
}
本例中没有使用资源服务器配置,但我添加了它,以防您希望将移动应用程序或SPA连接到此网关。如果您使用的是SPA,那么还需要添加bean来配置CORS。
@Bean
public FilterRegistrationBean<CorsFilter> simpleCorsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.setAllowedOrigins(Collections.singletonList("*"));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowedHeaders(Collections.singletonList("*"));
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
如果您确实使用像这样的CORS过滤器,我建议您将源、方法和头更改为更具体的,以提高安全性。
CarServiceApplication.java
仅配置为资源服务器,因为不希望直接访问它。
@Configuration
static class OktaOAuth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests().anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
// @formatter:on
}
}
为了使API网关能够访问汽车服务,我在API网关项目中创建了一个UserFeignClientInterceptor.java
。
package com.example.apigateway;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
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.Component;
@Component
public class UserFeignClientInterceptor implements RequestInterceptor {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_TOKEN_TYPE = "Bearer";
private final OAuth2AuthorizedClientService clientService;
public UserFeignClientInterceptor(OAuth2AuthorizedClientService clientService) {
this.clientService = clientService;
}
@Override
public void apply(RequestTemplate template) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
OAuth2AuthorizedClient client = clientService.loadAuthorizedClient(
oauthToken.getAuthorizedClientRegistrationId(),
oauthToken.getName());
OAuth2AccessToken accessToken = client.getAccessToken();
template.header(AUTHORIZATION_HEADER, String.format("%s %s", BEARER_TOKEN_TYPE, accessToken.getTokenValue()));
}
}
我在ApiGatewayApplication.java
中将其配置为RequestInterceptor
:
@Bean
public RequestInterceptor getUserFeignClientInterceptor(OAuth2AuthorizedClientService clientService) {
return new UserFeignClientInterceptor(clientService);
}
而且,我在api-gateway/src/main/resources/application.properties
中添加了两个属性,以便Feign具有Spring安全意识。
feign.hystrix.enabled=true
hystrix.shareSecurityContext=true
启用了安全性的Java微服务
使用./mvnw
在单独的终端窗口中运行所有应用程序,如果愿意,也可以在IDE中运行。
导航到http://localhost:8080/cool-cars
,您将被重定向到Okta登录。
【图片】
输入用户名和密码为您的Okta开发者帐户,你应该看到一个很酷的汽车列表。
【图片】
如果你做到了这一点,并运行了示例应用程序,恭喜你!你太酷了!
使用Netflix Zuul和Spring云代理路由
在微服务架构中,您可能喜欢的另一个便利功能是Netflix Zuul。Zuul是一个网关服务,提供动态路由、监视、恢复等功能。
为了添加Zuul,我将其作为依赖项添加到api-gateway/pom.xml
:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
然后,我将@EnableZuulProxy
添加到ApiGatewayApplication
类中。
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@SpringBootApplication
public class ApiGatewayApplication {
...
}
为了将访问令牌传递给代理路由,我创建了一个AuthorizationHeaderFilter
类,该类扩展了ZuFilter
。
package com.example.apigateway;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.core.Ordered;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
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.security.oauth2.core.oidc.OidcUserInfo;
import java.util.Optional;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
public class AuthorizationHeaderFilter extends ZuulFilter {
private final OAuth2AuthorizedClientService clientService;
public AuthorizationHeaderFilter(OAuth2AuthorizedClientService clientService) {
this.clientService = clientService;
}
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
Optional<String> authorizationHeader = getAuthorizationHeader();
authorizationHeader.ifPresent(s -> ctx.addZuulRequestHeader("Authorization", s));
return null;
}
private Optional<String> getAuthorizationHeader() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
OAuth2AuthorizedClient client = clientService.loadAuthorizedClient(
oauthToken.getAuthorizedClientRegistrationId(),
oauthToken.getName());
OAuth2AccessToken accessToken = client.getAccessToken();
if (accessToken == null) {
return Optional.empty();
} else {
String tokenType = accessToken.getTokenType().getValue();
String authorizationHeaderValue = String.format("%s %s", tokenType, accessToken.getTokenValue());
return Optional.of(authorizationHeaderValue);
}
}
}
您可能注意到getAuthorizationHeader()
方法中的代码与UserFeignClientInterceptor
中的代码非常相似。因为只有几行,所以我选择不把它们移到实用类中。
为了让springboot和Zuul知道这个过滤器,我在主应用程序类中将它注册为bean。
public AuthorizationHeaderFilter authHeaderFilter(OAuth2AuthorizedClientService clientService) {
return new AuthorizationHeaderFilter(clientService);
}
为了代理从API网关到汽车服务的请求,我添加了路由到API网关/src/main/resources/application.properties
。
zuul.routes.car-service.path=/cars
zuul.routes.car-service.url=http://localhost:8090
zuul.routes.home.path=/home
zuul.routes.home.url=http://localhost:8090
zuul.sensitive-headers=Cookie,Set-Cookie
我为/home路线的汽车服务项目添加了一个HomeController
。
package com.example.carservice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
@RestController
public class HomeController {
private final static Logger log = LoggerFactory.getLogger(HomeController.class);
@GetMapping("/home")
public String howdy(Principal principal) {
String username = principal.getName();
JwtAuthenticationToken token = (JwtAuthenticationToken) principal;
log.info("claims: " + token.getTokenAttributes());
return "Hello, " + username;
}
}
确认你的Zuul路线是否正常
由于这些更改已经存在于克隆的项目中,因此您应该能够查看https://localhost:8080/cars
和 http://localhost:8080/home
。
Spring Cloud Config
在本例中,您可能注意到的一点是您必须在每个应用程序中配置OIDC属性。如果你有500个微服务,这可能是一个真正的痛苦。是的,您可以将它们定义为环境变量,这样就可以解决问题。但是,如果您有使用不同OIDC客户机ID的不同微服务堆栈,那么这种方法将很困难。
Spring Cloud Config是一个为分布式系统提供外部化配置的项目。我将在以后的教程中介绍它,而不是将其添加到本示例中。
Kotlin呢?
我用Java写这篇文章是因为它是Java生态系统中最流行的语言。然而,根据RedMonk 2019年1月的编程语言排名,Kotlin正在上升。
Spring对Kotlin有很好的支持,您可以选择它作为start.Spring.io上的一种语言。
您可以在GitHub上找到本教程中显示的所有代码:https://github.com/oktadeveloper/java-microservices-examples
原文地址:https://dzone.com/articles/microservices-architecture-with-spring-boot-and-sp
除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2028.html
暂无评论