1

有没有办法扩展 Spring Actuator 记录器并从我自己的控制器调用它,以便我可以进行一些安全验证?例如,像这样:

@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;
    }

}
4

4 回答 4

3

最好是利用 Spring Security Semantics。

创建一个 bean,它有一个方法来检查特定 Authentication Principal 的访问:

@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 并使用access特定 ActuatorLoggerEndpoints 的方法:

    @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

于 2021-01-02T20:50:25.557 回答
2

我同意@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);
}

当然LoggersEndpoint,如果我们看一下自动配置,您可以自动装配 bean 并调用正确的 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"));
        }

    }

}
于 2021-01-02T20:42:05.723 回答
2

您可以使用 Spring Security 保护端点。请参阅保护 HTTP 端点


如果 Spring Security不是一个选项,并且您确实想以其他方式控制日志记录,该执行器不提供,您可以查看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);
    }
    
于 2021-01-02T20:16:02.310 回答
1

在这里查看实际的实现。您可以根据自己的要求进行更改。例如:

@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;
    }

}
于 2022-02-09T06:31:30.407 回答