公司有個SpringBoot項目需要加個監控,網上找了下發現大家都在推薦SpringBootAdmin。SpringBoot Admin是開源社區孵化的項目,用於對SpringBoot應用的管理和監控。SpringBoot Admin 分為服務端(spring-boot-admin-server)和客戶端(spring-boot-admin-client),服務端和客戶端之間采用http通訊方式實現數據交互;單體項目中需要整合spring-boot-admin-client才能讓應用被監控。在SpringCloud項目中,spring-boot-admin-server 是直接從注冊中心抓取應用信息,不需要每個微服務應用整合spring-boot-admin-client就可以實現應用的管理和監控。
本文只敘述SpringBoot Admin 管理和監控單體應用 ,不涉及SpringCloud相關的內容 。
一、快速入門
1.1 SpringBoot Admin服務端的搭建
(1) Maven依賴說明
SpringBoot版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
6
1
<parent>
2
<groupId>org.springframework.boot</groupId>
3
<artifactId>spring-boot-starter-parent</artifactId>
4
<version>2.2.10.RELEASE</version>
5
<relativePath/> <!-- lookup parent from repository -->
6
</parent>
添加SpringBootAdmin server依賴及SpringBoot web 依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--這里由於我的springboot版本是2.2.10.RELEASE,所以 springboot admin 也要用2.2.x版-->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.2.4</version>
</dependency>
1
<dependency>
2
<groupId>org.springframework.boot</groupId>
3
<artifactId>spring-boot-starter-web</artifactId>
4
</dependency>
5
6
<!--這里由於我的springboot版本是2.2.10.RELEASE,所以 springboot admin 也要用2.2.x版-->
7
<dependency>
8
<groupId>de.codecentric</groupId>
9
<artifactId>spring-boot-admin-starter-server</artifactId>
10
<version>2.2.4</version>
11
</dependency>
(2)application.yml中配置端口
# 指定端口
server:
port: 23333
1
# 指定端口
2
server
3
port23333
(3)編寫啟動類並開啟SpringBootAdminServer
package com.zcode.monitor.server;
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* AdminServerApplication
* @author ZENG.XIAO.YAN
* @version 1.0
* @Date 2020-11-12
*/
@EnableAdminServer // 開啟 springboot admin 服務端
@SpringBootApplication
public class AdminServerApplication {
public static void main(String[] args) {
SpringApplication.run(AdminServerApplication.class,args);
}
}
1
package com.zcode.monitor.server;
2
import de.codecentric.boot.admin.server.config.EnableAdminServer;
3
import org.springframework.boot.SpringApplication;
4
import org.springframework.boot.autoconfigure.SpringBootApplication;
5
/**
6
* AdminServerApplication
7
* @author ZENG.XIAO.YAN
8
* @version 1.0
9
* @Date 2020-11-12
10
*/
11
// 開啟 springboot admin 服務端
12
13
public class AdminServerApplication {
14
15
public static void main(String[] args) {
16
SpringApplication.run(AdminServerApplication.class,args);
17
}
18
}
(4)瀏覽器訪問測試

1.2 SpringBootAdmin client端搭建
備注:所謂的 client端就是指我們需要被監控的應用端。這里我們寫一個簡單點的SpringBoot web應用做演示
(1)Maven依賴說明
SpringBoot版本如下
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
1
<parent>
2
<groupId>org.springframework.boot</groupId>
3
<artifactId>spring-boot-starter-parent</artifactId>
4
<version>2.2.10.RELEASE</version>
5
<relativePath/> <!-- lookup parent from repository -->
6
</parent>
添加SpringBootAdmin client 依賴及SpringBoot web 依賴。這里不需要添加SpringBoot actuator 依賴,因為SpringBootAdmin client里面已經包含了actuator相關依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--這里由於我的springboot版本是2.2.10.RELEASE,所以 springboot admin 也要用2.2.x版-->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.2.4</version>
</dependency>
1
<dependency>
2
<groupId>org.springframework.boot</groupId>
3
<artifactId>spring-boot-starter-web</artifactId>
4
</dependency>
5
6
<!--這里由於我的springboot版本是2.2.10.RELEASE,所以 springboot admin 也要用2.2.x版-->
7
<dependency>
8
<groupId>de.codecentric</groupId>
9
<artifactId>spring-boot-admin-starter-client</artifactId>
10
<version>2.2.4</version>
11
</dependency>
(2) application.yml配置
在yml中需要 配置如下信息:
- 應用端口
- 開放端點用於SpringBootAdmin 監控
- 配置應用名稱(該名稱會在SpringBoot Admin的管理頁面顯示)
- 配置Admin Server的地址
- 配置下日志文件的文件名和存放位置 (如果不配置則會看不到日志)
# 端口
server:
port: 9088
#開放端點用於SpringBoot Admin的監控
management:
endpoints:
web:
exposure:
include: '*'
spring:
application:
name: admin-client # 給client應用取個名字
boot:
admin:
client:
url: http://localhost:23333 #這里配置admin server 的地址
logging:
file:
name: admin-client.log #配置生成日志文件名稱
1
# 端口
2
server
3
port9088
4
5
6
#開放端點用於SpringBoot Admin的監控
7
management
8
endpoints
9
web
10
exposure
11
include'*'
12
13
spring
14
application
15
name admin-client # 給client應用取個名字
16
17
boot
18
admin
19
client
20
url http //localhost 23333 #這里配置admin server 的地址
21
22
logging
23
file
24
name admin-client.log #配置生成日志文件名稱
(3)寫一個Controller模擬一個普通的接口
通過瀏覽器訪問這個接口就會打印日志,具體代碼如下
/**
* HelloController
*
* @author ZENG.XIAO.YAN
* @version 1.0
* @Date 2020-11-16
*/
@Slf4j
@RestController
@RequestMapping("api")
public class HelloController {
private AtomicInteger count = new AtomicInteger(0);
@GetMapping("hi")
private String sayHi() {
// 每次進來如打印下日志
log.info("{} 啪...我第{}次進來了.", LocalDateTime.now(), count.addAndGet(1));
// 每次進來new 個大對象,便於監控觀察堆內存變化
byte[] bytes = new byte[100*1024*1024];
log.info("new了 100MB");
return "hi springboot addmin " + LocalDateTime.now();
}
}
23
23
1
/**
2
* HelloController
3
*
4
* @author ZENG.XIAO.YAN
5
* @version 1.0
6
* @Date 2020-11-16
7
*/
8
9
10
"api") (
11
public class HelloController {
12
private AtomicInteger count = new AtomicInteger(0);
13
14
"hi") (
15
private String sayHi() {
16
// 每次進來如打印下日志
17
log.info("{} 啪...我第{}次進來了.", LocalDateTime.now(), count.addAndGet(1));
18
// 每次進來new 個大對象,便於監控觀察堆內存變化
19
byte[] bytes = new byte[100*1024*1024];
20
log.info("new了 100MB");
21
return "hi springboot addmin " + LocalDateTime.now();
22
}
23
}
(4)寫個啟動類
啟動類代碼就很簡單了,就是一個普通的SpringBoot項目的啟動類,上面沒加其他注解。具體如下
@SpringBootApplication
public class AdminClientApplication {
public static void main(String[] args) {
SpringApplication.run(AdminClientApplication.class, args);
}
}
1
2
public class AdminClientApplication {
3
4
public static void main(String[] args) {
5
SpringApplication.run(AdminClientApplication.class, args);
6
}
7
}
8
1.3 效果展示
(1)已管理的應用會在應用牆上展示
當我們的admin-client項目啟動后,在 admin-server的管理頁面的應用牆上就能看到admin-client這個應用了,具體可參考下圖

(2)可查看應用的具體信息
在應用牆點擊這個應用,我們可以看到這個應用的具體信息,如堆內存變化及線程數等。具體可參考下圖

(3)日志查看及堆內存變化觀察
請求多次后在網頁上可以實時的看到日志如下圖

由於我們直接new了100MB的大對象,此時可以查看細節中的堆內存變化;具體如下圖

二、安全性
2.1 admin-server端安全加固
這個SpringBoot Admin的管理后台如果沒密碼就能訪問,那實在太不安全了,因此我們要給它加上登錄的功能。
參考SpringBoot Admin的官方文檔,我們可以在Admin-Server端添加Spring Security 相關依賴及就可以實現需要登錄后才能訪問網頁管理面板。
下面開始具體的改造
(1)admin-server 添加Spring Security 相關依賴
<!--springboot admin 安全相關-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
5
1
<!--springboot admin 安全相關-->
2
<dependency>
3
<groupId>org.springframework.boot</groupId>
4
<artifactId>spring-boot-starter-security</artifactId>
5
</dependency>
(2)admin-server 設置賬號和密碼
在application.yml配置賬號和密碼
# 配置一個賬號和密碼
spring:
security:
user:
name: admin
password: root123456
6
1
# 配置一個賬號和密碼
2
spring
3
security
4
user
5
name admin
6
password root123456
(3)admin-server 添加一個Spring Security 配置類
@Configuration
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
private final String adminContextPath;
public SecuritySecureConfig(AdminServerProperties adminServerProperties) {
this.adminContextPath = adminServerProperties.getContextPath();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl(adminContextPath + "/");
http.authorizeRequests()
//1.配置所有靜態資源和登錄頁可以公開訪問
.antMatchers(adminContextPath + "/assets/**").permitAll()
.antMatchers(adminContextPath + "/login").permitAll()
.anyRequest().authenticated()
.and()
//2.配置登錄和登出路徑
.formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).and()
.logout().logoutUrl(adminContextPath + "/logout").and()
//3.開啟http basic支持,admin-client注冊時需要使用
.httpBasic().and()
.csrf()
//4.開啟基於cookie的csrf保護
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
//5.忽略這些路徑的csrf保護以便admin-client注冊
.ignoringAntMatchers(
adminContextPath + "/instances",
adminContextPath + "/actuator/**"
);
}
}
34
1
2
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
3
private final String adminContextPath;
4
5
public SecuritySecureConfig(AdminServerProperties adminServerProperties) {
6
this.adminContextPath = adminServerProperties.getContextPath();
7
}
8
9
10
protected void configure(HttpSecurity http) throws Exception {
11
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
12
successHandler.setTargetUrlParameter("redirectTo");
13
successHandler.setDefaultTargetUrl(adminContextPath + "/");
14
http.authorizeRequests()
15
//1.配置所有靜態資源和登錄頁可以公開訪問
16
.antMatchers(adminContextPath + "/assets/**").permitAll()
17
.antMatchers(adminContextPath + "/login").permitAll()
18
.anyRequest().authenticated()
19
.and()
20
//2.配置登錄和登出路徑
21
.formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).and()
22
.logout().logoutUrl(adminContextPath + "/logout").and()
23
//3.開啟http basic支持,admin-client注冊時需要使用
24
.httpBasic().and()
25
.csrf()
26
//4.開啟基於cookie的csrf保護
27
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
28
//5.忽略這些路徑的csrf保護以便admin-client注冊
29
.ignoringAntMatchers(
30
adminContextPath + "/instances",
31
adminContextPath + "/actuator/**"
32
);
33
}
34
}
(4)admin-server 安全加固后訪問測試

當我們輸入正確的賬號密碼登錄后,情況如下圖

這個時候的應用數居然變成了0了,在我們沒進行安全加固時是有一個admin-client應用的,為什么就不見了?
原因是添加了賬號密碼認證后,admin-client端也需要配置下 admin-server的賬號和密碼。
(5)admin-client 端設置 admin-server的賬號密碼
admin-client 注冊到 admin-server時,admin-server端有個http Basic認證,通過了認證后 admin-client才能注冊到 admin-server上。
admin-client的application.yml中配置訪問密碼配置可參考下面代碼
spring:
application:
name: admin-client # 給client應用取個名字
boot:
admin:
client:
url: http://localhost:23333 #這里配置admin server 的地址
# 配置 admin-server的賬號和密碼
username: admin
password: root123456
11
1
spring
2
application
3
name admin-client # 給client應用取個名字
4
5
boot
6
admin
7
client
8
url http //localhost 23333 #這里配置admin server 的地址
9
# 配置 admin-server的賬號和密碼
10
username admin
11
password root123456
(6) 再次訪問 admin-server 管理后台
當我們登錄后,終於再次看到了我們的admin-client這個應用

2.2 admin-client端的安全
admin-client端如果把actuator 端點都暴露出來,是非常不安全的。因此我們可以添加Spring Security對admin-client 也進行安全加固。
下面所有操作均在admin-client中進行
(1)添加Spring Security依賴
<!--spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
5
1
<!--spring security-->
2
<dependency>
3
<groupId>org.springframework.boot</groupId>
4
<artifactId>spring-boot-starter-security</artifactId>
5
</dependency>
(2)yml配置
yml中需要設置client的賬號和密碼,官網相關配置如下圖所示

本次演示的admin-client的相關yml配置參考下面代碼
spring:
application:
name: admin-client # 給client應用取個名字
boot:
admin:
client:
url: http://localhost:23333 #這里配置admin server 的地址
# 配置 admin-server的賬號和密碼
username: admin
password: root123456
instance:
metadata:
# 這里配置admin-client的賬號和密碼
user.name: ${spring.security.user.name}
user.password: ${spring.security.user.password}
# admin-client 的用戶名和密碼
security:
user:
name: clientAdmin
password: 123456
22
1
spring
2
application
3
name admin-client # 給client應用取個名字
4
5
boot
6
admin
7
client
8
url http //localhost 23333 #這里配置admin server 的地址
9
# 配置 admin-server的賬號和密碼
10
username admin
11
password root123456
12
instance
13
metadata
14
# 這里配置admin-client的賬號和密碼
15
user.name $ spring.security.user.name
16
user.password $ spring.security.user.password
17
18
# admin-client 的用戶名和密碼
19
security
20
user
21
name clientAdmin
22
password123456
(3)添加Spring Security 配置類
為何要到配置?因為Spring Security不配置時會把所有請求都攔截的,而我們這里只需要攔截監控端點/actuator/**即可。同時,官網中提到admin-server訪問admin-client時,也是采用http Basic認證方式的;因此需要配置Spring Security支持Http Basic認證方式。
@Configuration
@Slf4j
public class SpringSecurityActuatorConfig extends WebSecurityConfigurerAdapter {
public SpringSecurityActuatorConfig() {
log.info("SpringSecurityActuatorConfig... start");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 這個配置只針對 /actuator/** 的請求生效
http.antMatcher("/actuator/**")
// /actuator/下所有請求都要認證
.authorizeRequests().anyRequest().authenticated()
// 啟用httpBasic認證模式,當springboot admin-client 配置了密碼時,
// admin-server走httpbasic的認證方式來拉取client的信息
.and().httpBasic()
// 禁用csrf
.and().csrf().disable();
}
}
22
22
1
2
3
public class SpringSecurityActuatorConfig extends WebSecurityConfigurerAdapter {
4
5
public SpringSecurityActuatorConfig() {
6
log.info("SpringSecurityActuatorConfig... start");
7
}
8
9
10
protected void configure(HttpSecurity http) throws Exception {
11
// 這個配置只針對 /actuator/** 的請求生效
12
http.antMatcher("/actuator/**")
13
// /actuator/下所有請求都要認證
14
.authorizeRequests().anyRequest().authenticated()
15
// 啟用httpBasic認證模式,當springboot admin-client 配置了密碼時,
16
// admin-server走httpbasic的認證方式來拉取client的信息
17
.and().httpBasic()
18
// 禁用csrf
19
.and().csrf().disable();
20
21
}
22
}
(4)效果展示
admin-server端依舊能看到admin-client的信息,說明我們添加SpringSecurity 后 admin-server的監控管理功能正常,具體見下圖

當我們去訪問admin-client的監控端點
http://localhost:9088/actuator/health
時,發現需要進行http Basic認證;這也證明了我們的認證攔截只攔截了監控端點。效果如下圖

(5)存在的問題
通過上面的一通配置,admin-client 添加 Spring Security 對actuator的端點進行安全認證的功能是實現了,但也存在着問題。
當我們項目本來就是使用SpringSecurity 安全框架進行認證和授權時。上述的配置就要做修改了。因為我們一般都不用HttpBasic認證,而是用的表單登錄認證。也就出現了配置多個Spring Security的問題。雖然有這個問題,但是網上還是有解決方案的。
(6)多個Spring Security共存方案
這個方案是在Spring Security官方文檔里面找到的
官網關鍵信息截圖如下:

里面的重點就是通過添加Order注解來指定多個Spring Security的優先級
下面直接貼上我的代碼;為了直觀,我就在同一個類里面建了2個靜態的Spring Security配置類
/**
* SpringSecurity 表單和HttpBasic 共存配置參考,寫在一個類里面方便對比
* @author ZENG.XIAO.YAN
* @Date 2020-11-11
* @version 1.0
*/
@Slf4j
public class SpringSecurityConfig2 {
/*
* 這個表單和HttpBasic 共存配置玩法,參考url如下:
* 官方url:https://docs.spring.io/spring-security/site/docs/4.2.3.BUILD-SNAPSHOT/reference/htmlsingle/#multiple-httpsecurity
* 項目啟動日志如下,可以看到創建了2條過濾鏈
* 2020-11-11 22:57:56.340 INFO 12692 --- [main] o.s.s.web.DefaultSecurityFilterChain: Creating filter chain: Ant [pattern='/actuator/**'],
* 2020-11-11 22:57:56.344 INFO 12692 --- [main] o.s.s.web.DefaultSecurityFilterChain: Creating filter chain: any request,
*/
/**
* HttpBasic 認證方式,只對/actuator/** 生效,由於設置了Order,優先級會高於FormLoginWebSecurityConfigurerAdapter
* @author ZENG.XIAO.YAN
* @Date 2020-11-11
* @version 1.0
*/
@Configuration
@Order(1)
public static class HttpBasicSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
public HttpBasicSecurityConfigurationAdapter() {
log.info("HttpBasicSecurityConfigurationAdapter... start");
}
protected void configure(HttpSecurity http) throws Exception {
// 這個配置只針對 /actuator/** 的請求生效
http.antMatcher("/actuator/**")
// /actuator/下所有請求都要認證
.authorizeRequests().anyRequest().authenticated()
// 啟用httpBasic認證模式,當springboot admin-client 配置了密碼時,
// admin-server走httpbasic的認證方式來拉取client的信息
.and().httpBasic()
// 禁用csrf
.and().csrf().disable();
}
}
/**
* 表單登錄認證方式配置,由於沒有指定Order,所以默認是最大2147483647,數值越大,優先級越低
* @author ZENG.XIAO.YAN
* @Date 2020-11-11
* @version 1.0
*/
@Configuration
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
public FormLoginWebSecurityConfigurerAdapter() {
log.info("FormLoginWebSecurityConfigurerAdapter... start");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
}
68
68
1
/**
2
* SpringSecurity 表單和HttpBasic 共存配置參考,寫在一個類里面方便對比
3
* @author ZENG.XIAO.YAN
4
* @Date 2020-11-11
5
* @version 1.0
6
*/
7
8
public class SpringSecurityConfig2 {
9
10
/*
11
* 這個表單和HttpBasic 共存配置玩法,參考url如下:
12
* 官方url:https://docs.spring.io/spring-security/site/docs/4.2.3.BUILD-SNAPSHOT/reference/htmlsingle/#multiple-httpsecurity
13
* 項目啟動日志如下,可以看到創建了2條過濾鏈
14
* 2020-11-11 22:57:56.340 INFO 12692 --- [main] o.s.s.web.DefaultSecurityFilterChain: Creating filter chain: Ant [pattern='/actuator/**'],
15
* 2020-11-11 22:57:56.344 INFO 12692 --- [main] o.s.s.web.DefaultSecurityFilterChain: Creating filter chain: any request,
16
*/
17
18
19
/**
20
* HttpBasic 認證方式,只對/actuator/** 生效,由於設置了Order,優先級會高於FormLoginWebSecurityConfigurerAdapter
21
* @author ZENG.XIAO.YAN
22
* @Date 2020-11-11
23
* @version 1.0
24
*/
25
26
1) (
27
public static class HttpBasicSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
28
29
public HttpBasicSecurityConfigurationAdapter() {
30
log.info("HttpBasicSecurityConfigurationAdapter... start");
31
}
32
33
protected void configure(HttpSecurity http) throws Exception {
34
// 這個配置只針對 /actuator/** 的請求生效
35
http.antMatcher("/actuator/**")
36
// /actuator/下所有請求都要認證
37
.authorizeRequests().anyRequest().authenticated()
38
// 啟用httpBasic認證模式,當springboot admin-client 配置了密碼時,
39
// admin-server走httpbasic的認證方式來拉取client的信息
40
.and().httpBasic()
41
// 禁用csrf
42
.and().csrf().disable();
43
}
44
}
45
46
/**
47
* 表單登錄認證方式配置,由於沒有指定Order,所以默認是最大2147483647,數值越大,優先級越低
48
* @author ZENG.XIAO.YAN
49
* @Date 2020-11-11
50
* @version 1.0
51
*/
52
53
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
54
55
public FormLoginWebSecurityConfigurerAdapter() {
56
log.info("FormLoginWebSecurityConfigurerAdapter... start");
57
}
58
59
60
protected void configure(HttpSecurity http) throws Exception {
61
http.authorizeRequests()
62
.anyRequest().authenticated()
63
.and()
64
.formLogin();
65
}
66
}
67
68
}
添加完這個配置類后,記得把我們上面配置的SpringSecurityActuatorConfig 這個類刪除了,然后重啟項目。效果如下:


訪問admin-server 的管理頁面,發現admin-client應用信息正常,說明本次修改的Spring Security配置沒有問題

三、小結
(1)本文介紹了SpringBoot Admin的簡單使用,同時介紹了admin-server端的安全配置和admin-client端的安全配置
(2)在介紹admin-client端的安全配置時,引申出了 如何實現多個SpringSecurity 配置 共存