spring boot admin CSRF 解决记录
目标: 实现通过spring boot admin 修改对应进程的日志级别,但不希望裸奔,希望通过用户+密码模式处理
在处理过程中,在应用侧加上了如下代码:
package masktest.server.nacos.spring.security;
import java.util.Arrays;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@Configuration
public class SecurityPermitAllConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().requestMatchers(EndpointRequest.to("loggers", "details"))
.hasRole("ENDPOINT_ADMIN")
.requestMatchers(EndpointRequest.toAnyEndpoint().excluding("loggers", "details")).permitAll().and()
.httpBasic();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
admin web 出现403错误,跟踪发现是在如下地方报错
private Mono<Void> validateToken(ServerWebExchange exchange) {
return this.csrfTokenRepository.loadToken(exchange)
.switchIfEmpty(Mono.defer(() -> Mono.error(new CsrfException("CSRF Token has been associated to this client"))))
.filterWhen(expected -> containsValidCsrfToken(exchange, expected))
.switchIfEmpty(Mono.defer(() -> Mono.error(new CsrfException("Invalid CSRF Token"))))
.then();
}
private Mono<Boolean> containsValidCsrfToken(ServerWebExchange exchange, CsrfToken expected) {
return exchange.getFormData()
.flatMap(data -> Mono.justOrEmpty(data.getFirst(expected.getParameterName())))
.switchIfEmpty(Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst(expected.getHeaderName())))
.switchIfEmpty(tokenFromMultipartData(exchange, expected))
.map(actual -> actual.equals(expected.getToken()));
}
使用CURL模式访问对应链接,出现如下错误:
CSRF Token has been associated to this client
使用一般的csrf解决方案,如https://blog.csdn.net/caplike/article/details/106144789,并不能解决问题,
进一步跟踪发现如下内容:
applicationContext AnnotationConfigReactiveWebServerApplicationContext (id=137)
参考https://github.com/hantsy/spring-reactive-sample/issues/7,创建reactive CSRF解决问题,核心代码如下:
package com.xiehq.springtest.springbootadminserver;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import reactor.core.publisher.Mono;
@EnableWebFluxSecurity
class SecurityConfig {
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
return http.csrf().disable().authorizeExchange().pathMatchers(HttpMethod.GET, "/posts/**")
.permitAll()
.pathMatchers(HttpMethod.DELETE, "/posts/**").hasRole("ADMIN").pathMatchers("/posts/**")
.authenticated()
.anyExchange().permitAll().and().httpBasic()
// .pathMatchers("/users/{user}/**").access(this::currentUserMatchesPath)
.and().build();
}
private Mono<AuthorizationDecision> currentUserMatchesPath(Mono<Authentication> authentication,
AuthorizationContext context) {
return authentication.map(a -> context.getVariables().get("user").equals(a.getName()))
.map(granted -> new AuthorizationDecision(granted));
}
@Bean
public MapReactiveUserDetailsService userDetailsRepository() {
UserDetails rob = User.withDefaultPasswordEncoder().username("test").password("test123").roles("USER").build();
UserDetails admin = User.withDefaultPasswordEncoder().username("admin").password("admin")
.roles("USER", "ADMIN").build();
return new MapReactiveUserDetailsService(rob, admin);
}
}
注意下,可能需要导入starter-tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>```
请先 后发表评论~