Issue
I am trying to add CSRF protection to a project which is based on Spring-Boot (particularly WebFlux).
What I have tried so far is below security configuration.
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.
...
.and()
.csrf()
.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
.requireCsrfProtectionMatcher(
new NegatedServerWebExchangeMatcher(
new OrServerWebExchangeMatcher(
Arrays.asList(
ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, FORM_LOGIN_URL),
ServerWebExchangeMatchers
.pathMatchers(HttpMethod.GET, PERMISSION_ENDPOINT)
)
)
)
)
.accessDeniedHandler(new CustomHttpStatusServerAccessDeniedHandler(HttpStatus.FORBIDDEN))
...;
return http.build();
}
But when I tried above configuration, I could not see anything related to a CSRF token being pushed from server to browser. Is it not by default being pushed to clients? If so what would be the cleaner way to provide CSRF token to client (browser)? In which spring document this is mentioned. (I followed this one -> https://docs.spring.io/spring-security/site/docs/3.2.0.CI-SNAPSHOT/reference/html/csrf.html)
P.S. (hoping these following information are of any use for an answer)
- My login form is not being served by Spring Boot Webflux application. It is served by a angular application running on a different domain (different IP & Port). That is why I have a
FORM_LOGIN_URL
constant in above configuration, which is used as.loginPage(FORM_LOGIN_URL)
. I have customized the.authenticationSuccessHandler()
to redirect toPERMISSION_ENDPOINT
which worked as intended up until.
Solution
This part is a bit poorly documented, but apparently the csrf-token wont be saved unless you subscribe to the result.
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
final CookieServerCsrfTokenRepository cookieServerCsrfTokenRepository = new CookieServerCsrfTokenRepository();
cookieServerCsrfTokenRepository.setCookieHttpOnly(false);
http.csrf()
.csrfTokenRepository(cookieServerCsrfTokenRepository)
.and()
.cors()
...
}
// Add a tiny webfilter that just subscribes
@Bean
public WebFilter addCsrfToken() {
return (exchange, next) -> exchange.<Mono<CsrfToken>>getAttribute(CsrfToken.class.getName())
.doOnSuccess(token -> {}) // do nothing, just subscribe :/
.then(next.filter(exchange));
}
the above solution works well if you are using functional endpoints. You can also use a @ControllerAdvice
approach.
@ControllerAdvice
public class SecurityControllerAdvice {@ModelAttribute Mono<CsrfToken> csrfToken(ServerWebExchange exchange) {
Mono<CsrfToken> csrfToken = exchange.getAttribute(CsrfToken.class.getName());
return csrfToken.doOnSuccess(token -> exchange.getAttributes()
.put(CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME, token));
}
}
There is an open bug report for this on github
CookieServerCsrfTokenRepository does not add cookie
Answered By - Toerktumlare
Answer Checked By - Clifford M. (JavaFixing Volunteer)