Reputation: 41
I have a project with WebFlux, Health actuator, and Spring security. I am trying to build custom authentication, but that authentication kicks in on health actuator endpoints as well. How can I disable it?
According to docs I implemented a custom ServerSecurityContextRepository
, here is basic version of what it looks somewhat like:
@Component
class MySecurityContextRepository: ServerSecurityContextRepository {
override fun save(exchange: ServerWebExchange?, context: SecurityContext?) = Mono.empty()
override fun load(exchange: ServerWebExchange) = Mono.error(ResponseStatusException(HttpStatus.UNAUTHORIZED, "Access denied"))
}
According to documentation I should not be required to do any additional configuration in order to disable auth on health endpoints. Here is configuration from application.yml
:
management:
metrics:
web:
server:
auto-time-requests: true
requests-metric-name: xxx-xxx-xxx
export:
statsd:
enabled: xxxx
host: xxxxxxxxxxxx
flavor: xxx
endpoint:
health:
enabled: true
endpoints:
web:
base-path: /application
That did not work as I saw 401 from /application/health
endpoint. So I also added it to my security configuration:
@EnableWebFluxSecurity
class SecurityConfig @Autowired constructor(
private val myRepository: MySecurityContextRepository
) {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http.securityContextRepository(myRepository)
http.authorizeExchange()
.pathMatchers("/application/health").permitAll()
.anyExchange().permitAll()
http.cors().disable()
http.csrf().disable()
http.formLogin().disable()
return http.build()
}
}
Despite adding this doing curl http://localhost:8080/application/health/
results in {"name":"xxxxxx","message":"Unknown Error","response":"401 UNAUTHORIZED \"Access denied\""}
and status code is 401
as well. How can disable authorization for my health endpoint?
Upvotes: 1
Views: 5771
Reputation: 41
It is best to use EndpointRequest to match actuator end-points as it will match your configuration. Use the reactive import (not servlet).
application.yml :
management:
endpoint:
health:
enabled: true
endpoints:
web:
base-path: /application
WebSecurityConfig.java:
@Configuration
@EnableWebFluxSecurity
public class WebSecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.csrf().disable()
.authorizeExchange()
.matchers(EndpointRequest.to("health")).permitAll()
.and().build();
}
}
Upvotes: 4
Reputation: 275
Regarding spring docu 29.2 WebFlux Security you need just to add:
import org.springframework.boot.autoconfigure.security.reactive.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration
public class WebFluxSecurityConfigurer {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/status/**")
.permitAll().and()
.build();
}
}
Don't forget to add all possible paths like ../health and ../health/ or even better with wildcards
Upvotes: 0
Reputation: 41
So after no help I started looking at the source code and turns out ServerSecurityContextRepository
is always invoked for HealthEndpoint
and InfoEndpoint
so I added the logic to skip the authentication check in repository myself. You can easily do:
val HEALTH_ENDPOINTS = EndpointRequest.to(HealthEndpoint::class.java, InfoEndpoint::class.java)
Now you can do something like:
return HEALTH_ENDPOINTS.matches(exchange).flatMap { matches ->
if (matches.isMatch) {
Mono.just(SecurityContextImpl(GuestAuthToken))
} else {
Mono.empty()
}
}
That way I am not hardcoding any paths in my repository. It's not ideal but gets the job done.
Upvotes: 3