有没有办法扩展 Spring 执行器记录器并从我自己的控制器调用它?
Is there a way to extend the Spring Actuator logger and invoke it from my own controller?
有没有办法扩展 Spring 执行器记录器并从我自己的控制器调用它,以便我可以进行一些安全验证?例如,像这样:
@RestController
public class MyLoggingController {
@Autowired
private ActuatorLogger logger; // not sure what the actual class name is
@PostMapping("/loggers")
public String setLoggeringLevel( @RequestBody String body ) {
// do security validations
// set logging level
logger.setLoggingLevel( ... ); // not sure what the actual method signature is
return response;
}
}
您可以使用 Spring 安全性来保护端点。参见 Securing HTTP Endpoints。
如果Spring安全不是一个选项并且您想要以其他方式控制日志记录,执行器没有提供,可以看看LoggersEndpoint
:
- 它使用
LoggingSystem
/ LoggerGroups
来控制日志记录级别
- 这是更改日志记录级别的代码片段:
@WriteOperation
public void configureLogLevel(@Selector String name, @Nullable LogLevel configuredLevel) {
Assert.notNull(name, "Name must not be empty");
LoggerGroup group = this.loggerGroups.get(name);
if (group != null && group.hasMembers()) {
group.configureLogLevel(configuredLevel, this.loggingSystem::setLogLevel);
return;
}
this.loggingSystem.setLogLevel(name, configuredLevel);
}
我同意@Denis Zavedeev 的观点,保护内部端点的最佳方式是在安全配置器内部,当然如果可能的话。
例如:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/actuator/**");
}
您的主要目标是 class LoggersEndpoint
,正如@Denis Zavedeev 提到的,有设置日志级别的方法
@WriteOperation
public void configureLogLevel(@Selector String name, @Nullable LogLevel configuredLevel) {
Assert.notNull(name, "Name must not be empty");
LoggerGroup group = this.loggerGroups.get(name);
if (group != null && group.hasMembers()) {
group.configureLogLevel(configuredLevel, this.loggingSystem::setLogLevel);
return;
}
this.loggingSystem.setLogLevel(name, configuredLevel);
}
当然你可以自动装配 bean LoggersEndpoint
并调用正确的 write 方法,如果我们看一下自动配置:
@Configuration(proxyBeanMethods = false)
@ConditionalOnAvailableEndpoint(endpoint = LoggersEndpoint.class)
public class LoggersEndpointAutoConfiguration {
@Bean
@ConditionalOnBean(LoggingSystem.class)
@Conditional(OnEnabledLoggingSystemCondition.class)
@ConditionalOnMissingBean
public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem,
ObjectProvider<LoggerGroups> springBootLoggerGroups) {
return new LoggersEndpoint(loggingSystem, springBootLoggerGroups.getIfAvailable(LoggerGroups::new));
}
static class OnEnabledLoggingSystemCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("Logging System");
String loggingSystem = System.getProperty(LoggingSystem.SYSTEM_PROPERTY);
if (LoggingSystem.NONE.equals(loggingSystem)) {
return ConditionOutcome.noMatch(
message.because("system property " + LoggingSystem.SYSTEM_PROPERTY + " is set to none"));
}
return ConditionOutcome.match(message.because("enabled"));
}
}
}
最好利用 Spring 安全语义。
创建一个将具有单一方法的 bean,用于检查对特定身份验证主体的访问:
@Component
public class SetLoggerAccessChecker {
public boolean isAuthorizedToChangeLogs(Authentication authentication, HttpServletRequest request) {
// example custom logic below, implement your own
if (request.getMethod().equals(HttpMethod.POST.name())) {
return ((User) authentication.getPrincipal()).getUsername().equals("admin");
}
return true;
}
}
在 WebSecurityConfigurerAdapter 中注入 bean,并对特定的 ActuatorLoggerEndpoints 使用 access
方法:
@Autowired
private SetLoggerAccessChecker setLoggerAccessChecker;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").httpBasic();
http.csrf().disable().requestMatcher(EndpointRequest.to(LoggersEndpoint.class)).authorizeRequests((requests) -> {
requests.anyRequest().access("@setLoggerAccessChecker.isAuthorizedToChangeLogs(authentication, request)");
});
}
就是这样。
$ http -a user:password localhost:8080/actuator/loggers
// 403
$ http -a admin:password localhost:8080/actuator/loggers
// 200
$ curl --user "admin:password" -i -X POST -H 'Content-Type: application/json' -d '{"configuredLevel": "DEBUG"}' http://localhost:8080/actuator/loggers/com.ikwattro
HTTP/1.1 204
Set-Cookie: JSESSIONID=A013429ADE8B58239EBE385B9DEC524D; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Date: Sat, 02 Jan 2021 22:38:26 GMT
$ curl --user "user:password" -i -X POST -H 'Content-Type: application/json' -d '{"configuredLevel": "DEBUG"}' http://localhost:8080/actuator/loggers/com.ikwattro
HTTP/1.1 403
Set-Cookie: JSESSIONID=2A350627672B6742F5C842D2A3BC1330; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Disposition: inline;filename=f.txt
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sat, 02 Jan 2021 22:41:04 GMT
此处的示例存储库:https://github.com/ikwattro/spring-boot-actuator-custom-security
查看实际实施 here。
您可以根据您的要求更改它。例如:
@Component
public class LoggingConfiguration {
private final LoggingSystem loggingSystem;
private final LoggerGroups loggerGroups;
public LoggingConfiguration(LoggingSystem loggingSystem, LoggerGroups loggerGroups) {
Assert.notNull(loggingSystem, "LoggingSystem must not be null");
Assert.notNull(loggerGroups, "LoggerGroups must not be null");
this.loggingSystem = loggingSystem;
this.loggerGroups = loggerGroups;
}
public void configureLogLevel(String packageName, LogLevel configuredLevel) {
Assert.notNull(packageName, "Name must not be empty");
LoggerGroup group = loggerGroups.get(packageName);
if (group != null && group.hasMembers()) {
group.configureLogLevel(configuredLevel, loggingSystem::setLogLevel);
return;
}
loggingSystem.setLogLevel(packageName, configuredLevel);
}
}
现在您可以根据需要使用 LoggingConfiguration 的实例。
@RestController
public class MyLoggingController {
@Autowired
private LoggingConfiguration loggingConfiguration;
@PostMapping("/loggers")
public String setLoggeringLevel( @RequestBody String body ) {
// do security validations
// set logging level
loggingConfiguration.configureLogLevel( ... );
return response;
}
}
有没有办法扩展 Spring 执行器记录器并从我自己的控制器调用它,以便我可以进行一些安全验证?例如,像这样:
@RestController
public class MyLoggingController {
@Autowired
private ActuatorLogger logger; // not sure what the actual class name is
@PostMapping("/loggers")
public String setLoggeringLevel( @RequestBody String body ) {
// do security validations
// set logging level
logger.setLoggingLevel( ... ); // not sure what the actual method signature is
return response;
}
}
您可以使用 Spring 安全性来保护端点。参见 Securing HTTP Endpoints。
如果Spring安全不是一个选项并且您想要以其他方式控制日志记录,执行器没有提供,可以看看LoggersEndpoint
:
- 它使用
LoggingSystem
/LoggerGroups
来控制日志记录级别
- 这是更改日志记录级别的代码片段:
@WriteOperation public void configureLogLevel(@Selector String name, @Nullable LogLevel configuredLevel) { Assert.notNull(name, "Name must not be empty"); LoggerGroup group = this.loggerGroups.get(name); if (group != null && group.hasMembers()) { group.configureLogLevel(configuredLevel, this.loggingSystem::setLogLevel); return; } this.loggingSystem.setLogLevel(name, configuredLevel); }
我同意@Denis Zavedeev 的观点,保护内部端点的最佳方式是在安全配置器内部,当然如果可能的话。 例如:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/actuator/**");
}
您的主要目标是 class LoggersEndpoint
,正如@Denis Zavedeev 提到的,有设置日志级别的方法
@WriteOperation
public void configureLogLevel(@Selector String name, @Nullable LogLevel configuredLevel) {
Assert.notNull(name, "Name must not be empty");
LoggerGroup group = this.loggerGroups.get(name);
if (group != null && group.hasMembers()) {
group.configureLogLevel(configuredLevel, this.loggingSystem::setLogLevel);
return;
}
this.loggingSystem.setLogLevel(name, configuredLevel);
}
当然你可以自动装配 bean LoggersEndpoint
并调用正确的 write 方法,如果我们看一下自动配置:
@Configuration(proxyBeanMethods = false)
@ConditionalOnAvailableEndpoint(endpoint = LoggersEndpoint.class)
public class LoggersEndpointAutoConfiguration {
@Bean
@ConditionalOnBean(LoggingSystem.class)
@Conditional(OnEnabledLoggingSystemCondition.class)
@ConditionalOnMissingBean
public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem,
ObjectProvider<LoggerGroups> springBootLoggerGroups) {
return new LoggersEndpoint(loggingSystem, springBootLoggerGroups.getIfAvailable(LoggerGroups::new));
}
static class OnEnabledLoggingSystemCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("Logging System");
String loggingSystem = System.getProperty(LoggingSystem.SYSTEM_PROPERTY);
if (LoggingSystem.NONE.equals(loggingSystem)) {
return ConditionOutcome.noMatch(
message.because("system property " + LoggingSystem.SYSTEM_PROPERTY + " is set to none"));
}
return ConditionOutcome.match(message.because("enabled"));
}
}
}
最好利用 Spring 安全语义。
创建一个将具有单一方法的 bean,用于检查对特定身份验证主体的访问:
@Component
public class SetLoggerAccessChecker {
public boolean isAuthorizedToChangeLogs(Authentication authentication, HttpServletRequest request) {
// example custom logic below, implement your own
if (request.getMethod().equals(HttpMethod.POST.name())) {
return ((User) authentication.getPrincipal()).getUsername().equals("admin");
}
return true;
}
}
在 WebSecurityConfigurerAdapter 中注入 bean,并对特定的 ActuatorLoggerEndpoints 使用 access
方法:
@Autowired
private SetLoggerAccessChecker setLoggerAccessChecker;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").httpBasic();
http.csrf().disable().requestMatcher(EndpointRequest.to(LoggersEndpoint.class)).authorizeRequests((requests) -> {
requests.anyRequest().access("@setLoggerAccessChecker.isAuthorizedToChangeLogs(authentication, request)");
});
}
就是这样。
$ http -a user:password localhost:8080/actuator/loggers
// 403
$ http -a admin:password localhost:8080/actuator/loggers
// 200
$ curl --user "admin:password" -i -X POST -H 'Content-Type: application/json' -d '{"configuredLevel": "DEBUG"}' http://localhost:8080/actuator/loggers/com.ikwattro
HTTP/1.1 204
Set-Cookie: JSESSIONID=A013429ADE8B58239EBE385B9DEC524D; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Date: Sat, 02 Jan 2021 22:38:26 GMT
$ curl --user "user:password" -i -X POST -H 'Content-Type: application/json' -d '{"configuredLevel": "DEBUG"}' http://localhost:8080/actuator/loggers/com.ikwattro
HTTP/1.1 403
Set-Cookie: JSESSIONID=2A350627672B6742F5C842D2A3BC1330; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Disposition: inline;filename=f.txt
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sat, 02 Jan 2021 22:41:04 GMT
此处的示例存储库:https://github.com/ikwattro/spring-boot-actuator-custom-security
查看实际实施 here。 您可以根据您的要求更改它。例如:
@Component
public class LoggingConfiguration {
private final LoggingSystem loggingSystem;
private final LoggerGroups loggerGroups;
public LoggingConfiguration(LoggingSystem loggingSystem, LoggerGroups loggerGroups) {
Assert.notNull(loggingSystem, "LoggingSystem must not be null");
Assert.notNull(loggerGroups, "LoggerGroups must not be null");
this.loggingSystem = loggingSystem;
this.loggerGroups = loggerGroups;
}
public void configureLogLevel(String packageName, LogLevel configuredLevel) {
Assert.notNull(packageName, "Name must not be empty");
LoggerGroup group = loggerGroups.get(packageName);
if (group != null && group.hasMembers()) {
group.configureLogLevel(configuredLevel, loggingSystem::setLogLevel);
return;
}
loggingSystem.setLogLevel(packageName, configuredLevel);
}
}
现在您可以根据需要使用 LoggingConfiguration 的实例。
@RestController
public class MyLoggingController {
@Autowired
private LoggingConfiguration loggingConfiguration;
@PostMapping("/loggers")
public String setLoggeringLevel( @RequestBody String body ) {
// do security validations
// set logging level
loggingConfiguration.configureLogLevel( ... );
return response;
}
}