All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.yadaframework.security.YadaSecurityConfig Maven / Gradle / Ivy

There is a newer version: 0.7.7.R4
Show newest version
package net.yadaframework.security;

import java.io.IOException;
import java.util.Locale;
import java.util.regex.Pattern;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.annotation.Order;
// import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.YadaCommonsMultipartResolver;

import jakarta.servlet.DispatcherType;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.yadaframework.components.YadaWebUtil;
import net.yadaframework.core.YadaConfiguration;
import net.yadaframework.security.components.YadaAuthenticationFailureHandler;
import net.yadaframework.security.components.YadaAuthenticationSuccessHandler;
import net.yadaframework.security.components.YadaLogoutSuccessHandler;
import net.yadaframework.security.components.YadaUserDetailsService;

/**
 * Basic security configuration.
 * The application paths must be protected in an application-specific subclass of WebSecurityConfigurerAdapter
 */
// @Configuration not needed because set in the subclass?
@EnableWebSecurity
@ComponentScan(basePackages = { "net.yadaframework.security.components", "net.yadaframework.security.persistence.repository" })
@Order(10) // Just in case there will be others
public class YadaSecurityConfig {
//	private Logger log = LoggerFactory.getLogger(YadaSecurityConfig.class);

	@Autowired private YadaUserDetailsService userDetailsService;
	@Autowired private YadaConfiguration yadaConfiguration;
	@Autowired private YadaWebUtil yadaWebUtil;

	@Autowired protected YadaAuthenticationFailureHandler failureHandler;
	@Autowired protected YadaAuthenticationSuccessHandler successHandler;
	@Autowired protected YadaLogoutSuccessHandler logoutSuccessHandler;
	@Autowired protected PasswordEncoder passwordEncoder;

	// This should be used by any @RequestMapping that wants to open the login page/modal
	public final static String DEFAULT_LOGIN_URL = "/login";
	public final static String DEFAULT_LOGIN_URL_AJAX = "/ajaxLogin";
	public final static String DEFAULT_LOGIN_POST = "/loginPost";
	
	// These can be overridden
	protected String loginUrl = DEFAULT_LOGIN_URL;
	protected String loginUrlAjax = DEFAULT_LOGIN_URL_AJAX;
	protected String loginPost = DEFAULT_LOGIN_POST;

	/**
	 * Configures basic security settings. Must be overridden to configure url protections.
	 */
	protected void configure(HttpSecurity http) throws Exception {
		failureHandler.setFailureUrlAjaxRequest(loginUrlAjax);
		failureHandler.setFailureUrlNormalRequest(loginUrl);
		// The "/yadaLoginSuccess" target can be overridden to include the redirect to any target page.
		// See YadaLoginController.yadaLoginSuccess()
		successHandler.setDefaultTargetUrlAjaxRequest("/yadaLoginSuccess"); // Returns the string "success"
		successHandler.setDefaultTargetUrlNormalRequest("/");
		logoutSuccessHandler.setDefaultTargetUrl("/"); // language path will be added in the handler

	    http
	        .headers(headers -> headers.disable())
	        .csrf(csrf -> csrf.disable())
	        .sessionManagement(sessions -> sessions.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
	        .logout(logout -> {
	            logout
	                .logoutUrl("/logout")
	                .logoutSuccessHandler(logoutSuccessHandler);
	            // .logoutSuccessUrl("/") // TODO rimanere nella pagina corrente se non è protetta!
	            // .invalidateHttpSession(false) // Lascio che la session si cancelli quando esco
	        })
	        .formLogin(formLogin -> formLogin
				.loginPage(loginUrl) // url of the login form (GET)
				.loginProcessingUrl(loginPost) // url where the login form is sent (POST)
				.failureHandler(failureHandler)
				.successHandler(successHandler))
	        .exceptionHandling(exceptionHandling -> {
	            // This is needed to redirect to a language-specific login url
	            exceptionHandling.authenticationEntryPoint(new CustomAuthenticationEntryPoint());
	        })
	        .requestCache(requestCache -> {
	            if (yadaConfiguration.isLocalePathVariableEnabled()) {
	                // Resetto la RequestCache in modo che salvi le request di qualunque tipo, anche ajax,
	                // altrimenti il meccanismo del redirect alla pagina di partenza non funziona con le chiamate ajax.
	                requestCache.requestCache(new YadaLocalePathRequestCache());
	            } else {
	                requestCache.requestCache(new HttpSessionRequestCache());
	            }
	        })
	        .authorizeHttpRequests(authorize -> {
	            // Forward requests should never be protected with Spring MVC: https://docs.spring.io/spring-security/reference/5.8/migration/servlet/authorization.html#_permit_forward_when_using_spring_mvc
	            // This is especially the case when using YadaLocalePathVariableFilter.
	            authorize
	                .dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll();
	        });
	}


	/**
	 * Needed to redirect to a language-specific login url when a protected page is requested
	 */
    public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        	boolean ajaxRequest = yadaWebUtil.isAjaxRequest(request);
        	String fixedLoginUrl = ajaxRequest?loginUrlAjax:loginUrl;
        	if (yadaConfiguration.isLocalePathVariableEnabled()) {
        		Locale locale = LocaleContextHolder.getLocale();
        		fixedLoginUrl = yadaWebUtil.enhanceUrl(fixedLoginUrl, locale);
        	}
            response.sendRedirect(fixedLoginUrl);
        }
    }
    
	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		// Uso un PostProcessor per chiamare setHideUserNotFoundExceptions
		auth.userDetailsService(userDetailsService).addObjectPostProcessor(new ObjectPostProcessor() {
			@Override
			public DaoAuthenticationProvider postProcess(DaoAuthenticationProvider processor) {
				processor.setHideUserNotFoundExceptions(false); // Permette alla UsernameNotFoundException di arrivare al FailureHandler
				if (yadaConfiguration.encodePassword()) {
					processor.setPasswordEncoder(passwordEncoder);
				}
				return processor;
			}
		});
	}

	// Non più usato perchè CSRF disabilitato per tutti, visto che causa troppi 403 al timeout di session
	//
	// Questo consente di fare delle richieste ajax in post verso le url indicate, senza incorrere in un 403 Forbidden
	// Veniva usato per /ajaxStoryBunch ma adesso quello usa una GET quindi non serve più per ora.
	private static class MyRequestMatcher implements RequestMatcher {
		private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");
		private AntPathRequestMatcher apiMatcher = new AntPathRequestMatcher("/ajaxStoryBunch*", null);
		// Per usare altri matcher si possono concatenare con AndRequestMatcher e simili
		// Vedi http://docs.spring.io/spring-security/site/docs/3.2.x/apidocs/ per i matcher disponibili
		// private RegexRequestMatcher apiMatcher = new RegexRequestMatcher("/ajax.*", null);

		@Override
		public boolean matches(HttpServletRequest request) {
			// No CSRF due to allowedMethod
			if(allowedMethods.matcher(request.getMethod()).matches()) {
				return false;
			}

			// No CSRF due to api call
			if(apiMatcher.matches(request)) {
				return false;
			}

			// CSRF for everything else that is not an API call or an allowedMethod
			return true;
		}
	}

	@Bean(name="filterMultipartResolver")
	public MultipartResolver multipartResolver() {
		return new YadaCommonsMultipartResolver();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy