Spring SecurityLección 13
Lo nuevo de Spring Security 7
Qué rompe tu código de la 6.x, cómo migrar, y lo realmente nuevo — MFA como parte del modelo de autorización, login por enlace mágico y passkeys.
Al terminar sabrás
- Traducir configuración de la 6.x a la 7.x con la tabla de equivalencias.
- Exigir MFA de forma global o selectiva usando factores como authorities.
- Configurar login por enlace mágico (one-time token) y passkeys.
- Conocer las novedades de la 7.1 y la ruta de migración recomendada.
La 7.0 (noviembre de 2025) fue sobre todo una limpieza: eliminó lo que la 6.x había deprecado. Lo realmente nuevo es la autenticación multifactor como parte del modelo de autorización, que la 7.1 (2026) refinó. La ruta de migración recomendada es subir primero a la 6.5, eliminar todos los avisos de deprecación y después saltar a la 7.
Lo que rompe tu código de la 6.x
Sección titulada «Lo que rompe tu código de la 6.x»| Antes (6.x o anterior) | Ahora (7.x) |
|---|---|
extends WebSecurityConfigurerAdapter | Beans SecurityFilterChain (ya en la 6.0) |
.and() entre configurers | Solo DSL con lambdas |
authorizeRequests() | authorizeHttpRequests() |
AntPathRequestMatcher, MvcRequestMatcher | PathPatternRequestMatcher (y ** solo al final del patrón) |
AccessDecisionManager, AccessDecisionVoter | AuthorizationManager (lo viejo vive en spring-security-access) |
AuthorizationManager#check | AuthorizationManager#authorize |
http.apply(dsl) | http.with(dsl, Customizer.withDefaults()) |
requiresChannel(c -> c.anyRequest().requiresSecure()) | redirectToHttps(Customizer.withDefaults()) |
Jackson 2 (SecurityJackson2Modules) | Jackson 3 (SecurityJacksonModules) |
| Password grant en OAuth2 | Eliminado: Authorization Code + PKCE o Client Credentials |
| PKCE opcional | Activado por defecto |
typ del JWT sin validar | JwtTypeValidator: acepta JWT o ausente |
| Spring Authorization Server aparte | Módulo de Spring Security |
| OpenSAML 4 | OpenSAML 5 |
MFA: los factores son authorities
Sección titulada «MFA: los factores son authorities»La idea es elegante: cuando te autenticas con un mecanismo, Spring añade a tu Authentication una authority de factor (FactorGrantedAuthority) con el instante en que la obtuviste. El login con contraseña añade FACTOR_PASSWORD; el enlace mágico, FACTOR_OTT; un passkey, FACTOR_WEBAUTHN. Exigir MFA es, por tanto, una regla de autorización más: “para entrar aquí necesitas estas dos authorities”.
- Login con contraseña+
FACTOR_PASSWORD - Regla de
/admin/**exigeROLE_ADMIN+FACTOR_PASSWORD+FACTOR_OTT - Redirección al factorenlace mágico → +
FACTOR_OTT - Acceso concedido
Forma global
Sección titulada «Forma global»@Configuration@EnableWebSecurity@EnableMultiFactorAuthentication(authorities = { FactorGrantedAuthority.PASSWORD_AUTHORITY, FactorGrantedAuthority.OTT_AUTHORITY })class SecurityConfig {
@Bean SecurityFilterChain web(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/admin/**").hasRole("ADMIN") // ahora exige ROLE_ADMIN + los dos factores .anyRequest().authenticated()) // exige los dos factores .formLogin(Customizer.withDefaults()) .oneTimeTokenLogin(Customizer.withDefaults()); return http.build(); }}Si el usuario entró con contraseña y le falta el segundo factor, Spring lo redirige automáticamente a la página del factor que falta.
Forma selectiva, con caducidad del factor
Sección titulada «Forma selectiva, con caducidad del factor»var mfa = AuthorizationManagerFactories.multiFactor() .requireFactors(FactorGrantedAuthority.PASSWORD_AUTHORITY, FactorGrantedAuthority.OTT_AUTHORITY) .build();
var recentPassword = AuthorizationManagerFactories.multiFactor() .requireFactor(f -> f.passwordAuthority().validDuration(Duration.ofMinutes(30))) .build();
http.authorizeHttpRequests(auth -> auth .requestMatchers("/admin/**").access(mfa.hasRole("ADMIN")) .requestMatchers("/account/password").access(recentPassword.authenticated()) // re-autenticación .anyRequest().authenticated());La 7.1 añadió condiciones: when(...) y withWhen(...) en multiFactor() para exigir MFA solo a ciertos usuarios, y MultiFactorCondition.WEBAUTHN_REGISTERED para pedirlo solo a quien ya registró un passkey.
Login por enlace mágico (one-time token)
Sección titulada «Login por enlace mágico (one-time token)»oneTimeTokenLogin() genera un token de un solo uso y te deja decidir cómo entregarlo (email, SMS). Solo implementas el envío:
@Componentclass MagicLinkSender implements OneTimeTokenGenerationSuccessHandler {
private final OneTimeTokenGenerationSuccessHandler redirect = new RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent"); private final MailService mail;
MagicLinkSender(MailService mail) { this.mail = mail; }
@Override public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken token) throws IOException, ServletException { String link = ServletUriComponentsBuilder.fromContextPath(request) .path("/login/ott") .queryParam("token", token.getTokenValue()) .toUriString(); mail.send(token.getUsername(), "Tu enlace de acceso", link); redirect.handle(request, response, token); // "revisa tu correo" }}/ott/sent es una página tuya: créala con un controlador simple y déjala en permitAll(). El formulario para pedir el enlace y la página /login/ott que lo canjea los genera Spring. Los tokens por defecto viven en memoria y caducan a los 5 minutos; en producción usa JdbcOneTimeTokenService.
Passkeys (WebAuthn)
Sección titulada «Passkeys (WebAuthn)»Los passkeys sustituyen la contraseña por un par de claves guardado en el dispositivo (Touch ID, Windows Hello, el móvil). Son resistentes al phishing porque la clave está ligada al dominio:
http.webAuthn(w -> w .rpName("Notas") .rpId("example.com") // tu dominio .allowedOrigins("https://app.example.com"));Necesitas la dependencia de WebAuthn que indica la documentación de tu versión y, en producción, repositorios JDBC para usuarios y credenciales (JdbcPublicKeyCredentialUserEntityRepository, JdbcUserCredentialRepository). Desde la 7.1, el login con passkey publica eventos de autenticación como cualquier otro mecanismo.
Otras novedades de la 7.1
Sección titulada «Otras novedades de la 7.1»hasAllAuthorities(...)yhasAllRoles(...)en las reglas (desde la 7.0).InetAddressMatcher, para reglas por IP o rango.ConditionalAuthorizationManager, para componer reglas con condiciones.RestClientOpaqueTokenIntrospector: introspección de tokens opacos sobreRestClient.- Soporte de
PreFlightRequestFilterpara los preflights de CORS. NimbusJwtEncoder.withKeyPair(...)para crear un encoder sin montar unJWKSourcea mano.
La 7.2 está en fase de release candidate (octubre de 2026); cuando salga, esta guía se actualizará.
Mini lab · 30 min
MFA solo donde importa
Objetivo: Exigir un segundo factor (enlace mágico) únicamente en una página sensible y ver las authorities resultantes.
- En la cadena web activa
formLogin+oneTimeTokenLoginy registra unOneTimeTokenGenerationSuccessHandlerque, en lugar de enviar email, escriba el enlace en el log. - Crea
/web/adminy exígele MFA conAuthorizationManagerFactories.multiFactor()(contraseña + OTT) yhasRole("ADMIN"). - Entra con contraseña, visita
/web/adminy sigue la redirección; copia el enlace del log y ábrelo. - Crea un endpoint que devuelva
authentication.getAuthorities().
Preguntas:
- ¿Qué authorities ves junto a
ROLE_ADMINtras completar los dos factores? - ¿Qué ocurre si visitas
/web/adminsolo con la contraseña? - ¿Cómo exigirías ese mismo segundo factor en un método de servicio, no en una URL?
Pista
Busca authorities que empiecen por FACTOR_.
Mostrar respuestaOcultar respuesta
FACTOR_PASSWORDyFACTOR_OTT(cada una comoFactorGrantedAuthority, con el instante en que se obtuvo), además deROLE_ADMIN.- La regla falla por falta de
FACTOR_OTT. Como estás autenticado pero te falta un factor, Spring no responde con un 403 seco: te redirige a la página del factor que falta (el formulario de enlace mágico). - Con una anotación que pida la authority del factor:
@PreAuthorize("hasRole('ADMIN') and hasAuthority('FACTOR_OTT')"), o con@EnableMultiFactorAuthentication, que también aplica a la seguridad por método. No hace falta ninguna API nueva: los factores son authorities.