Lesson 15: Spring Boot Security Basics with JWT
Master Spring Security fundamentals: implement authentication, authorization, JWT tokens, and protect your REST APIs with enterprise-grade security practices.
Introduction
Building applications without security is like constructing a house without locks, leaving your valuable data and user information completely exposed to anyone who wants to access it. Spring Security provides a comprehensive, production-ready security framework that protects your applications from unauthorized access, data breaches, and various security threats. Combined with JWT (JSON Web Tokens), you can create stateless, scalable authentication systems that work perfectly with modern single-page applications and mobile apps. Security isn't just about keeping bad actors out - it's about ensuring that legitimate users can access exactly what they need while protecting sensitive operations and data. This lesson teaches you to implement authentication (who you are), authorization (what you can do), secure your REST endpoints, and follow security best practices that will make your applications enterprise-ready and trustworthy.
Understanding Security
Definition
Application security involves protecting your software from unauthorized access, data breaches, and malicious attacks. It encompasses authentication (verifying user identity), authorization (controlling access to resources), data protection (encrypting sensitive information), and secure communication (preventing eavesdropping and tampering). Security is not an afterthought but a fundamental aspect that should be built into every layer of your application from the ground up.
Analogy
Think of application security like a multi-layered security system for a high-end office building. First, you need proper identification to enter the building (authentication) - showing your ID card to prove who you are. Once inside, different key cards give you access to different floors and rooms based on your role (authorization) - executives can access the boardroom, while interns can only access common areas. Security cameras monitor activities (logging and auditing), sensitive documents are kept in locked safes (data encryption), and all communications happen through secure channels (HTTPS). Guards patrol regularly (security monitoring), and there are emergency protocols for handling threats (incident response). Each layer works together to create a comprehensive security system that protects valuable assets while allowing legitimate business to continue smoothly.
Examples
Common security threats:
// SQL Injection - malicious input
String query = "SELECT * FROM users WHERE name = '" + userInput + "'";
// If userInput = "'; DROP TABLE users; --"
// This could delete your entire users table!
Cross-Site Scripting (XSS):
// Unsafe: directly displaying user input
model.addAttribute("username", request.getParameter("name"));
// If name containsalert('hacked'), it executes
Unauthorized access:
// Without security: anyone can access admin functions
@GetMapping("/admin/delete-user/{id}")
public String deleteUser(@PathVariable Long id) {
userService.delete(id); // Anyone can delete any user!
}
Data exposure:
// Returning sensitive data to unauthorized users
public User getUser(Long id) {
return userRepository.findById(id); // Includes password, SSN, etc.
}
Spring Security Basics
Definition
Spring Security is a powerful framework that provides comprehensive security services for Java applications. It handles authentication, authorization, protection against common attacks, session management, and integration with various security standards. Spring Security uses a filter chain to intercept requests, check credentials, enforce access rules, and protect against security vulnerabilities. It integrates seamlessly with Spring Boot through auto-configuration while remaining highly customizable for specific security requirements.
Analogy
Spring Security is like hiring a professional security company to protect your office building. Instead of you having to learn about security cameras, access control systems, threat detection, and emergency protocols, the security company provides a complete, integrated solution. They install a sophisticated system with multiple checkpoints (filter chain) that automatically screens everyone entering the building, verifies their credentials against a database, checks their authorization level for different areas, and logs all activities for auditing. The security team handles everything from basic door locks to advanced threat detection, using industry-standard procedures and equipment. You can customize the security level for different areas - some rooms might be open to everyone, others require special clearance, and the most sensitive areas need multiple forms of authentication. The beauty is that this comprehensive security operates transparently, protecting your business without interfering with normal operations.
Examples
Adding Spring Security dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Default security behavior:
// Spring Security automatically secures ALL endpoints
// Default username: user
// Password: generated in console logs
// All requests require authentication
Basic security properties:
spring.security.user.name=admin
spring.security.user.password=secret
spring.security.user.roles=ADMIN
Security auto-configuration:
// Spring Boot automatically configures:
// - HTTP Basic authentication
// - Form-based login
// - CSRF protection
// - Session management
// - Password encoding
JWT Fundamentals
Definition
JWT (JSON Web Token) is a compact, self-contained token format that securely transmits information between parties. A JWT consists of three parts: header (algorithm info), payload (claims/data), and signature (verification). JWTs are stateless, meaning all necessary information is contained within the token itself, eliminating the need for server-side session storage. This makes them perfect for scalable applications, microservices, and single-page applications where you need to verify user identity and permissions without maintaining server state.
Analogy
Think of a JWT like a special concert wristband that contains all the information security needs in one place. Unlike a simple ticket stub that requires staff to look up your details in a computer system, this smart wristband has everything embedded directly on it: your name, what sections you can access, when the wristband expires, and a special holographic seal that proves it's authentic and hasn't been tampered with. Security guards can instantly verify your access level just by scanning the wristband - no need to call the box office or check a central database. The wristband is self-contained and cryptographically signed, so guards know immediately if someone has tried to forge or modify it. This system works perfectly even if thousands of people are entering different venues simultaneously, because each wristband carries its own verification information.
Examples
JWT structure (header.payload.signature):
// JWT Token example:
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
// eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
// SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT payload (claims):
{
"sub": "1234567890", // Subject (user ID)
"name": "John Doe", // User name
"roles": ["USER", "ADMIN"], // User roles
"iat": 1516239022, // Issued at
"exp": 1516325422 // Expires at
}
Creating JWT in Java:
String token = Jwts.builder()
.setSubject(user.getUsername())
.claim("roles", user.getRoles())
.setExpiration(new Date(System.currentTimeMillis() + 86400000))
.signWith(secretKey)
.compact();
Validating JWT:
Claims claims = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
Security Configuration
Definition
Security configuration in Spring Boot involves creating a configuration class that customizes how Spring Security behaves. You define which endpoints require authentication, configure password encoding, set up authentication providers, and specify security filters. The SecurityFilterChain bean replaces the older WebSecurityConfigurerAdapter, providing a more functional approach to security configuration. Proper configuration ensures your application follows security best practices while remaining usable for legitimate users.
Analogy
Security configuration is like setting up the rules and procedures for a corporate office building's security system. You create a comprehensive security manual that specifies which areas are public (lobby, reception), which require employee badges (work floors), and which need special clearance (executive offices, server rooms). You define the authentication methods (ID cards, biometric scanners, security codes), set up different access levels for different employee types (intern, employee, manager, executive), and establish protocols for handling visitors. The manual also covers password policies (how often to change access codes), session management (how long badges remain active), and emergency procedures. Just like how security guards follow this manual to consistently enforce building security, Spring Security follows your configuration to protect your application endpoints and resources.
Examples
Basic security configuration:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.build();
}
}
Configuring endpoint security:
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated());
Password encoder configuration:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // Strong password hashing
}
CORS configuration:
http.cors(cors -> cors.configurationSource(request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
return config;
}));
JWT Authentication
Definition
JWT authentication involves creating a custom authentication filter that intercepts requests, extracts JWT tokens from headers, validates the tokens, and sets up the security context for authenticated users. The process includes generating tokens during login, including them in subsequent requests, and validating them on the server side. This stateless approach eliminates the need for server-side session storage, making your application more scalable and suitable for distributed environments.
Analogy
JWT authentication is like using a smart keycard system in a modern office building. When you first arrive for work (login), you show your credentials to security and receive a smart keycard (JWT token) that contains all your access information encoded on it. This keycard has everything security needs to know: your name, department, clearance level, and when it expires. Throughout the day, whenever you approach a door (make a request), the card reader (JWT filter) scans your keycard, instantly verifies it's authentic and hasn't expired, and grants or denies access based on the information stored on the card. The beautiful thing is that each door can make decisions independently without calling the main security office - the keycard contains everything needed for verification. At the end of the day or when your employment status changes, the keycard expires automatically, and you'd need to get a new one to continue accessing the building.
Examples
JWT authentication filter:
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) {
String token = extractToken(request);
if (token != null && validateToken(token)) {
setAuthentication(token);
}
filterChain.doFilter(request, response);
}
}
Login endpoint generating JWT:
@PostMapping("/login")
public ResponseEntity login(@RequestBody LoginRequest request) {
Authentication auth = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
);
String token = jwtService.generateToken(auth.getName());
return ResponseEntity.ok(new LoginResponse(token));
}
Extracting token from request:
private String extractToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7); // Remove "Bearer " prefix
}
return null;
}
Client sending JWT token:
// HTTP Header
// Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Protecting Endpoints
Definition
Protecting endpoints involves configuring which URLs require authentication, what roles or authorities are needed to access specific resources, and how to handle unauthorized access attempts. Spring Security provides multiple ways to secure endpoints: URL-based security in configuration classes, method-level security with annotations, and programmatic security checks within your business logic. Different endpoints may have different security requirements - some might be public, others require authentication, and sensitive operations might need specific roles or permissions.
Analogy
Protecting endpoints is like designing access control for different areas of a large hospital. The lobby and information desk are open to everyone (public endpoints), but you need to show ID and sign in to visit patient rooms (authenticated endpoints). Only doctors can access the operating rooms (role-based access), and only authorized pharmacists can enter the medication storage area (permission-based access). The emergency room has special access rules during emergencies (conditional access), and the administrative offices require different credentials than medical areas (different authentication methods). Each area has security checkpoints appropriate to its sensitivity level, and the security system automatically enforces these rules, redirecting unauthorized people to appropriate areas or asking them to get proper clearance before proceeding.
Examples
URL-based endpoint protection:
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.DELETE, "/api/**").hasRole("ADMIN")
.anyRequest().authenticated());
Method-level security:
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
Conditional authorization:
@PreAuthorize("hasRole('ADMIN') or @userService.isCurrentUser(#id, authentication.name)")
@PutMapping("/users/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
return userService.update(id, user);
}
Programmatic security check:
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id, Authentication auth) {
Order order = orderService.findById(id);
if (!order.getCustomer().getEmail().equals(auth.getName())) {
throw new AccessDeniedException("Cannot access another user's order");
}
return order;
}
Role-Based Security
Definition
Role-based security assigns users to roles (like ADMIN, USER, MANAGER) and grants permissions based on these roles rather than individual users. Roles simplify permission management by grouping related permissions together and make it easy to modify access levels by changing role assignments. Spring Security supports both role-based access control (RBAC) and more granular authority-based access control, allowing you to implement sophisticated permission systems that scale with your application's complexity and organizational structure.
Analogy
Role-based security is like how a restaurant operates with different staff positions, each having specific responsibilities and access levels. The dishwasher can access the kitchen and dish area but not the cash register or manager's office. Servers can access dining areas, place orders in the system, and handle payments, but can't modify menu prices or access employee records. Managers can access all areas, modify schedules, view sales reports, and handle customer complaints. The head chef can control kitchen operations and inventory but might not handle financial reports. Instead of giving each person individual keys to specific doors, you create master key sets for each role (DISHWASHER, SERVER, MANAGER, CHEF), and when someone gets promoted or changes positions, you just switch their key set rather than reconfiguring every individual permission. This system is much easier to manage and ensures consistent access levels for people in similar positions.
Examples
User entity with roles:
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String username;
private String password;
@Enumerated(EnumType.STRING)
@ElementCollection(fetch = FetchType.EAGER)
private Set roles = new HashSet<>();
}
Role enum definition:
public enum Role {
USER, ADMIN, MANAGER, MODERATOR
}
Role-based method security:
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/users")
public List getAllUsers() { return userService.findAll(); }
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
@GetMapping("/reports")
public Report getReport() { return reportService.generate(); }
Custom UserDetailsService:
@Service
public class CustomUserDetailsService implements UserDetailsService {
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username);
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.name()))
.collect(Collectors.toList()))
.build();
}
}
Security Testing
Definition
Security testing verifies that your authentication and authorization mechanisms work correctly under various scenarios. This includes testing successful authentication, authorization failures, token validation, role-based access control, and security edge cases. Spring Security provides testing support through @WithMockUser, @WithUserDetails, and MockMvc integration that allows you to simulate different user contexts and verify that security rules are properly enforced without compromising test isolation or requiring complex setup.
Analogy
Security testing is like conducting comprehensive drills and audits for a building's security system. You test every scenario: legitimate employees accessing their authorized areas (positive authentication tests), unauthorized people being properly denied access (negative authorization tests), expired keycards being rejected (token expiration tests), and emergency procedures working correctly (edge case testing). Security auditors simulate different roles - they might pose as employees, contractors, visitors, or even potential intruders to ensure the system responds appropriately in each situation. They test not just that the right people get in, but also that the wrong people are kept out, that alarms work properly, and that security logs capture all the necessary information. These comprehensive tests ensure that when real security challenges arise, the system performs exactly as designed.
Examples
Testing with mock user:
@Test
@WithMockUser(roles = "ADMIN")
void adminCanAccessUserList() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isOk());
}
Testing authorization failure:
@Test
@WithMockUser(roles = "USER")
void userCannotAccessAdminEndpoint() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isForbidden());
}
Testing JWT authentication:
@Test
void validTokenAllowsAccess() throws Exception {
String token = jwtService.generateToken("testuser");
mockMvc.perform(get("/api/profile")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk());
}
Testing without authentication:
@Test
void unauthenticatedUserCannotAccessProtectedEndpoint() throws Exception {
mockMvc.perform(get("/api/profile"))
.andExpect(status().isUnauthorized());
}
Security Best Practices
Definition
Security best practices involve implementing multiple layers of defense, following the principle of least privilege, securing sensitive data, validating all inputs, implementing proper session management, and maintaining security through regular updates and monitoring. These practices include using strong password hashing, implementing proper error handling that doesn't leak sensitive information, enabling HTTPS for all communications, and following secure coding practices that prevent common vulnerabilities like injection attacks and cross-site scripting.
Analogy
Security best practices are like implementing comprehensive safety protocols for a nuclear power plant. You don't rely on just one safety measure - instead, you implement multiple independent layers of protection (defense in depth). Workers only have access to areas and information they absolutely need for their jobs (principle of least privilege). All safety-critical information is encrypted and stored securely (data protection). Every input into control systems is validated and sanitized (input validation). All communications happen through secure, monitored channels (secure communication). Regular safety drills, inspections, and system updates ensure that security measures remain effective over time (continuous monitoring and updates). Emergency procedures are in place to handle any security incidents quickly and effectively (incident response). The goal is to create a system so robust that even if one security measure fails, multiple backup systems prevent any serious problems.
Examples
Strong password encoding:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // Strong cost factor
}
// Never store plain text passwords
user.setPassword(passwordEncoder.encode(rawPassword));
Input validation and sanitization:
@PostMapping("/users")
public User createUser(@Valid @RequestBody CreateUserRequest request) {
// @Valid ensures all validation annotations are checked
String sanitizedName = StringEscapeUtils.escapeHtml4(request.getName());
return userService.create(request);
}
Secure error handling:
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity handleAccessDenied(AccessDeniedException e) {
// Don't expose sensitive information in error messages
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(new ErrorResponse("Access denied", "FORBIDDEN"));
}
Security headers configuration:
http.headers(headers -> headers
.frameOptions().deny() // Prevent clickjacking
.contentTypeOptions().and() // Prevent MIME sniffing
.httpStrictTransportSecurity(hstsConfig -> hstsConfig
.maxAgeInSeconds(31536000) // Force HTTPS
.includeSubdomains(true))); // Include all subdomains
Summary
You've now mastered the essential concepts of Spring Security and JWT authentication, from understanding the difference between authentication and authorization to implementing role-based access control and following security best practices. Spring Security provides a powerful, flexible framework for protecting your applications, while JWT tokens enable scalable, stateless authentication perfect for modern web applications. You've learned to configure security rules, protect endpoints, handle user roles, and test your security implementation thoroughly. These skills are crucial for building trustworthy applications that protect user data and maintain security in production environments. Next, you'll explore Spring Boot production features like actuators and monitoring, which help you maintain and observe your secure applications in real-world deployment scenarios.
Programming Challenge
Challenge: Secure Task Management API
Task: Build a secure task management system with JWT authentication, role-based authorization, and comprehensive security features.
Requirements:
- Create entities:
User
: id, username, email, password, roles (USER, ADMIN, MANAGER)Task
: id, title, description, status, priority, assignedTo, createdByProject
: id, name, description, members, createdBy- Implement JWT authentication:
- Login endpoint that returns JWT token
- JWT filter for validating tokens on protected endpoints
- Token should include user ID, username, and roles
- Token expiration and refresh mechanism
- Implement role-based authorization:
- PUBLIC: Registration, login endpoints
- USER: View own tasks, create tasks, update own tasks
- MANAGER: Assign tasks to team members, view team tasks
- ADMIN: Full access to all users, tasks, and projects
- Secure endpoints with proper authorization:
- Users can only see/edit their own profile unless they're admin
- Tasks can only be viewed/edited by assignee, creator, or manager
- Projects are visible to members and creators
- Add security features:
- Password encryption with BCrypt
- Input validation for all request bodies
- Global exception handler for security errors
- CORS configuration for frontend integration
- Write comprehensive security tests
Bonus features:
- Implement password reset functionality with secure tokens
- Add rate limiting for authentication endpoints
- Create audit logging for security-sensitive operations
- Implement account lockout after failed login attempts
- Add email verification for new user registration
Learning Goals: Practice comprehensive Spring Security implementation including JWT authentication, role-based authorization, method-level security, input validation, and security testing in a realistic business application.