66 lines
3.0 KiB
Java
66 lines
3.0 KiB
Java
package eu.bitfield.recipes.config;
|
|
|
|
import org.springframework.context.annotation.Bean;
|
|
import org.springframework.context.annotation.Configuration;
|
|
import org.springframework.core.Ordered;
|
|
import org.springframework.core.annotation.Order;
|
|
import org.springframework.security.config.Customizer;
|
|
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
|
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
|
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
|
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
|
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
|
|
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
|
|
|
|
import static org.springframework.http.HttpMethod.*;
|
|
|
|
@Configuration
|
|
@EnableWebFluxSecurity
|
|
public class SecurityConfiguration {
|
|
private SecurityWebFilterChain filterChainDefaults(ServerHttpSecurity http) {
|
|
// disable session management
|
|
// https://github.com/spring-projects/spring-security/issues/6552#issuecomment-519398510
|
|
return http.csrf(ServerHttpSecurity.CsrfSpec::disable)
|
|
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
|
|
.build();
|
|
}
|
|
|
|
@Order(Ordered.HIGHEST_PRECEDENCE) @Bean
|
|
public SecurityWebFilterChain accountEndpointFilterChain(ServerHttpSecurity httpSecurity) {
|
|
httpSecurity.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/api/account/**"))
|
|
.authorizeExchange(exchange -> exchange.anyExchange().permitAll());
|
|
return filterChainDefaults(httpSecurity);
|
|
}
|
|
|
|
@Order(Ordered.HIGHEST_PRECEDENCE) @Bean
|
|
public SecurityWebFilterChain recipeEndpointFilterChain(ServerHttpSecurity httpSecurity) {
|
|
httpSecurity.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/api/recipe/**"))
|
|
.httpBasic(Customizer.withDefaults())
|
|
.authorizeExchange(exchange -> {
|
|
exchange.pathMatchers(GET, "/api/recipe/{recipeId:\\d+}").permitAll()
|
|
.pathMatchers(GET, "/api/recipe/search").permitAll()
|
|
.anyExchange().authenticated();
|
|
});
|
|
return filterChainDefaults(httpSecurity);
|
|
}
|
|
|
|
@Bean
|
|
public SecurityWebFilterChain fallbackFilterChain(ServerHttpSecurity httpSecurity) {
|
|
httpSecurity.authorizeExchange(exchange -> exchange.anyExchange().denyAll());
|
|
return filterChainDefaults(httpSecurity);
|
|
}
|
|
|
|
@Bean
|
|
public PasswordEncoder passwordEncoder() {
|
|
// https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
|
|
// accessed at 2025-04-22
|
|
int saltLength = 16;
|
|
int hashLength = 32;
|
|
int parallelism = 1;
|
|
int memory = 1 << 16; // in KiB = 64 MiB
|
|
int iterations = 2;
|
|
return new Argon2PasswordEncoder(saltLength, hashLength, parallelism, memory, iterations);
|
|
}
|
|
}
|