package com.mousetech.gourmetj; import java.io.File; import java.io.FileReader; import java.io.LineNumberReader; import java.util.Arrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer; import org.springframework.security.config.annotation.authentication.configurers.provisioning.UserDetailsManagerConfigurer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; import jakarta.servlet.DispatcherType; @Configuration @EnableWebSecurity public class SpringSecurityConfig { //extends WebSecurityConfigurerAdapter { /* Logger */ private static final Logger log = LoggerFactory.getLogger(SpringSecurityConfig.class); @Value("${gourmet.password.file}") private String passwordFile; /** * Load in config file with in-memory credentials. * For practical use, this should probably be * replace with JdbcUserDetailsManager and credentials in * the recipe database. * * @param auth Builder for the authenticator * @throws Exception */ @Autowired public void configureGlobal( AuthenticationManagerBuilder auth) throws Exception { File pwFile = new File(passwordFile); if (!pwFile.canRead()) { String msg = "Password file '" + pwFile.getAbsolutePath() + "' could not be found or read."; log.error(msg); throw new RuntimeException(msg); } LineNumberReader rdr = new LineNumberReader(new FileReader(pwFile)); String pwLine; InMemoryUserDetailsManagerConfigurer authenticator = auth.inMemoryAuthentication(); while ((pwLine = rdr.readLine()) != null) { pwLine = pwLine.trim(); if ((pwLine.length() == 0) || (pwLine.charAt(0) == '#')) { continue; } String[] creds = parseCreds(pwLine); UserDetailsManagerConfigurer> .UserDetailsBuilder bar = authenticator.withUser(creds[0]) .password("{noop}" + creds[1]); int credlen = creds.length; for (int i = 2; i < credlen; i++) { bar.roles(creds[i]); } } rdr.close(); } /** * Parse CSV credential/roles line. Element 1 is userid, * element 2 is password, following element(s) are role(s) * * @param pwLine * @return Credentials array following CSV values, trimmed */ private String[] parseCreds(String pwLine) { String[] creds = pwLine.split(","); String[] ocreds = Arrays.stream(creds).map(e -> e.trim()) .toArray(String[]::new); return ocreds; } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .cors(AbstractHttpConfigurer::disable) .formLogin(Customizer.withDefaults()) .authorizeHttpRequests((authorize)-> authorize .dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll() .anyRequest().authenticated() ); return http.build(); } /** * Replaces old antMatchers for determining secured URLs. * @return customizer */ @Bean public WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web.ignoring().requestMatchers( "/javax.faces.resource/**", "/", "/index.jsf", "/index.xhtml", "/main.jsf", "/img/**", "/recipeDetails.jsf", "/shoppingList.jsf", "/recipePrint.jsf"); } }