??業(yè)務(wù)系統(tǒng)正常運(yùn)行的穩(wěn)定性十分重要击罪,作為SpringBoot的四大核心之一芋簿,Actuator讓你時(shí)刻探知SpringBoot服務(wù)運(yùn)行狀態(tài)信息,是保障系統(tǒng)正常運(yùn)行必不可少的組件说墨。
??spring-boot-starter-actuator提供的是一系列HTTP或者JMX監(jiān)控端點(diǎn)骏全,通過監(jiān)控端點(diǎn)我們可以獲取到系統(tǒng)的運(yùn)行統(tǒng)計(jì)信息,同時(shí)婉刀,我們可以自己選擇開啟需要的監(jiān)控端點(diǎn)吟温,也可以自定義擴(kuò)展監(jiān)控端點(diǎn)。
??Actuator通過端點(diǎn)對外暴露的監(jiān)控信息是JSON格式數(shù)據(jù)突颊,我們需要使用界面來展示鲁豪,目前使用比較多的就是Spring Boot Admin或者Prometheus + Grafana的方式:Spring Boot Admin實(shí)現(xiàn)起來相對比較簡單潘悼,不存在數(shù)據(jù)庫,不能存儲(chǔ)和展示歷史監(jiān)控?cái)?shù)據(jù)爬橡;Prometheus(時(shí)序數(shù)據(jù)庫) + Grafana(界面)的方式相比較而言功能更豐富治唤,提供歷史記錄存儲(chǔ),界面展示也比較美觀糙申。
??相比較而言宾添,Prometheus + Grafana的方式更為流行一些,現(xiàn)在的微服務(wù)及Kubernetes基本是采用這種方式的柜裸。但是對于小的項(xiàng)目或者單體應(yīng)用缕陕,Spring Boot Admin會(huì)更加方便快捷一些。具體采用那種方式疙挺,可以根據(jù)自己的系統(tǒng)運(yùn)維需求來取舍扛邑,這里我們把框架集成兩種方式,在實(shí)際應(yīng)用過程中自有選擇铐然。
??本文主要介紹如何集成Spring Boot Admin以及通過SpringSecurity控制Actuator的端點(diǎn)權(quán)限蔬崩。
1、在基礎(chǔ)服務(wù)gitegg-platform中引入spring-boot-starter-actuator包搀暑。
??無論是使用Spring Boot Admin還是使用Prometheus + Grafana的方式都需要spring-boot-starter-actuator來獲取監(jiān)控信息沥阳,這里將spring-boot-starter-actuator包添加到gitegg-platform-boot基礎(chǔ)平臺(tái)包中,這樣所有的微服務(wù)都集成了此功能自点。
<!-- spring boot 健康監(jiān)控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2桐罕、確定并引入工程使用的spring-boot-admin-starter-server和spring-boot-admin-starter-client依賴包。
??spring-boot-admin-starter-server是Spring Boot Admin的服務(wù)端樟氢,我們需要新建一個(gè)SpringBoot工程來啟動(dòng)這個(gè)服務(wù)端冈绊,用來接收需要監(jiān)控的服務(wù)注冊,展示監(jiān)控告警信息埠啃。spring-boot-admin-starter-client是客戶端,需要被監(jiān)控的服務(wù)需要引入這個(gè)依賴包伟恶。
??此處請注意: 看到網(wǎng)上很多文章里面寫著添加spring-boot-admin-starter-client包碴开,在SpringCloud微服務(wù)中是不需要引入的,spring-boot-admin-starter-client包僅僅是為了引入我們gitegg-platform平臺(tái)工程的對應(yīng)版本博秫,在gitegg-boot框架中使用潦牛,在SpringCloud微服務(wù)框架中,不需要引入spring-boot-admin-starter-client挡育,SpringBootAdmin會(huì)自動(dòng)根據(jù)微服務(wù)注冊信息查找服務(wù)端點(diǎn)巴碗,官方文檔說明:spring-cloud-discovery-support 。
??在選擇版本時(shí)即寒,一定要找到對應(yīng)SpringBoot版本的Spring Boot Admin橡淆,GitHub上有版本對應(yīng)關(guān)系的說明:
??我們在gitegg-platform-pom中來定義需要引入的spring-boot-admin-starter-server和spring-boot-admin-starter-client依賴包版本召噩,然后在微服務(wù)業(yè)務(wù)開發(fā)中具體引入,這里不做統(tǒng)一引入逸爵,方便微服務(wù)切換監(jiān)控方式具滴。
......
<!-- spring-boot-admin 微服務(wù)監(jiān)控-->
<spring.boot.admin.version>2.3.1</spring.boot.admin.version>
......
<!-- spring-boot-admin監(jiān)控 服務(wù)端 https://mvnrepository.com/artifact/de.codecentric/spring-boot-admin-starter-server -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>${spring.boot.admin.version}</version>
</dependency>
<!-- spring-boot-admin監(jiān)控 客戶端 https://mvnrepository.com/artifact/de.codecentric/spring-boot-admin-starter-client -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>${spring.boot.admin.version}</version>
</dependency>.
......
3、在GitEgg-Cloud項(xiàng)目的gitegg-plugin工程下新建gitegg-admin-monitor工程师倔,用于運(yùn)行spring-boot-admin-starter-server构韵。
- pom.xml中引入需要的依賴包
<dependencies>
<!-- gitegg Spring Boot自定義及擴(kuò)展 -->
<dependency>
<groupId>com.gitegg.platform</groupId>
<artifactId>gitegg-platform-boot</artifactId>
<!-- 去除gitegg-platform-boot默認(rèn)的依賴-->
<exclusions>
<exclusion>
<groupId>com.gitegg.platform</groupId>
<artifactId>gitegg-platform-cache</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- gitegg Spring Cloud自定義及擴(kuò)展 -->
<dependency>
<groupId>com.gitegg.platform</groupId>
<artifactId>gitegg-platform-cloud</artifactId>
</dependency>
<!-- security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<!-- 去除springboot默認(rèn)的logback配置-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
</dependencies>
- 添加spring-boot-admin-starter-server啟動(dòng)類GitEggMonitorApplication.java,添加@EnableAdminServer注解即可趋艘。
@EnableAdminServer
@SpringBootApplication
@RefreshScope
public class GitEggMonitorApplication {
public static void main(String[] args)
{
SpringApplication.run(GitEggMonitorApplication.class, args);
}
}
- 添加SpringSecurity的WebSecurityConfigurerAdapter配置類疲恢,保護(hù)監(jiān)控系統(tǒng)安全。
??這里主要配置登錄頁面瓷胧、靜態(tài)文件冈闭、登錄、退出等的權(quán)限抖单。請注意這里配置了publicUrl的前綴萎攒,當(dāng)部署在微服務(wù)環(huán)境或Docker環(huán)境中需要經(jīng)過gateway或者nginx轉(zhuǎn)發(fā)時(shí),在SpringBootAdmin配置中矛绘,需要配置publicUrl耍休,否則SpringBootAdmin只會(huì)跳轉(zhuǎn)到本機(jī)環(huán)境的地址和端口。publicUrl如果是80端口货矮,那么這個(gè)端口不能省略羊精,需要配置上。
@Configuration(proxyBeanMethods = false)
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
private final AdminServerUiProperties adminUi;
private final AdminServerProperties adminServer;
private final SecurityProperties security;
public SecuritySecureConfig(AdminServerUiProperties adminUi, AdminServerProperties adminServer, SecurityProperties security) {
this.adminUi = adminUi;
this.adminServer = adminServer;
this.security = security;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 當(dāng)設(shè)置了publicUrl時(shí)囚玫,Gateway跳轉(zhuǎn)到login或logout鏈接需要redirect到publicUrl
String publicUrl = this.adminUi.getPublicUrl() != null ? this.adminUi.getPublicUrl() : this.adminServer.getContextPath();
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl(publicUrl + "/");
http.authorizeRequests(
(authorizeRequests) -> authorizeRequests.antMatchers(this.adminServer.path("/assets/**")).permitAll()
.antMatchers(this.adminServer.path("/actuator/info")).permitAll()
.antMatchers(this.adminServer.path("/actuator/health")).permitAll()
.antMatchers(this.adminServer.path("/login")).permitAll().anyRequest().authenticated()
).formLogin(
(formLogin) -> formLogin.loginPage(publicUrl + "/login").loginProcessingUrl(this.adminServer.path("/login")).successHandler(successHandler).and()
).logout((logout) -> logout.logoutUrl(publicUrl + "/logout")).httpBasic(Customizer.withDefaults())
.csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers(
new AntPathRequestMatcher(this.adminServer.path("/instances"),
HttpMethod.POST.toString()),
new AntPathRequestMatcher(this.adminServer.path("/instances/*"),
HttpMethod.DELETE.toString()),
new AntPathRequestMatcher(this.adminServer.path("/actuator/**"))
))
.rememberMe((rememberMe) -> rememberMe.key(UUID.randomUUID().toString()).tokenValiditySeconds(1209600));
}
/**
* Required to provide UserDetailsService for "remember functionality"
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser(security.getUser().getName())
.password("{noop}" + security.getUser().getPassword()).roles(security.getUser().getRoles().toArray(new String[0]));
}
}
4喧锦、在Nacos配置中心配置SpringBootAdmin的相關(guān)配置,在gitegg-admin-monitor工程中抓督,也需要配置讀取配置的相關(guān)yml文件燃少,除了讀取主配置之外,還需要讀取SpringBootAdmin專屬配置铃在。
- 新增gitegg-cloud-config-admin-monitor.yaml配置文件
spring:
boot:
admin:
ui:
brand: <img src="http://img.gitegg.com/cloud/docs/images/logo.png"><span>GitEgg微服務(wù)監(jiān)控系統(tǒng)</span>
title: GitEgg微服務(wù)監(jiān)控系統(tǒng)
favicon: http://img.gitegg.com/cloud/docs/images/logo.png
public-url: http://127.0.0.1:80/gitegg-admin-monitor/monitor
context-path: /monitor
- 在bootstrap.yml中新增讀取gitegg-cloud-config-admin-monitor.yaml的配置
server:
port: 8009
spring:
profiles:
active: '@spring.profiles.active@'
application:
name: '@artifactId@'
cloud:
inetutils:
ignored-interfaces: docker0
nacos:
discovery:
server-addr: ${spring.nacos.addr}
metadata:
# 啟用SpringBootAdmin時(shí) 客戶端端點(diǎn)信息的安全認(rèn)證信息
user.name: ${spring.security.user.name}
user.password: ${spring.security.user.password}
config:
server-addr: ${spring.nacos.addr}
file-extension: yaml
extension-configs:
# 必須帶文件擴(kuò)展名阵具,此時(shí) file-extension 的配置對自定義擴(kuò)展配置的 Data Id 文件擴(kuò)展名沒有影響
- data-id: ${spring.nacos.config.prefix}.yaml
group: ${spring.nacos.config.group}
refresh: true
- data-id: ${spring.nacos.config.prefix}-admin-monitor.yaml
group: ${spring.nacos.config.group}
refresh: true
5、擴(kuò)展gitegg-gateway的SpringSecurity配置定铜,增加統(tǒng)一鑒權(quán)校驗(yàn)阳液。因我們有多個(gè)微服務(wù),且所有的微服務(wù)在生產(chǎn)環(huán)境部署時(shí)都不會(huì)暴露端口揣炕,所以所有的微服務(wù)鑒權(quán)都會(huì)在網(wǎng)關(guān)做帘皿。
??SpringSecurity權(quán)限驗(yàn)證支持多過濾器配置,同時(shí)可配置驗(yàn)證順序畸陡,我們這里需要改造之前的過濾器鹰溜,這里新增Basic認(rèn)證過濾器虽填,通過securityMatcher設(shè)置,只有健康檢查的請求走這個(gè)權(quán)限過濾器奉狈,其他請求繼續(xù)走之前我們設(shè)置的OAuth2+JWT權(quán)限驗(yàn)證器卤唉。
/**
* 權(quán)限配置
* 注解需要使用@EnableWebFluxSecurity而非@EnableWebSecurity,因?yàn)镾pringCloud Gateway基于WebFlux
*
* @author GitEgg
*
*/
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Configuration
@EnableWebFluxSecurity
public 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;
/**
* 健康檢查接口權(quán)限配置
* @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("當(dāng)啟用健康檢查時(shí),不允許健康檢查的路徑為空");
}
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();
}
/**
* 設(shè)置Basic認(rèn)證用戶信息
* @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());
}
/**
* 設(shè)置密碼編碼
* @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;
}
/**
* 路由轉(zhuǎn)發(fā)權(quán)限配置
* @param http
* @return
*/
@Bean
SecurityWebFilterChain apiHttpSecurity(ServerHttpSecurity http) {
http.oauth2ResourceServer().jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
// 自定義處理JWT請求頭過期或簽名錯(cuò)誤的結(jié)果
http.oauth2ResourceServer().authenticationEntryPoint(authServerAuthenticationEntryPoint);
// 對白名單路徑仁期,直接移除JWT請求頭桑驱,不移除的話,后臺(tái)會(huì)校驗(yàn)jwt
http.addFilterBefore(whiteListRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION);
// Basic認(rèn)證直接放行
if (!CollectionUtils.isEmpty(authUrlWhiteListProperties.getTokenUrls()))
{
http.authorizeExchange().pathMatchers(ArrayUtil.toArray(authUrlWhiteListProperties.getTokenUrls(), String.class)).permitAll();
}
// 判斷是否有靜態(tài)文件
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()
/**
* 處理未授權(quán)
*/
.accessDeniedHandler(authServerAccessDeniedHandler)
/**
* 處理未認(rèn)證
*/
.authenticationEntryPoint(authServerAuthenticationEntryPoint)
.and()
.cors()
.and().csrf().disable();
return http.build();
}
/**
* ServerHttpSecurity沒有將jwt中authorities的負(fù)載部分當(dāng)做Authentication跛蛋,需要把jwt的Claim中的authorities加入
* 解決方案:重新定義ReactiveAuthenticationManager權(quán)限管理器熬的,默認(rèn)轉(zhuǎn)換器JwtGrantedAuthoritiesConverter
*/
@Bean
public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME);
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
}
6、在Nacos配置中心赊级,統(tǒng)一配置所有微服務(wù)的健康檢查端點(diǎn)地址押框,權(quán)限校驗(yàn)的用戶名密碼等。
spring:
......
security:
# # 啟用SpringBootAdmin時(shí)理逊,健康檢查權(quán)限校驗(yàn)橡伞,不使用SpringBootAdmin此處可省略
user:
name: user
password: password
roles: ACTUATOR_ADMIN
......
# 性能監(jiān)控端點(diǎn)配置
management:
security:
enabled: true
role: ACTUATOR_ADMIN
endpoint:
health:
show-details: always
endpoints:
enabled-by-default: true
web:
base-path: /actuator
exposure:
include: '*'
server:
servlet:
context-path: /actuator
health:
mail:
enabled: false
......
7、設(shè)置網(wǎng)關(guān)Gateway配置晋被,對gitegg-admin-monitor進(jìn)行過路由和轉(zhuǎn)發(fā)兑徘。
spring:
gateway:
discovery:
locator:
enabled: true
routes:
......
- id: gitegg-admin-monitor
uri: lb://gitegg-admin-monitor
predicates:
- Path=/gitegg-admin-monitor/**
filters:
- StripPrefix=1
- id: monitor
uri: lb://gitegg-admin-monitor
predicates:
- Path=/monitor/**
filters:
- StripPrefix=0
......
8、啟動(dòng)所有的微服務(wù)羡洛,并訪問 http://127.0.0.1/gitegg-admin-monitor/monitor/login 進(jìn)行健康檢查微服務(wù)配置挂脑。
??根據(jù)我們在Nacos中的配置,我們這里的登錄用戶名密碼是:user / password
??以上為SpringBootAdmin在SpringCloud微服務(wù)中的搭建和配置步驟欲侮,相比較而言比較簡單崭闲,但是一定要注意權(quán)限問題,不要因?yàn)榻】禉z查而泄露了系統(tǒng)信息威蕉。我們這里是通過Gateway進(jìn)行的統(tǒng)一鑒權(quán)刁俭,在生產(chǎn)環(huán)境部署時(shí),一定要注意修改默認(rèn)的Basic校驗(yàn)用戶名密碼忘伞,甚至需要修改健康檢查端點(diǎn)薄翅。
GitEgg-Cloud是一款基于SpringCloud整合搭建的企業(yè)級微服務(wù)應(yīng)用開發(fā)框架,開源項(xiàng)目地址:
Gitee: https://gitee.com/wmz1930/GitEgg
GitHub: https://github.com/wmz1930/GitEgg
歡迎感興趣的小伙伴Star支持一下氓奈。