mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 00:48:49 +00:00
implementado el soft-delete
This commit is contained in:
@ -1,10 +1,16 @@
|
||||
package com.imprimelibros.erp.config;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
@ -15,11 +21,19 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
|
||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
||||
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
import com.imprimelibros.erp.users.User;
|
||||
import com.imprimelibros.erp.users.UserDao;
|
||||
import com.imprimelibros.erp.users.UserDetailsImpl;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
@Configuration
|
||||
public class SecurityConfig {
|
||||
|
||||
@ -32,12 +46,16 @@ public class SecurityConfig {
|
||||
// ========== Beans base ==========
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService(UserDao repo) {
|
||||
return username -> {
|
||||
User u = repo.findByUserNameAndEnabledTrue(username);
|
||||
if (u == null) throw new UsernameNotFoundException("No existe: " + username);
|
||||
return new UserDetailsImpl(u);
|
||||
};
|
||||
public UserDetailsService userDetailsService(UserDao repo, MessageSource messages) {
|
||||
return username -> repo
|
||||
.findByUserNameIgnoreCaseAndEnabledTrueAndDeletedFalse(username) // <-- @EntityGraph aplicado
|
||||
.map(UserDetailsImpl::new) // dentro del ctor precalculamos authorities
|
||||
.orElseThrow(() -> {
|
||||
var locale = org.springframework.context.i18n.LocaleContextHolder.getLocale();
|
||||
String msg = messages.getMessage("usuarios.error.no-activo", null,
|
||||
"Usuario no activo o no existe", locale);
|
||||
return new org.springframework.security.core.userdetails.UsernameNotFoundException(msg);
|
||||
});
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -45,84 +63,131 @@ public class SecurityConfig {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
// Remember-me (tabla persistent_logins)
|
||||
@Bean
|
||||
public PersistentTokenRepository persistentTokenRepository() {
|
||||
JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
|
||||
repo.setDataSource(dataSource);
|
||||
// repo.setCreateTableOnStartup(true); // solo 1ª vez si necesitas crear la
|
||||
// tabla
|
||||
return repo;
|
||||
}
|
||||
|
||||
// Provider que soporta UsernamePasswordAuthenticationToken
|
||||
@Bean
|
||||
public AuthenticationProvider daoAuthenticationProvider(
|
||||
UserDetailsService userDetailsService,
|
||||
PasswordEncoder passwordEncoder) {
|
||||
|
||||
DaoAuthenticationProvider p = new DaoAuthenticationProvider();
|
||||
p.setUserDetailsService(userDetailsService);
|
||||
p.setPasswordEncoder(passwordEncoder);
|
||||
return p;
|
||||
// ✅ constructor recomendado (sin deprecations)
|
||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(userDetailsService);
|
||||
provider.setPasswordEncoder(passwordEncoder); // este setter NO está deprecado
|
||||
return provider;
|
||||
}
|
||||
|
||||
// Remember-me (tabla persistent_logins)
|
||||
@Bean
|
||||
public PersistentTokenRepository persistentTokenRepository() {
|
||||
JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
|
||||
repo.setDataSource(dataSource);
|
||||
// repo.setCreateTableOnStartup(true); // solo 1ª vez si necesitas crear la tabla
|
||||
return repo;
|
||||
// Provider que soporta UsernamePasswordAuthenticationToken
|
||||
private static RequestMatcher pathStartsWith(String... prefixes) {
|
||||
return new RequestMatcher() {
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
String uri = request.getRequestURI();
|
||||
if (uri == null)
|
||||
return false;
|
||||
for (String p : prefixes) {
|
||||
if (uri.startsWith(p))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ========== Filtro de seguridad ==========
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(
|
||||
HttpSecurity http,
|
||||
@Value("${security.rememberme.key}") String keyRememberMe,
|
||||
UserDetailsService userDetailsService,
|
||||
PersistentTokenRepository tokenRepo,
|
||||
AuthenticationProvider daoAuthenticationProvider
|
||||
) throws Exception {
|
||||
AuthenticationProvider daoAuthenticationProvider) throws Exception {
|
||||
|
||||
http
|
||||
// Registra explícitamente el provider para Username/Password
|
||||
.authenticationProvider(daoAuthenticationProvider)
|
||||
.authenticationProvider(daoAuthenticationProvider)
|
||||
|
||||
.sessionManagement(session -> session
|
||||
.invalidSessionUrl("/login?expired")
|
||||
.maximumSessions(1)
|
||||
)
|
||||
.sessionManagement(session -> session
|
||||
.invalidSessionUrl("/login?expired")
|
||||
.maximumSessions(1))
|
||||
|
||||
.csrf(csrf -> csrf
|
||||
.ignoringRequestMatchers("/presupuesto/public/**")
|
||||
)
|
||||
// Ignora CSRF para tu recurso público (sin Ant/Mvc matchers)
|
||||
.csrf(csrf -> csrf
|
||||
.ignoringRequestMatchers(pathStartsWith("/presupuesto/public/")))
|
||||
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/", "/login",
|
||||
"/assets/**", "/css/**", "/js/**", "/images/**",
|
||||
"/public/**", "/presupuesto/public/**",
|
||||
"/error", "/favicon.ico").permitAll()
|
||||
.requestMatchers("/users/**").hasAnyRole("SUPERADMIN", "ADMIN")
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
// ====== RequestCache: sólo navegaciones HTML reales ======
|
||||
.requestCache(rc -> {
|
||||
HttpSessionRequestCache cache = new HttpSessionRequestCache();
|
||||
|
||||
.formLogin(login -> login
|
||||
.loginPage("/login").permitAll()
|
||||
.loginProcessingUrl("/login")
|
||||
.usernameParameter("username")
|
||||
.passwordParameter("password")
|
||||
.defaultSuccessUrl("/", false)
|
||||
.failureUrl("/login?error") // útil para diagnosticar
|
||||
)
|
||||
// Navegación HTML (por tipo o por cabecera Accept)
|
||||
RequestMatcher htmlPage = new OrRequestMatcher(
|
||||
new MediaTypeRequestMatcher(MediaType.TEXT_HTML),
|
||||
new MediaTypeRequestMatcher(MediaType.APPLICATION_XHTML_XML),
|
||||
new RequestHeaderRequestMatcher("Accept", "text/html"));
|
||||
|
||||
.rememberMe(rm -> rm
|
||||
.key(keyRememberMe)
|
||||
.rememberMeParameter("remember-me")
|
||||
.rememberMeCookieName("IMPRIMELIBROS_REMEMBER")
|
||||
.tokenValiditySeconds(60 * 60 * 24 * 2)
|
||||
.userDetailsService(userDetailsService)
|
||||
.tokenRepository(tokenRepo)
|
||||
)
|
||||
// No AJAX
|
||||
RequestMatcher nonAjax = new NegatedRequestMatcher(
|
||||
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
|
||||
|
||||
.logout(logout -> logout
|
||||
.logoutUrl("/logout")
|
||||
.logoutSuccessUrl("/")
|
||||
.invalidateHttpSession(true)
|
||||
.deleteCookies("JSESSIONID", "IMPRIMELIBROS_REMEMBER")
|
||||
.permitAll()
|
||||
);
|
||||
// Excluir sondas .well-known
|
||||
RequestMatcher notWellKnown = new NegatedRequestMatcher(pathStartsWith("/.well-known/"));
|
||||
|
||||
// Excluir estáticos: comunes + tu /assets/**
|
||||
RequestMatcher notStatic = new AndRequestMatcher(
|
||||
new NegatedRequestMatcher(PathRequest.toStaticResources().atCommonLocations()),
|
||||
new NegatedRequestMatcher(pathStartsWith("/assets/")));
|
||||
|
||||
cache.setRequestMatcher(new AndRequestMatcher(htmlPage, nonAjax, notStatic, notWellKnown));
|
||||
rc.requestCache(cache);
|
||||
})
|
||||
// ========================================================
|
||||
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
// Aquí usa patrones String (no deprecados)
|
||||
.requestMatchers(
|
||||
"/",
|
||||
"/login",
|
||||
"/assets/**",
|
||||
"/css/**",
|
||||
"/js/**",
|
||||
"/images/**",
|
||||
"/public/**",
|
||||
"/presupuesto/public/**",
|
||||
"/error",
|
||||
"/favicon.ico",
|
||||
"/.well-known/**" // opcional
|
||||
).permitAll()
|
||||
.requestMatchers("/users/**").hasAnyRole("SUPERADMIN", "ADMIN")
|
||||
.anyRequest().authenticated())
|
||||
|
||||
.formLogin(login -> login
|
||||
.loginPage("/login").permitAll()
|
||||
.loginProcessingUrl("/login")
|
||||
.usernameParameter("username")
|
||||
.passwordParameter("password")
|
||||
.defaultSuccessUrl("/", false) // respeta SavedRequest (ya filtrada)
|
||||
.failureUrl("/login?error"))
|
||||
|
||||
.rememberMe(rm -> rm
|
||||
.key(keyRememberMe)
|
||||
.rememberMeParameter("remember-me")
|
||||
.rememberMeCookieName("IMPRIMELIBROS_REMEMBER")
|
||||
.tokenValiditySeconds(60 * 60 * 24 * 2)
|
||||
.userDetailsService(userDetailsService)
|
||||
.tokenRepository(tokenRepo))
|
||||
|
||||
.logout(logout -> logout
|
||||
.logoutUrl("/logout")
|
||||
.logoutSuccessUrl("/")
|
||||
.invalidateHttpSession(true)
|
||||
.deleteCookies("JSESSIONID", "IMPRIMELIBROS_REMEMBER")
|
||||
.permitAll());
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user