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>```



举报
评论 0