Spring Security
01 What is Spring Security?
Solution and explanation
Solution:
Spring Security is responsible for protecting Spring Boot applications.
Suppose our job portal has these users:
- JOB_SEEKER
- RECRUITER
- HR
- ADMIN
A job seeker can:
- Search jobs
- View companies
- Apply for jobs
- View his own applications
A recruiter can:
- Create jobs
- View candidates for his jobs
- Shortlist candidates
HR can:
- Manage recruitment
- Update interview status
- View recruitment reports
ADMIN can:
- Manage users
- Manage companies
- Manage jobs
- Manage system configuration
Spring Security handles two different problems.
Authentication:
"Who are you?"
Example:
Sachin logs in using:
email = sachin@example.com password = ********
The application verifies the credentials and creates an authenticated identity.
Authorization:
"What are you allowed to do?"
After Sachin is authenticated, Spring checks his role.
If Sachin is JOB_SEEKER:
GET /api/jobs GET /api/applications POST /api/jobs/10/apply
may be allowed.
But:
DELETE /api/jobs/10
may return 403 Forbidden.
The important difference is:
401 = authentication is missing or invalid.
403 = authentication exists, but the user does not have permission.
Example:
Banking system:
Customer logs in successfully.
Authentication: Customer = ACCOUNT_1001
Authorization:
Customer can:
- View own balance
- Transfer money
- Download statement
Customer cannot:
- Change another customer's balance
- Access bank employee administration
- Approve another customer's loan
Spring Security should therefore be applied at the HTTP and service/method boundaries, not only in the frontend.
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/api/auth/login",
"/api/auth/register",
"/api/jobs",
"/api/companies/**"
).permitAll()
.requestMatchers("/api/admin/**")
.hasRole("ADMIN")
.requestMatchers("/api/hr/**")
.hasAnyRole("HR", "ADMIN")
.requestMatchers("/api/recruiter/**")
.hasAnyRole("RECRUITER", "HR", "ADMIN")
.anyRequest()
.authenticated()
)
.build();
}
Build authentication and authorization for JOB_SEEKER, RECRUITER, HR and ADMIN.
Quick check
Which statement is correct?
02 Authentication versus Authorization
Solution and explanation
Solution:
Authentication answers:
"Who is this user?"
Authorization answers:
"What can this user do?"
Consider an Indian banking application.
Customer A logs in.
The authentication process verifies:
- Customer ID
- Password
- OTP or another authentication factor
- Account/session/token validity
After authentication:
customer_id = 1001
Now authorization begins.
Customer 1001 requests:
GET /api/accounts/1001
The server checks whether account 1001 belongs to the authenticated customer.
If yes:
200 OK
If the customer tries:
GET /api/accounts/2000
the server must not simply trust the account ID received from the URL.
The authenticated identity and ownership must be checked.
The same principle applies to a job portal.
A recruiter may request:
GET /api/jobs/500/applications
The system must verify that recruiter 20 owns job 500 or has explicit permission to access it.
Authentication without authorization would allow a logged-in user to access data belonging to another user.
Therefore:
Authentication = identity.
Authorization = permission.
@GetMapping("/jobs/{jobId}/applications")
@PreAuthorize("hasAnyRole('RECRUITER','HR','ADMIN')")
public List<ApplicationDto> applications(
@PathVariable Long jobId,
Authentication authentication) {
return applicationService
.getApplicationsForAuthorizedRecruiter(
jobId,
authentication.getName()
);
}
Implement ownership validation so one recruiter cannot access another recruiter’s candidate applications.
Quick check
Which statement is correct?
03 How does JWT authentication work?
Solution and explanation
Solution:
JWT authentication is commonly used when a frontend such as React or Angular communicates with a Spring Boot REST API.
Example:
The user submits:
POST /api/auth/login
Request:
email = sachin@example.com password = password123
Spring Security verifies the credentials.
If valid, the backend creates a JWT.
The token contains information such as:
- subject/user ID
- roles
- issuer
- issued time
- expiration time
The frontend then sends:
Authorization: Bearer <token>
on subsequent API requests.
Example:
GET /api/applications
The request reaches the Spring Security filter.
The filter:
- Reads Authorization header.
- Extracts Bearer token.
- Verifies the JWT signature.
- Checks expiration.
- Checks issuer/audience when configured.
- Extracts user identity and authorities.
- Creates Authentication.
- Places it into SecurityContext.
- Allows authorization rules to execute.
If the signature is invalid:
401 Unauthorized.
If the token is expired:
401 Unauthorized.
If the token is valid but the role is insufficient:
403 Forbidden.
Never trust a JWT merely because it can be decoded. The payload is readable. The signature must be verified.
@Component
public class JwtAuthenticationFilter
extends OncePerRequestFilter {
private final JwtService jwtService;
public JwtAuthenticationFilter(JwtService jwtService) {
this.jwtService = jwtService;
}
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
String token = header.substring(7);
if (jwtService.isValid(token)) {
String username =
jwtService.extractUsername(token);
var authentication =
new UsernamePasswordAuthenticationToken(
username,
null,
jwtService.extractAuthorities(token)
);
SecurityContextHolder
.getContext()
.setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}
Trace login -> JWT creation -> Authorization header -> JWT validation -> SecurityContext -> controller.
Quick check
Which statement is correct?
04 What is PasswordEncoder and why is BCrypt used?
Solution and explanation
Solution:
Passwords must never be stored directly in a database.
Bad:
users.password = "Sachin@123"
If the database is compromised, the original passwords are immediately exposed.
Instead, the application stores a password hash.
Example:
password entered:
Sachin@123
database:
$2a$10$....
During login:
- User enters password.
- Spring Security loads stored hash.
- PasswordEncoder compares the supplied password with the stored hash.
- If it matches, authentication succeeds.
- The original password is never recovered from the hash.
BCrypt includes a salt.
Therefore two users having the same password should not necessarily have the same stored hash.
Example:
User A password:
Welcome@123
User B password:
Welcome@123
Their BCrypt hashes can still be different because different salts are used.
Never use MD5 or SHA-1 directly for password storage.
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
@Service
public class UserService {
private final PasswordEncoder passwordEncoder;
public UserService(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
public String hashPassword(String password) {
return passwordEncoder.encode(password);
}
public boolean verifyPassword(
String rawPassword,
String storedHash) {
return passwordEncoder.matches(
rawPassword,
storedHash
);
}
}
Create a registration API that stores only a BCrypt hash and a login API that verifies the password using matches().
Quick check
Which statement is correct?
05 What is OAuth2 and OpenID Connect?
Solution and explanation
Solution:
OAuth2 is used when an application wants delegated access to another system.
Example:
A user clicks:
"Login with Google"
The job portal does not need to collect the user's Google password.
The browser is redirected to the identity provider.
The provider authenticates the user.
After successful authentication, the provider redirects back to the application with an authorization result.
OpenID Connect adds an identity layer on top of OAuth2.
For an enterprise application, the same concept can be used with:
- Microsoft
- GitHub
- Okta
- Keycloak
- enterprise identity providers
Example:
A company has 10,000 employees.
Instead of creating separate passwords in every internal Spring Boot application, employees can authenticate through the organization's identity provider.
The Spring Boot application receives verified identity information and maps it to application roles.
OAuth2 answers delegated authorization.
OpenID Connect provides standardized authentication/identity information.
spring:
security:
oauth2:
client:
registration:
google:
client-id: ${GOOGLE_CLIENT_ID}
client-secret: ${GOOGLE_CLIENT_SECRET}
scope:
- openid
- profile
- email
Implement Google login and map the returned email to an existing JOB_SEEKER or HR account.
Quick check
Which statement is correct?
06 What is CORS and why does it matter in React + Spring Boot?
Solution and explanation
- Solution:
- Suppose the frontend runs on:
- https://jobs.example.com
- and Spring Boot runs on:
- https://api.example.com
- The browser treats these as different origins.
- The frontend may call:
- GET https://api.example.com/api/jobs
- The browser checks whether the API allows the frontend origin.
- Spring Boot can explicitly allow:
- https://jobs.example.com
- but reject:
- https://unknown-site.com
- CORS is enforced by browsers.
- It should not be used as the only API security mechanism.
- A common mistake is:
- allowedOrigins("*")
- for production APIs.
- Instead, maintain an explicit allow-list.
- For authenticated browser requests, credentials and CORS configuration must also be handled consistently.
@Configuration
public class CorsConfig {
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config =
new CorsConfiguration();
config.setAllowedOrigins(
List.of("https://jobs.example.com")
);
config.setAllowedMethods(
List.of(
"GET",
"POST",
"PUT",
"PATCH",
"DELETE",
"OPTIONS"
)
);
config.setAllowedHeaders(
List.of("Authorization", "Content-Type")
);
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration(
"/**",
config
);
return source;
}
}
Allow the production React frontend while rejecting requests from an unauthorized browser origin.
Quick check
Which statement is correct?
07 What is role-based authorization?
Solution and explanation
Solution:
Suppose the portal contains these roles:
JOB_SEEKER RECRUITER HR ADMIN
Permissions:
JOB_SEEKER:
- Search jobs
- Apply
- View own applications
RECRUITER:
- Create jobs
- View candidates
- Shortlist candidates
HR:
- Manage recruitment
- Schedule interviews
- View recruitment reports
ADMIN:
- Manage everything permitted by the application policy
Example:
A recruiter sends:
PUT /api/applications/100/status
with:
status = SHORTLISTED
The backend must check:
- Is the recruiter authenticated?
- Does the recruiter have recruiter permission?
- Does application 100 belong to a job managed by this recruiter?
- Is the status transition valid?
Checking only the role is insufficient when resource ownership matters.
The database and service layer must enforce ownership rules.
@PreAuthorize("hasAnyRole('RECRUITER','HR','ADMIN')")
@PutMapping("/applications/{id}/status")
public ApplicationDto updateStatus(
@PathVariable Long id,
@RequestBody UpdateStatusRequest request,
Authentication authentication) {
return applicationService.updateStatus(
id,
request,
authentication.getName()
);
}
Prevent recruiter A from updating an application belonging to recruiter B.
Quick check
Which statement is correct?
