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
usercon 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ónauthenticated(): requiere un usuario autenticadohasRole("X"): requiere el rol XhasAuthority("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:
- Header: algoritmo y tipo
- Payload: claims (datos)
- 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:
| Claim | Descripción |
|---|---|
sub | Sujeto (usuario) |
iss | Emisor |
exp | Expiración |
iat | Emitido en |
nbf | No antes de |
jti | ID 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:
- Variables de entorno
- Vault (HashiCorp Vault)
- AWS Secrets Manager
- 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:
- Blacklist: almacenar los tokens revocados (Redis)
- Expiración corta: tokens válidos por poco tiempo
- 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:
- Siempre codificar las contraseñas (BCrypt)
- Usar HTTPS en producción
- Configurar CORS estrictamente
- Implementar rate limiting
- Validar todas las entradas
- Registrar los eventos de seguridad
- Usar secretos fuertes y externalizarlos
- Actualizar regularmente las dependencias
Puntaje
| Puntaje | Nivel |
|---|---|
| 27-30 | Excelente - Seguridad dominada |
| 21-26 | Bien - Algunas revisiones |
| 15-20 | Medio - Repase los capítulos |
| Menos de 15 | A revisar |