PiggyMetrics是一个模拟的个人记账理财的应用,原作者称其为一个端到端的微服务PoC(Proof of Concept),也就是说他开发这个是为了验证微服务架构和Spring Cloud技术栈。PiggyMetrics目前在github上有超过12k星,是学习微服务架构和Spring Cloud技术栈的一个不错参考。
- │
- ├─account-service # 账户服务
- │ │ Dockerfile
- │ │ pom.xml
- │ │
- │ └─src
- │ ├─main
- │ │ ├─java
- │ │ │ └─com
- │ │ │ └─piggymetrics
- │ │ │ └─account
- │ │ │ │ AccountApplication.java
- │ │ │ ├─client
- │ │ │ │ AuthServiceClient.java
- │ │ │ │ StatisticsServiceClient.java
- │ │ │ │ StatisticsServiceClientFallback.java
- │ │ │ ├─config
- │ │ │ │ ResourceServerConfig.java
- │ │ │ │
- │ │ │ ├─controller
- │ │ │ │ AccountController.java
- │ │ │ │ ErrorHandler.java
- │ │ │ │
- │ │ │ ├─domain
- │ │ │ │ Account.java
- │ │ │ │ Currency.java
- │ │ │ │ Item.java
- │ │ │ │ Saving.java
- │ │ │ │ TimePeriod.java
- │ │ │ │ User.java
- │ │ │ │
- │ │ │ ├─repository
- │ │ │ │ AccountRepository.java
- │ │ │ └─service
- │ │ │ │ AccountService.java
- │ │ │ │ AccountServiceImpl.java
- │ │ │ │
- │ │ │ └─security
- │ │ │ CustomUserInfoTokenServices.java
- │ │ │
- │ │ └─resources
- │ │ bootstrap.yml
- │
- ├─auth-service # 授权认证服务
- │ │ Dockerfile
- │ │ pom.xml
- │ │
- │ └─src
- │ ├─main
- │ │ ├─java
- │ │ │ └─com
- │ │ │ └─piggymetrics
- │ │ │ └─auth
- │ │ │ │ AuthApplication.java
- │ │ │ ├─config
- │ │ │ │ OAuth2AuthorizationConfig.java
- │ │ │ │ WebSecurityConfig.java
- │ │ │ ├─controller
- │ │ │ │ UserController.java
- │ │ │ ├─domain
- │ │ │ │ User.java
- │ │ │ ├─repository
- │ │ │ │ UserRepository.java
- │ │ │ └─service
- │ │ │ │ UserService.java
- │ │ │ │ UserServiceImpl.java
- │ │ │ └─security
- │ │ │ MongoUserDetailsService.java
- │ │ │
- │ │ └─resources
- │ │ bootstrap.yml
- │ │
- │ └─
- ├─config # 配置中心
- │ │ Dockerfile
- │ │ pom.xml
- │ │
- │ └─src
- │ └─main
- │ ├─java
- │ │ └─com
- │ │ └─piggymetrics
- │ │ └─config
- │ │ ConfigApplication.java
- │ │ SecurityConfig.java
- │ │
- │ └─resources
- │ │ application.yml
- │ │
- │ └─shared
- │ account-service.yml
- │ application.yml
- │ auth-service.yml
- │ gateway.yml
- │ monitoring.yml
- │ notification-service.yml
- │ registry.yml
- │ statistics-service.yml
- │ turbine-stream-service.yml
- │
- ├─gateway # API网关
- │ │ Dockerfile
- │ │ pom.xml
- │ │
- │ └─src
- │ ├─main
- │ │ ├─java
- │ │ │ └─com
- │ │ │ └─piggymetrics
- │ │ │ └─gateway
- │ │ │ GatewayApplication.java
- │ │ │
- │ │ └─resources
- │
- ├─monitoring
- │ │ Dockerfile
- │ │ pom.xml
- │ │
- │ └─src
- │ ├─main
- │ │ ├─java
- │ │ │ └─com
- │ │ │ └─piggymetrics
- │ │ │ └─monitoring
- │ │ │ MonitoringApplication.java
- │ │ │
- │ │ └─resources
- │ │ bootstrap.yml
- │ │
- │ └─test
- │ ├─java
- │ │ └─com
- │ │ └─piggymetrics
- │ │ └─monitoring
- │ │ MonitoringApplicationTests.java
- │ │
- │ └─resources
- │ bootstrap.yml
- │
- ├─notification-service # 通知服务
- │ │ Dockerfile
- │ │ pom.xml
- │ │
- │ └─src
- │ ├─main
- │ │ ├─java
- │ │ │ └─com
- │ │ │ └─piggymetrics
- │ │ │ └─notification
- │ │ │ │ NotificationServiceApplication.java
- │ │ │ │
- │ │ │ ├─client
- │ │ │ │ AccountServiceClient.java
- │ │ │ │
- │ │ │ ├─config
- │ │ │ │ ResourceServerConfig.java
- │ │ │ │
- │ │ │ ├─controller
- │ │ │ │ RecipientController.java
- │ │ │ │
- │ │ │ ├─domain
- │ │ │ │ Frequency.java
- │ │ │ │ NotificationSettings.java
- │ │ │ │ NotificationType.java
- │ │ │ │ Recipient.java
- │ │ │ │
- │ │ │ ├─repository
- │ │ │ │ │ RecipientRepository.java
- │ │ │ │ │
- │ │ │ │ └─converter
- │ │ │ │ FrequencyReaderConverter.java
- │ │ │ │ FrequencyWriterConverter.java
- │ │ │ │
- │ │ │ └─service
- │ │ │ EmailService.java
- │ │ │ EmailServiceImpl.java
- │ │ │ NotificationService.java
- │ │ │ NotificationServiceImpl.java
- │ │ │ RecipientService.java
- │ │ │ RecipientServiceImpl.java
- │ │ │
- │ │ └─resources
- │ │ bootstrap.yml
- │ │
- ├─registry # 注册服务
- │ │ Dockerfile
- │ │ pom.xml
- │ │
- │ └─src
- │ └─main
- │ ├─java
- │ │ └─com
- │ │ └─piggymetrics
- │ │ └─registry
- │ │ RegistryApplication.java
- │ │
- │ └─resources
- │ bootstrap.yml
- │
- ├─statistics-service # 统计服务
- │ │ Dockerfile
- │ │ pom.xml
- │ │
- │ └─src
- │ ├─main
- │ │ ├─java
- │ │ │ └─com
- │ │ │ └─piggymetrics
- │ │ │ └─statistics
- │ │ │ │ StatisticsApplication.java
- │ │ │ │
- │ │ │ ├─client
- │ │ │ │ ExchangeRatesClient.java
- │ │ │ │ ExchangeRatesClientFallback.java
- │ │ │ │
- │ │ │ ├─config
- │ │ │ │ ResourceServerConfig.java
- │ │ │ │
- │ │ │ ├─controller
- │ │ │ │ StatisticsController.java
- │ │ │ │
- │ │ │ ├─domain
- │ │ │ │ │ Account.java
- │ │ │ │ │ Currency.java
- │ │ │ │ │ ExchangeRatesContainer.java
- │ │ │ │ │ Item.java
- │ │ │ │ │ Saving.java
- │ │ │ │ │ TimePeriod.java
- │ │ │ │ │
- │ │ │ │ └─timeseries
- │ │ │ │ DataPoint.java
- │ │ │ │ DataPointId.java
- │ │ │ │ ItemMetric.java
- │ │ │ │ StatisticMetric.java
- │ │ │ │
- │ │ │ ├─repository
- │ │ │ │ │ DataPointRepository.java
- │ │ │ │ │
- │ │ │ │ └─converter
- │ │ │ │ DataPointIdReaderConverter.java
- │ │ │ │ DataPointIdWriterConverter.java
- │ │ │ │
- │ │ │ └─service
- │ │ │ │ ExchangeRatesService.java
- │ │ │ │ ExchangeRatesServiceImpl.java
- │ │ │ │ StatisticsService.java
- │ │ │ │ StatisticsServiceImpl.java
- │ │ │ │
- │ │ │ └─security
- │ │ │ CustomUserInfoTokenServices.java
- │ │ │
- │ │ └─resources
- │ │ bootstrap.yml
- │
- └─turbine-stream-service # turbine流服务
- │ Dockerfile
- │ pom.xml
- │
- └─src
- ├─main
- │ ├─java
- │ │ └─com
- │ │ └─piggymetrics
- │ │ └─turbine
- │ │ TurbineStreamServiceApplication.java
- │ │
- │ └─resources
- │ bootstrap.yml
- └─
- 复制代码
技术栈 | 技术栈选型 |
---|---|
API网关 | Spring Cloud Zuul |
负载均衡 | Eurake+Ribbon |
远程调用 | Feign |
限流熔断 | Hystrix |
服务注册与发现 | Spring Cloud Eurake |
授权认证 | Spring Cloud OAuth2 |
配置中心 | Spring Cloud Config&&Apollo |
分布式调用链监控 | Spring Cloud Sleuth |
链路追踪 | Zipkin |
消息队列 | RabbitMQ |
日志监控 | ELK |
目前PiggMetrics采用前后分离架构,前端是单页SPA,后端采用基于Spring Cloud技术栈的微服务架构。
Piggy Metrics
项目主要有三个服务,Account service 账户服务,存储用户账户和记账信息、Statistics service 统计服务,计算用户财务状况和统计信息、Notification service 通知服务,存储通知和备份等相关配置。
Spring Cloud Zuul
的网关,是调用后台API的聚合入口,实现反向路由和负载均衡(Eureka+Ribbon
)、限流熔断(Hystrix
)等功能。CLIENT
单页应用和ZUUL
网关暂住在一起,简化部署。Spring Cloud Eureka
的服务注册中心。业务服务启动时通过Eureka
注册,网关调用后台服务通过Eureka
做服务发现,服务之间调用也通过Eureka
做服务发现。Spring Security OAuth2
的授权认证中心。客户端登录时通过AUTHSERVICE
获取访问令牌(走用户名密码模式)。服务之间调用也通过AUTHSERVICE
获取访问令牌(走客户端模式)。令牌校验方式、各资源服务器去AUTHSERVICE
集中校验令牌。Spring Cloud Config
的配置中心,集中管理所有Spring
服务的配置文件。Spring Cloud Sleuth
的调用链监控。网关调用后台服务,服务之间调用,都采用Zipkin进行埋点和跟踪。Spring Cloud Ribbon&Hystrix
,Zuul
调用后台服务,服务之间相互调用,都通过Ribbon
实现软负载,也通过Hystrix
实现熔断限流保护。Spring Cloud Turbine + Hystrix Dashboard
,对所有Hystrix
产生的Metrics
流进行聚合,并展示在Hystrix Dashboard
上。ELK
栈集中收集和分析应用日志。上图是经过造后优化后的架构,浅蓝色标注的都属于基础服务,主要替换的组件如下:
Gravitee OAuth2
服务器。Apollo
做统一配置中心,集中管理所有Spring
微服务的配置。CAT
做调用链监控,从网关调后台服务,服务之间相互调用,都采用CAT
客户端进行埋点监控。CAT
埋点既演示使用拦截器(interceptor
)方式,也演示使用AOP
非侵入方式。Prometheus Metrics
端点,便于集成Prometheus
监控和告警。其它组件,比如Zuul
网关、Eureka
服务发现、Ribbon
软负载、Hystrix
限流熔断,以及ELK
集中日志都同原架构,没有太大变化。
通过Piggy Metrics
项目可以从中学习到以下几点:
Apollo
集中管理Spring
应用的配置?token
怎么实现?OAuth2
的注册登录和API
调用具体是如何实现的?CAT
非侵入式埋点怎么做,如何尽量减少业务研发直接使用CAT
进行埋点?上图展示PiggyMetrics的登录注册流程,简化流程如下:
Account Svc
)。Gravitee OAuth2
)创建一个用户(包括用户和密码,这样后续才可以登录获取访问令牌)。账户服务再保存新账户信息到本地MongoDB
数据库。localstorage
。上图展示PiggyMetrics的API调用流程,简化流程如下:
将用户名以HTTP HEADER方式向后台服务传递
,如果不通过,则直接报授权错到客户端。HTTP HEADER
请求头获取用户名等信息,可通过用户名进一步查询用户相关信息,实现业务逻辑。客户端调用后台服务,经过改造为网关集中校验令牌
方式,这样可以简化安全架构,即在企业内网,资源服务器端可直接获取用户名信息,不需要再到授权服务器做集中令牌校验。另外,服务之间的调用也改造为可以直接调用,不需要授权认证和令牌,这种做法也是很多一线企业实际落地的做法,即在生产环境中,内部服务之间调用不授权认证,这样可以简化服务的开发和部署,但是对于安全敏感的服务要求做好生产网段隔离(需运维配合)。
项目采用Spring Security&OAuth2
实现用户认证授权,通过实现UserDetailsService
接口实现身份认证:
- @Service
- public class MongoUserDetailsService implements UserDetailsService {
-
- @Autowired
- private UserRepository repository;
-
- /**
- * 根据用户名获取用户(用户的角色、权限等信息)
- *
- * @return UserDetails
- */
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- return repository.findById(username).orElseThrow(() -> new UsernameNotFoundException(username));
- }
- }
- 复制代码
security的WebSecurityConfigurerAdapter常用于配置任认证管理器配置、核心过滤器配置、安全过滤器链配置, HttpSecurity使用了builder的构建方式来灵活指定访问策略。
- /**
- * spring security配置
- *
- * @return UserDetails
- */
- @Configuration
- public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
-
- /**
- * 自定义身份认证逻辑
- */
- @Autowired
- private MongoUserDetailsService userDetailsService;
-
- /**
- * anyRequest | 匹配所有请求路径
- * access | SpringEl表达式结果为true时可以访问
- * anonymous | 匿名可以访问
- * denyAll | 用户不能访问
- * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
- * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
- * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
- * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
- * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
- * hasRole | 如果有参数,参数表示角色,则其角色可以访问
- * permitAll | 用户可以任意访问
- * rememberMe | 允许通过remember-me登录的用户访问
- * authenticated | 用户登录后可访问
- */
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.authorizeRequests().anyRequest()
- .authenticated()
- .and()
- .csrf().disable();
- }
-
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- auth.userDetailsService(userDetailsService)
- .passwordEncoder(new BCryptPasswordEncoder());
- }
-
- /**
- * 解决 无法直接注入 AuthenticationManager
- */
- @Override
- @Bean
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
- }
- }
- 复制代码
AuthorizationServerConfigurer
是配置OAuth2
授权配置服务的配置类接口,只需要添加@EnableAuthorizationServer
,Spring会实现自动注入,接口有三个方法,可实现客户端的配置、安全功能、以及各个Endpoint(端点)的相关配置。
- public interface AuthorizationServerConfigurer {
- // 配置授权服务器的安全性
- void configure(AuthorizationServerSecurityConfigurer security) throws Exception;
-
- //客户端信息配置
- void configure(ClientDetailsServiceConfigurer clients) throws Exception;
-
- //配置授权服务器端点的非安全功能,如令牌存储、令牌定制、用户批准和授权类型。默认情况下你不需要做任何事情,除非你需要密码授权,在这种情况下你需要提供一个 {@link AuthenticationManager}。
- void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception;
- }
- 复制代码
AuthorizationServerConfigurerAdapter继承了AuthorizationServerConfigurer接口
- public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {
-
- public AuthorizationServerConfigurerAdapter() {
- }
-
- public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
- }
-
- public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
- }
-
- public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
- }
- }
- 复制代码
OAuth2AuthorizationConfig
是AuthorizationServerConfigurer
默认的实现类,它是Spring Security OAuth2
授权服务器的默认配置,当没有自定义配置时,将会使用此配置。
- EnableAuthorizationServer
- public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
-
- //保存 OAuth2 token ,有InMemoryTokenStore、JdbcTokenStore、JwkTokenStore、RedisTokenStore
- private TokenStore tokenStore = new InMemoryTokenStore();
-
- private final String NOOP_PASSWORD_ENCODE = "{noop}";
-
- @Autowired
- @Qualifier("authenticationManagerBean")
- private AuthenticationManager authenticationManager;
-
- @Autowired
- private MongoUserDetailsService userDetailsService;
-
- @Autowired
- private Environment env;
-
- @Override
- public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
-
- // 在内存中创建一个Oauth2 Client
- clients.inMemory()
- .withClient("browser")
- // 授权类型集合
- .authorizedGrantTypes("refresh_token", "password")
- .scopes("ui")
- .and()
- .withClient("account-service")
- // 密码
- .secret(env.getProperty("ACCOUNT_SERVICE_PASSWORD"))
- .authorizedGrantTypes("client_credentials", "refresh_token")
- .scopes("server")
- .and()
- .withClient("statistics-service")
- .secret(env.getProperty("STATISTICS_SERVICE_PASSWORD"))
- .authorizedGrantTypes("client_credentials", "refresh_token")
- .scopes("server")
- .and()
- .withClient("notification-service")
- .secret(env.getProperty("NOTIFICATION_SERVICE_PASSWORD"))
- .authorizedGrantTypes("client_credentials", "refresh_token")
- // 授权范围
- .scopes("server");
- }
-
- @Override
- public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
- // token存储器
- endpoints
- .tokenStore(tokenStore)
- .authenticationManager(authenticationManager)
- .userDetailsService(userDetailsService);
- }
-
- @Override
- public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
- oauthServer
- .tokenKeyAccess("permitAll()")
- .checkTokenAccess("isAuthenticated()")
- .passwordEncoder(NoOpPasswordEncoder.getInstance());
- }
- }
- 复制代码
Piggy Metrics在AccountService调用用户服务来创建用户,通过在账户服务定义FeignClient(AuthServiceClient)实现远程调用:
- @FeignClient(name = "auth-service")
- public interface AuthServiceClient {
-
- @RequestMapping(method = RequestMethod.POST, value = "/uaa/users", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
- void createUser(User user);
- }
- 复制代码
AuthService#UserController开放创建用户的API:
- @RestController
- @RequestMapping("/users")
- public class UserController {
-
- @Autowired
- private UserService userService;
-
- @RequestMapping(value = "/current", method = RequestMethod.GET)
- public Principal getUser(Principal principal) {
- return principal;
- }
-
- @PreAuthorize("#oauth2.hasScope('server')")
- @RequestMapping(method = RequestMethod.POST)
- public void createUser(@Valid @RequestBody User user) {
- userService.create(user);
- }
- }
- 复制代码
实际上这个架构进行生产化,仍需做生产化扩展,下面是一些可能的扩展点:
Spring Cloud Sleuth Starter
埋点方式),方便业务研发团队接入。基本上,框架层集中埋点以后,业务应用只需引入依赖即可,一般不需要再手工埋点。Gravitee OAuth2
集成在一起,但实际企业中用户服务可能是独立不耦合的,Gravitee OAuth2
可以扩展集成独立用户服务,账户服务也可以集成对接独立用户服务。Gravitee OAuth2
本身也需要扩展 Gravitee OAuth2。