Saltar al contenido principal

Quiz: Spring Security

Ponga a prueba sus conocimientos sobre seguridad en Spring, JWT y OAuth2.

30 preguntas - Haga clic en cada pregunta para revelar la respuesta.


Preguntas 1-10: Fundamentos de Security​

Pregunta 1: ¿Qué dependencia agregar para Spring Security?

Respuesta:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

Por defecto, todas las rutas están protegidas.


Pregunta 2: ¿Qué sucede al agregar Spring Security sin configuración?

Respuesta:

  • Todas las rutas requieren autenticación
  • Se muestra un formulario de inicio de sesión
  • Se crea un usuario user con una contraseña generada (mostrada en los logs)
  • CSRF está activado

Pregunta 3: ¿Cómo configurar la seguridad en Spring Boot 3?

Respuesta:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());

return http.build();
}
}

Pregunta 4: ¿Cuál es la diferencia entre authenticated() y permitAll()?

Respuesta:

  • permitAll(): accesible sin autenticación
  • authenticated(): requiere un usuario autenticado
  • hasRole("X"): requiere el rol X
  • hasAuthority("X"): requiere la autoridad X

Pregunta 5: ¿Cómo definir usuarios en memoria?

Respuesta:

@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder().encode("password"))
.roles("USER")
.build();

UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("admin"))
.roles("ADMIN")
.build();

return new InMemoryUserDetailsManager(user, admin);
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

Pregunta 6: ¿Cómo implementar UserDetailsService con una base de datos?

Respuesta:

@Service
public class CustomUserDetailsService implements UserDetailsService {

private final UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));

return org.springframework.security.core.userdetails.User.builder()
.username(user.getEmail())
.password(user.getPassword())
.roles(user.getRole().name())
.build();
}
}

Pregunta 7: ¿Por qué codificar las contraseñas?

Respuesta:

  • Seguridad: contraseñas ilegibles si la base de datos es comprometida
  • BCrypt: genera un hash diferente cada vez (sal incorporada)
  • Obligatorio: Spring Security rechaza contraseñas en texto plano
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

Pregunta 8: ¿Cuál es la diferencia entre Role y Authority?

Respuesta:

  • Role: se le antepone automáticamente ROLE_
  • Authority: se utiliza tal cual
// Estos dos son equivalentes:
.hasRole("ADMIN") // verifica ROLE_ADMIN
.hasAuthority("ROLE_ADMIN")

// Autoridades personalizadas
.hasAuthority("READ_USERS")
.hasAuthority("WRITE_USERS")

Pregunta 9: ¿Qué es CSRF y cuándo desactivarlo?

Respuesta: CSRF (Cross-Site Request Forgery) protege contra solicitudes maliciosas.

Desactivar para APIs REST stateless (JWT):

http.csrf(csrf -> csrf.disable())

Mantener activado para aplicaciones con sesiones.


Pregunta 10: ¿Cómo recuperar el usuario conectado?

Respuesta:

// En un controlador
@GetMapping("/me")
public User getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {
return userService.findByEmail(userDetails.getUsername());
}

// O vía SecurityContextHolder
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();

Preguntas 11-20: JWT​

Pregunta 11: ¿Qué es un JWT?

Respuesta: JSON Web Token - un token compuesto por 3 partes:

  1. Header: algoritmo y tipo
  2. Payload: claims (datos)
  3. Signature: verificación de integridad

Formato: header.payload.signature (codificado en Base64)


Pregunta 12: ¿Cuáles son los claims estándar de un JWT?

Respuesta:

ClaimDescripción
subSujeto (usuario)
issEmisor
expExpiración
iatEmitido en
nbfNo antes de
jtiID de JWT (único)

Pregunta 13: ¿Cómo generar un JWT?

Respuesta:

@Service
public class JwtService {

@Value("${jwt.secret}")
private String secret;

public String generateToken(UserDetails user) {
return Jwts.builder()
.setSubject(user.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}

private Key getSigningKey() {
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
}
}

Pregunta 14: ¿Cómo validar un JWT?

Respuesta:

public boolean isTokenValid(String token, UserDetails userDetails) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();

String username = claims.getSubject();
Date expiration = claims.getExpiration();

return username.equals(userDetails.getUsername())
&& !expiration.before(new Date());
} catch (JwtException e) {
return false;
}
}

Pregunta 15: ¿Cómo implementar un filtro JWT?

Respuesta:

@Component
public class JwtAuthFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) {

String header = request.getHeader("Authorization");

if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
String username = jwtService.extractUsername(token);

if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails user = userDetailsService.loadUserByUsername(username);

if (jwtService.isTokenValid(token, user)) {
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
}

chain.doFilter(request, response);
}
}

Pregunta 16: ¿Cómo configurar el filtro JWT?

Respuesta:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

return http.build();
}

Pregunta 17: ¿Qué es un Refresh Token?

Respuesta: Un token de larga duración para obtener nuevos access tokens sin volver a iniciar sesión:

  • Access Token: corta duración (15min-1h)
  • Refresh Token: larga duración (7-30 días)
// Endpoint de refresh
@PostMapping("/refresh")
public TokenResponse refresh(@RequestBody RefreshRequest request) {
String refreshToken = request.getRefreshToken();
// Validar y generar nuevo access token
}

Pregunta 18: ¿Cómo almacenar el secreto JWT de forma segura?

Respuesta:

  1. Variables de entorno
  2. Vault (HashiCorp Vault)
  3. AWS Secrets Manager
  4. Spring Cloud Config (cifrado)
# ¡Nunca comitee el secreto!
jwt:
secret: ${JWT_SECRET}

Pregunta 19: ¿Cómo invalidar un JWT?

Respuesta: Los JWT son stateless, por lo que no se pueden revocar directamente. Soluciones:

  1. Blacklist: almacenar los tokens revocados (Redis)
  2. Expiración corta: tokens válidos por poco tiempo
  3. Versión de usuario: invalidar en cada cambio de contraseña

Pregunta 20: ¿Cómo asegurar los endpoints con anotaciones?

Respuesta:

@EnableMethodSecurity
@Configuration
public class SecurityConfig { }

@RestController
public class UserController {

@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/users")
public List<User> getAllUsers() { }

@PreAuthorize("#id == authentication.principal.id or hasRole('ADMIN')")
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) { }
}

Preguntas 21-30: OAuth2 y Seguridad Avanzada​

Pregunta 21: ¿Qué es OAuth2?

Respuesta: Protocolo de autorización que permite a una aplicación acceder a los recursos de un usuario a través de un proveedor externo (Google, GitHub, etc.) sin compartir la contraseña.


Pregunta 22: ¿Cuáles son los roles en OAuth2?

Respuesta:

  • Resource Owner: el usuario
  • Client: la aplicación
  • Authorization Server: Google, GitHub...
  • Resource Server: API protegida

Pregunta 23: ¿Cómo configurar OAuth2 con Google?

Respuesta:

spring:
security:
oauth2:
client:
registration:
google:
client-id: ${GOOGLE_CLIENT_ID}
client-secret: ${GOOGLE_CLIENT_SECRET}
scope: openid, profile, email
http.oauth2Login(Customizer.withDefaults());

Pregunta 24: ¿Cómo personalizar el éxito del login OAuth2?

Respuesta:

http.oauth2Login(oauth -> oauth
.successHandler((request, response, authentication) -> {
OAuth2User oauthUser = (OAuth2User) authentication.getPrincipal();
userService.processOAuthUser(oauthUser);
response.sendRedirect("/dashboard");
})
);

Pregunta 25: ¿Qué es CORS?

Respuesta: Cross-Origin Resource Sharing - permite solicitudes desde otros dominios:

@Configuration
public class CorsConfig {

@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}

Pregunta 26: ¿Cómo agregar headers de seguridad?

Respuesta:

http.headers(headers -> headers
.contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'"))
.frameOptions(frame -> frame.deny())
.xssProtection(xss -> xss.enable())
);

Pregunta 27: ¿Cómo limitar los intentos de inicio de sesión?

Respuesta: Implementar un rate limiter:

@Component
public class LoginAttemptService {

private final LoadingCache<String, Integer> attemptsCache;

public void loginFailed(String ip) {
int attempts = attemptsCache.get(ip);
attemptsCache.put(ip, attempts + 1);
}

public boolean isBlocked(String ip) {
return attemptsCache.get(ip) >= MAX_ATTEMPTS;
}
}

Pregunta 28: ¿Cómo auditar los accesos seguros?

Respuesta:

@Component
public class SecurityAuditListener {

@EventListener
public void onAuthSuccess(AuthenticationSuccessEvent event) {
log.info("Login successful: {}", event.getAuthentication().getName());
}

@EventListener
public void onAuthFailure(AuthenticationFailureBadCredentialsEvent event) {
log.warn("Login failed: {}", event.getAuthentication().getName());
}
}

Pregunta 29: ¿Cómo probar la seguridad?

Respuesta:

@WebMvcTest(UserController.class)
@Import(SecurityConfig.class)
class UserControllerSecurityTest {

@Test
@WithMockUser(roles = "ADMIN")
void adminCanAccessAdminEndpoint() {
mockMvc.perform(get("/admin/users"))
.andExpect(status().isOk());
}

@Test
@WithMockUser(roles = "USER")
void userCannotAccessAdminEndpoint() {
mockMvc.perform(get("/admin/users"))
.andExpect(status().isForbidden());
}
}

Pregunta 30: ¿Cuáles son las buenas prácticas de seguridad en Spring?

Respuesta:

  1. Siempre codificar las contraseñas (BCrypt)
  2. Usar HTTPS en producción
  3. Configurar CORS estrictamente
  4. Implementar rate limiting
  5. Validar todas las entradas
  6. Registrar los eventos de seguridad
  7. Usar secretos fuertes y externalizarlos
  8. Actualizar regularmente las dependencias

Puntaje​

PuntajeNivel
27-30Excelente - Seguridad dominada
21-26Bien - Algunas revisiones
15-20Medio - Repase los capítulos
Menos de 15A revisar

Volver al Módulo 8