Spring Security and JWT Authentication
Introduction
Public APIs need authentication (who are you?) and authorization (what may you do?). Spring Security filters every request; JWT tokens carry signed claims for stateless APIs. This chapter secures REST endpoints, implements login that returns JWT, and protects routes by role—foundation for production apps.
Prerequisites
- Spring MVC Basics
- Validation and Global Exception Handling
- HTTPS in production—see Nginx HTTPS
Add Dependencies
Security Filter Chain (Boot 3)
Code explanation:
- Stateless — no HTTP session; JWT carries identity
csrfdisabled — common for pure JSON APIs (re-enable for cookie-based forms)
JWT Utility
application.yml:
app:
jwt:
secret: ${JWT_SECRET:change-me-to-long-random-string-at-least-32-chars}
expiration-ms: 3600000Warning
Strong JWT Secret in Production
Use long random JWT_SECRET from env/secrets manager—never commit real secret to Git.
JWT Filter
Login Endpoint
Provide UserDetailsService bean loading users from DB with encoded passwords.
Call Protected API
curl -X POST http://localhost:8080/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"pass"}'
curl http://localhost:8080/api/users/1 \
-H "Authorization: Bearer <token>"Method Security (Optional)
@EnableMethodSecurity
@Configuration
public class MethodSecurityConfig { }
@Service
public class AdminService {
@PreAuthorize("hasRole('ADMIN')")
public void purgeCache() { }
}CORS
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:5173"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}Enable in HttpSecurity: .cors(Customizer.withDefaults()).
FAQ
401 on every request?
Missing/invalid Bearer token, wrong secret, expired JWT.
403 vs 401?
401 not authenticated; 403 authenticated but insufficient role.
Store JWT where?
Browser SPA: memory or httpOnly cookie—avoid localStorage if XSS risk high.
Refresh tokens?
Issue short access + long refresh token stored securely—separate endpoint pattern.
OAuth2 / Login with Google?
Use spring-boot-starter-oauth2-client—different flow from custom JWT.
Password in DB?
Always BCrypt or Argon2—never plain text.
SpringDoc + JWT?
Add security scheme in OpenAPI—chapter 20—Authorize button in Swagger UI.