SpringCloud微服务实战——搭建企业级开发框架(四十四):【微服务监控告 ...

打印 上一主题 下一主题

主题 867|帖子 867|积分 2603

  业务系统正常运行的稳定性十分重要,作为SpringBoot的四大核心之一,Actuator让你时刻探知SpringBoot服务运行状态信息,是保障系统正常运行必不可少的组件。
  spring-boot-starter-actuator提供的是一系列HTTP或者JMX监控端点,通过监控端点我们可以获取到系统的运行统计信息,同时,我们可以自己选择开启需要的监控端点,也可以自定义扩展监控端点。
  Actuator通过端点对外暴露的监控信息是JSON格式数据,我们需要使用界面来展示,目前使用比较多的就是Spring Boot Admin或者Prometheus + Grafana的方式:Spring Boot Admin实现起来相对比较简单,不存在数据库,不能存储和展示历史监控数据;Prometheus(时序数据库) + Grafana(界面)的方式相比较而言功能更丰富,提供历史记录存储,界面展示也比较美观。
  相比较而言,Prometheus + Grafana的方式更为流行一些,现在的微服务及Kubernetes基本是采用这种方式的。但是对于小的项目或者单体应用,Spring Boot Admin会更加方便快捷一些。具体采用那种方式,可以根据自己的系统运维需求来取舍,这里我们把框架集成两种方式,在实际应用过程中自有选择。
  本文主要介绍如何集成Spring Boot Admin以及通过SpringSecurity控制Actuator的端点权限。
1、在基础服务gitegg-platform中引入spring-boot-starter-actuator包。

  无论是使用Spring Boot Admin还是使用Prometheus + Grafana的方式都需要spring-boot-starter-actuator来获取监控信息,这里将spring-boot-starter-actuator包添加到gitegg-platform-boot基础平台包中,这样所有的微服务都集成了此功能。
  1.         
  2.         <dependency>
  3.             <groupId>org.springframework.boot</groupId>
  4.             <artifactId>spring-boot-starter-actuator</artifactId>
  5.         </dependency>
复制代码
2、确定并引入工程使用的spring-boot-admin-starter-server和spring-boot-admin-starter-client依赖包。

  spring-boot-admin-starter-server是Spring Boot Admin的服务端,我们需要新建一个SpringBoot工程来启动这个服务端,用来接收需要监控的服务注册,展示监控告警信息。spring-boot-admin-starter-client是客户端,需要被监控的服务需要引入这个依赖包。
  此处请注意: 看到网上很多文章里面写着添加spring-boot-admin-starter-client包,在SpringCloud微服务中是不需要引入的,spring-boot-admin-starter-client包仅仅是为了引入我们gitegg-platform平台工程的对应版本,在gitegg-boot框架中使用,在SpringCloud微服务框架中,不需要引入spring-boot-admin-starter-client,SpringBootAdmin会自动根据微服务注册信息查找服务端点,官方文档说明:spring-cloud-discovery-support
  在选择版本时,一定要找到对应SpringBoot版本的Spring Boot Admin,GitHub上有版本对应关系的说明:

  我们在gitegg-platform-pom中来定义需要引入的spring-boot-admin-starter-server和spring-boot-admin-starter-client依赖包版本,然后在微服务业务开发中具体引入,这里不做统一引入,方便微服务切换监控方式。
  1. ......
  2.         
  3.         <spring.boot.admin.version>2.3.1</spring.boot.admin.version>
  4. ......
  5.             
  6.             <dependency>
  7.                 <groupId>de.codecentric</groupId>
  8.                 <artifactId>spring-boot-admin-starter-server</artifactId>
  9.                 <version>${spring.boot.admin.version}</version>
  10.             </dependency>
  11.             
  12.             <dependency>
  13.                 <groupId>de.codecentric</groupId>
  14.                 <artifactId>spring-boot-admin-starter-client</artifactId>
  15.                 <version>${spring.boot.admin.version}</version>
  16.             </dependency>.
  17. ......
复制代码
3、在GitEgg-Cloud项目的gitegg-plugin工程下新建gitegg-admin-monitor工程,用于运行spring-boot-admin-starter-server。


  • pom.xml中引入需要的依赖包
  1.     <dependencies>
  2.         
  3.         <dependency>
  4.             <groupId>com.gitegg.platform</groupId>
  5.             <artifactId>gitegg-platform-boot</artifactId>
  6.             
  7.             <exclusions>
  8.                 <exclusion>
  9.                     <groupId>com.gitegg.platform</groupId>
  10.                     <artifactId>gitegg-platform-cache</artifactId>
  11.                 </exclusion>
  12.             </exclusions>
  13.         </dependency>
  14.         
  15.         <dependency>
  16.             <groupId>com.gitegg.platform</groupId>
  17.             <artifactId>gitegg-platform-cloud</artifactId>
  18.         </dependency>
  19.         
  20.         <dependency>
  21.             <groupId>org.springframework.boot</groupId>
  22.             <artifactId>spring-boot-starter-security</artifactId>
  23.             
  24.             <exclusions>
  25.                 <exclusion>
  26.                     <groupId>org.springframework.boot</groupId>
  27.                     <artifactId>spring-boot-starter-logging</artifactId>
  28.                 </exclusion>
  29.             </exclusions>
  30.         </dependency>
  31.         <dependency>
  32.             <groupId>de.codecentric</groupId>
  33.             <artifactId>spring-boot-admin-starter-server</artifactId>
  34.         </dependency>
  35.     </dependencies>
复制代码

  • 添加spring-boot-admin-starter-server启动类GitEggMonitorApplication.java,添加@EnableAdminServer注解即可。
  1. @EnableAdminServer
  2. @SpringBootApplication
  3. @RefreshScope
  4. public class GitEggMonitorApplication {
  5.    
  6.     public static void main(String[] args)
  7.     {
  8.         SpringApplication.run(GitEggMonitorApplication.class, args);
  9.     }
  10.    
  11. }
复制代码

  • 添加SpringSecurity的WebSecurityConfigurerAdapter配置类,保护监控系统安全。
      这里主要配置登录页面、静态文件、登录、退出等的权限。请注意这里配置了publicUrl的前缀,当部署在微服务环境或Docker环境中需要经过gateway或者nginx转发时,在SpringBootAdmin配置中,需要配置publicUrl,否则SpringBootAdmin只会跳转到本机环境的地址和端口。publicUrl如果是80端口,那么这个端口不能省略,需要配置上。
  1. @Configuration(proxyBeanMethods = false)
  2. public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
  3.    
  4.     private final AdminServerUiProperties adminUi;
  5.    
  6.     private final AdminServerProperties adminServer;
  7.    
  8.     private final SecurityProperties security;
  9.    
  10.     public SecuritySecureConfig(AdminServerUiProperties adminUi, AdminServerProperties adminServer, SecurityProperties security) {
  11.         this.adminUi = adminUi;
  12.         this.adminServer = adminServer;
  13.         this.security = security;
  14.     }
  15.    
  16.     @Override
  17.     protected void configure(HttpSecurity http) throws Exception {
  18.         
  19.         // 当设置了publicUrl时,Gateway跳转到login或logout链接需要redirect到publicUrl
  20.         String publicUrl = this.adminUi.getPublicUrl() != null ? this.adminUi.getPublicUrl() : this.adminServer.getContextPath();
  21.         SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
  22.         successHandler.setTargetUrlParameter("redirectTo");
  23.         successHandler.setDefaultTargetUrl(publicUrl + "/");
  24.         
  25.         http.authorizeRequests(
  26.                 (authorizeRequests) -> authorizeRequests.antMatchers(this.adminServer.path("/assets/**")).permitAll()
  27.                         .antMatchers(this.adminServer.path("/actuator/info")).permitAll()
  28.                         .antMatchers(this.adminServer.path("/actuator/health")).permitAll()
  29.                         .antMatchers(this.adminServer.path("/login")).permitAll().anyRequest().authenticated()
  30.         ).formLogin(
  31.                 (formLogin) -> formLogin.loginPage(publicUrl + "/login").loginProcessingUrl(this.adminServer.path("/login")).successHandler(successHandler).and()
  32.         ).logout((logout) -> logout.logoutUrl(publicUrl + "/logout")).httpBasic(Customizer.withDefaults())
  33.                 .csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
  34.                         .ignoringRequestMatchers(
  35.                                 new AntPathRequestMatcher(this.adminServer.path("/instances"),
  36.                                         HttpMethod.POST.toString()),
  37.                                 new AntPathRequestMatcher(this.adminServer.path("/instances/*"),
  38.                                         HttpMethod.DELETE.toString()),
  39.                                 new AntPathRequestMatcher(this.adminServer.path("/actuator/**"))
  40.                         ))
  41.                 .rememberMe((rememberMe) -> rememberMe.key(UUID.randomUUID().toString()).tokenValiditySeconds(1209600));
  42.     }
  43.    
  44.     /**
  45.      * Required to provide UserDetailsService for "remember functionality"
  46.      * @param auth
  47.      * @throws Exception
  48.      */
  49.     @Override
  50.     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  51.         auth.inMemoryAuthentication().withUser(security.getUser().getName())
  52.                 .password("{noop}" + security.getUser().getPassword()).roles(security.getUser().getRoles().toArray(new String[0]));
  53.     }
  54.    
  55. }
复制代码
4、在Nacos配置中心配置SpringBootAdmin的相关配置,在gitegg-admin-monitor工程中,也需要配置读取配置的相关yml文件,除了读取主配置之外,还需要读取SpringBootAdmin专属配置。


  • 新增gitegg-cloud-config-admin-monitor.yaml配置文件
  1. spring:
  2.   boot:
  3.     admin:
  4.       ui:
  5.         brand: <img src="https://img2022.cnblogs.com/blog/460952/202207/460952-20220727124816822-208395561.png">GitEgg微服务监控系统
  6.         title: GitEgg微服务监控系统
  7.         favicon: https://img2022.cnblogs.com/blog/460952/202207/460952-20220727124816822-208395561.png
  8.         public-url: http://127.0.0.1:80/gitegg-admin-monitor/monitor
  9.       context-path: /monitor
复制代码

  • 在bootstrap.yml中新增读取gitegg-cloud-config-admin-monitor.yaml的配置
  1. server:
  2.   port: 8009
  3. spring:
  4.   profiles:
  5.     active: '@spring.profiles.active@'
  6.   application:
  7.     name: '@artifactId@'
  8.   cloud:
  9.     inetutils:
  10.       ignored-interfaces: docker0
  11.     nacos:
  12.       discovery:
  13.         server-addr: ${spring.nacos.addr}
  14.         metadata:
  15.           # 启用SpringBootAdmin时 客户端端点信息的安全认证信息
  16.           user.name: ${spring.security.user.name}
  17.           user.password: ${spring.security.user.password}
  18.       config:
  19.         server-addr: ${spring.nacos.addr}
  20.         file-extension: yaml
  21.         extension-configs:
  22.           # 必须带文件扩展名,此时 file-extension 的配置对自定义扩展配置的 Data Id 文件扩展名没有影响
  23.           - data-id: ${spring.nacos.config.prefix}.yaml
  24.             group: ${spring.nacos.config.group}
  25.             refresh: true
  26.           - data-id: ${spring.nacos.config.prefix}-admin-monitor.yaml
  27.             group: ${spring.nacos.config.group}
  28.             refresh: true
复制代码
5、扩展gitegg-gateway的SpringSecurity配置,增加统一鉴权校验。因我们有多个微服务,且所有的微服务在生产环境部署时都不会暴露端口,所以所有的微服务鉴权都会在网关做。

  SpringSecurity权限验证支持多过滤器配置,同时可配置验证顺序,我们这里需要改造之前的过滤器,这里新增Basic认证过滤器,通过securityMatcher设置,只有健康检查的请求走这个权限过滤器,其他请求继续走之前我们设置的OAuth2+JWT权限验证器。
[code]/** * 权限配置 * 注解需要使用@EnableWebFluxSecurity而非@EnableWebSecurity,因为SpringCloud Gateway基于WebFlux * * @author GitEgg * */@RequiredArgsConstructor(onConstructor_ = @Autowired)@Configuration@EnableWebFluxSecuritypublic class MultiWebSecurityConfig {        private final AuthorizationManager authorizationManager;        private final AuthServerAccessDeniedHandler authServerAccessDeniedHandler;        private final AuthServerAuthenticationEntryPoint authServerAuthenticationEntryPoint;        private final AuthUrlWhiteListProperties authUrlWhiteListProperties;        private final WhiteListRemoveJwtFilter whiteListRemoveJwtFilter;        private final SecurityProperties securityProperties;        @Value("${management.endpoints.web.base-path:}")    private String actuatorPath;        /**     * 健康检查接口权限配置     * @param http     * @return     */    @Order(Ordered.HIGHEST_PRECEDENCE)    @Bean    @ConditionalOnProperty( value = {"management.security.enabled", "management.endpoints.enabled-by-default"}, havingValue = "true")    SecurityWebFilterChain webHttpSecurity(ServerHttpSecurity http) {        if (StringUtils.isEmpty(actuatorPath))        {            throw new BusinessException("当启用健康检查时,不允许健康检查的路径为空");        }        http                .cors()                .and()                .csrf().disable()                .formLogin().disable()                .securityMatcher(new OrServerWebExchangeMatcher(                        new PathPatternParserServerWebExchangeMatcher(actuatorPath + "/**"),                        new PathPatternParserServerWebExchangeMatcher("/**" + actuatorPath + "/**")                ))                .authorizeExchange((exchanges) -> exchanges                        .anyExchange().hasAnyRole(securityProperties.getUser().getRoles().toArray(new String[0]))                )                .httpBasic(Customizer.withDefaults());        return http.build();    }        /**     * 设置Basic认证用户信息     * @return     */    @Bean    @ConditionalOnProperty( value = {"management.security.enabled", "management.endpoints.enabled-by-default"}, havingValue = "true")    ReactiveUserDetailsService userDetailsService() {        return new MapReactiveUserDetailsService(User                .withUsername(securityProperties.getUser().getName())                .password(passwordEncoder().encode(securityProperties.getUser().getPassword()))                .roles(securityProperties.getUser().getRoles().toArray(new String[0]))                .build());    }        /**     * 设置密码编码     * @return     */    @Bean    @ConditionalOnProperty( value = {"management.security.enabled", "management.endpoints.enabled-by-default"}, havingValue = "true")    public static PasswordEncoder passwordEncoder() {        DelegatingPasswordEncoder delegatingPasswordEncoder =                (DelegatingPasswordEncoder) PasswordEncoderFactories.createDelegatingPasswordEncoder();        return  delegatingPasswordEncoder;    }        /**     * 路由转发权限配置     * @param http     * @return     */    @Bean    SecurityWebFilterChain apiHttpSecurity(ServerHttpSecurity http) {                http.oauth2ResourceServer().jwt()                .jwtAuthenticationConverter(jwtAuthenticationConverter());            // 自定义处理JWT请求头过期或签名错误的结果        http.oauth2ResourceServer().authenticationEntryPoint(authServerAuthenticationEntryPoint);            // 对白名单路径,直接移除JWT请求头,不移除的话,后台会校验jwt        http.addFilterBefore(whiteListRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION);            // Basic认证直接放行        if (!CollectionUtils.isEmpty(authUrlWhiteListProperties.getTokenUrls()))        {            http.authorizeExchange().pathMatchers(ArrayUtil.toArray(authUrlWhiteListProperties.getTokenUrls(), String.class)).permitAll();        }            // 判断是否有静态文件        if (!CollectionUtils.isEmpty(authUrlWhiteListProperties.getStaticFiles()))        {            http.authorizeExchange().pathMatchers(ArrayUtil.toArray(authUrlWhiteListProperties.getStaticFiles(), String.class)).permitAll();        }            http.authorizeExchange()                .pathMatchers(ArrayUtil.toArray(authUrlWhiteListProperties.getWhiteUrls(), String.class)).permitAll()                .anyExchange().access(authorizationManager)                .and()                .exceptionHandling()                /**                 * 处理未授权                 */                .accessDeniedHandler(authServerAccessDeniedHandler)                /**                 * 处理未认证                 */                .authenticationEntryPoint(authServerAuthenticationEntryPoint)                .and()                .cors()                .and().csrf().disable();            return http.build();    }        /**     * ServerHttpSecurity没有将jwt中authorities的负载部分当做Authentication,需要把jwt的Claim中的authorities加入     * 解决方案:重新定义ReactiveAuthenticationManager权限管理器,默认转换器JwtGrantedAuthoritiesConverter     */    @Bean    public Converter

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用多少眼泪才能让你相信

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表