In a distributed microservices environment, storing user sessions in memory is impossible. This project implements JSON Web Tokens (JWT), allowing the server to verify user identity without maintaining any server-side state.
- Generation: When a user logs in via
/auth/generateToken, theJwtServicecreates a signed string containing the username and expiration date. - Signing: We use the
HMAC-SHAalgorithm with a secret key. This ensures that if a user tries to change their username inside the token, the signature becomes invalid. - Storage: The client (Frontend) saves this token.
This is the "bouncer" of the application. It extends OncePerRequestFilter:
- It intercepts every incoming request.
- It looks for the
Authorization: Bearer <token>header. - If found, it validates the token with
JwtService. - If valid, it manually populates the
SecurityContextHolderso Spring knows the user is authenticated for that specific request.
In SecurityConfig.java, we set:
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
This tells Spring Security never to create an HTTPSession. Every single request must provide a valid JWT to be processed.
The utility class responsible for:
- Creating the token with a 30-minute expiration.
- Extracting the "Subject" (username) from the token.
- Verifying if the token has expired or been tampered with.
These bridge our MySQL database with Spring Security. UserInfoDetails converts our UserInfo entity into something Spring Security understands, including converting a comma-separated string of roles (e.g., "ROLE_USER, ROLE_ADMIN") into GrantedAuthority objects.
- @Entity: Maps this class to a database table.
- @Id + @GeneratedValue: Defines the primary key (id) with auto-increment.
- Fields:
- name: Display name.
- email: Often used as the username for login.
- password: Stored securely (hashed with BCrypt).
- roles: Defines user’s authorities (e.g.,
ROLE_USER, ROLE_ADMIN).
- Lombok Annotations:
@Data:Auto-generates getters/setters, equals, hashCode, toString.@AllArgsConstructor:Constructor with all fields.@NoArgsConstructor:Default constructor.
- Purpose of AuthRequest:
- Acts as a Data Transfer Object (DTO) for login requests.
- When a user sends credentials
(username + password)via aREST API, this class captures them.
- Integration in JWT Flow:
- User sends a POST request to
/auth/generateTokenwith JSON body:
{ "username": "harman", "password": "123" }- Spring Boot maps this JSON into an AuthRequest object automatically.
- Authentication logic checks the credentials against the database (UserInfo).
- If valid: a JWT token is generated and returned.
- If invalid: authentication fails with
401Unauthorized.
- User sends a POST request to
- Lombok Benefits:
- Saves boilerplate code (no need to manually write getters/setters).
- Keeps the class clean and focused on its purpose.
JpaRepository<UserInfo, Long>:- Provides built-in CRUD methods:
save(), findById(), findAll(), deleteById(), etc. - UserInfo: entity type.
- Long: primary key type (id).
- Provides built-in CRUD methods:
- Custom Finder Method (
findByEmail):- Spring Data JPA parses the method name and auto-generates the query.
- No need to write SQL manually.
- Useful for authentication to load user by email.
Optional<UserInfo>:- Prevents NullPointerException.
- Forces you to handle the case where no user is found.
- UserDetails: Spring Security’s standard representation of a user.
- UserInfoDetails: Custom adapter that converts your UserInfo entity into UserDetails.
- Authorities: Roles like
ROLE_USER, ROLE_ADMINconverted intoGrantedAuthority. - Password: Stored as a hashed value
(BCrypt). - Account Flags: Always true here, but can be customized (e.g., disabled accounts).
- PasswordEncoder Bean:
- Central place to define how passwords are hashed.
- Makes it easy to inject into services, repositories, or security configs.
- BCryptPasswordEncoder:
- Secure hashing algorithm.
- Automatically adds salt which prevents rainbow table attacks.
- Adjustable strength factor to balance security and performance.
- Integration in JWT Flow:
- When a user registers: password is hashed using
passwordEncoder.encode(). - Hashed password is stored in the database (UserInfo.password).
- When user logs in: Spring Security uses
passwordEncoder.matches()to verify plain-text password against stored hash. - If valid: JWT token is generated and returned.
- When a user registers: password is hashed using
- UserDetailsService:
- Spring Security uses this interface to load user data during authentication.
loadUserByUsername()is called automatically when a user tries to log in.
- UserInfoService:
- Implements UserDetailsService.
- Loads users from DB via UserInfoRepository.
- Wraps them into UserInfoDetails (adapter for Spring Security).
- PasswordEncoder (BCrypt):
- Ensures passwords are stored securely.
encoder.encode():hashes password before saving.encoder.matches():verifies login attempts.
- addUser():
- Handles registration.
- Encodes password, saves user, and returns success message.
- JWT (JSON Web Token):
- Compact, secure way to transmit user identity + roles.
- Contains
Header,Payload (claims),Signature.
- generateToken(): Creates a JWT with subject = username, valid for 30 minutes.
- extractUsername(): Reads the
subject (username)from token. - extractClaims(): Generic method that can
extract expiration, issuedAt, roles, etc. - validateToken(): Ensures token belongs to the right user and is not expired.
- isTokenExpired(): Checks expiration claim against current time.
- OncePerRequestFilter: Ensures JWT validation runs once per request.
- Authorization Header: Expected format:
Authorization: Bearer <token>. - jwtService.extractUsername(): Reads
username (subject)from token. - jwtService.validateToken(): Ensures token is valid and not expired.
- SecurityContextHolder: Stores authentication info used by Spring Security for authorization.
- UsernamePasswordAuthenticationToken: Represents authenticated user with roles.
- SecurityFilterChain: Defines security rules for HTTP requests.
- JWT Filter (JwtAuthFilter): Validates JWT tokens before requests reach controllers.
- SessionCreationPolicy.STATELESS: No sessions; every request must include JWT.
- AuthenticationProvider: Uses
UserDetailsService + PasswordEncoderto authenticate users. - AuthenticationManager: Central authentication engine used when generating tokens.
- Public Endpoint:
/auth/welcomeaccessible without login. - Registration:
/auth/addNewUsersaves new users with encoded passwords. - Login & Token Generation:
/auth/generateTokenauthenticates credentials and returns JWT. - Role-Based Authorization:
/auth/user/userProfile: requires ROLE_USER./auth/admin/adminProfile: requires ROLE_ADMIN.
@PreAuthorize:Ensures only users with correct roles (from JWT claims) can access endpoints.
@SpringBootApplication:Marks this as the main Spring Boot app and auto-configures everything.SpringApplication.run():Starts the app, loads beans, controllers, filters, and security configuration.- Integration:
- Loads your SecurityConfig
(JWT filter + role-based rules). - Loads your UserController (registration, login, secured endpoints).
- Connects UserInfoService and JwtService for authentication and token management.
- Loads your SecurityConfig
The /generateToken endpoint is the gateway to your secured application. When you send a JSON request with your username and password, Spring Security orchestrates a sophisticated dance between multiple components to verify your identity and issue a cryptographically signed token.
Here is the complete step-by-step flow of how that process works.
- Postman sends a
POSTrequest tohttp://localhost:8080/auth/generateTokenwith a JSON body containingusernameandpassword. UserControllerreceives the request in theauthenticateAndGetToken(@RequestBody AuthRequest authRequest)method.- Inside this method, an unauthenticated
UsernamePasswordAuthenticationTokenobject is created using the credentials from the JSON.
- The controller calls
authenticationManager.authenticate(). AuthenticationManagerdelegates this task to theDaoAuthenticationProvider(configured in yourSecurityConfig).- The
DaoAuthenticationProvidercallsUserInfoService.loadUserByUsername().
- This method calls
UserInfoRepository.findByEmail()to fetch the user from your MySQL database. - The data is wrapped into a
UserInfoDetailsobject (which implementsUserDetails).
- The
DaoAuthenticationProviderthen uses yourBCryptPasswordEncoder(fromPasswordConfig) to compare the raw password from Postman with the hashed password from the database.
- If the password matches, the
AuthenticationManagerreturns a fully authenticatedAuthenticationobject back to theUserController. - The controller checks
authentication.isAuthenticated(). If true, it callsjwtService.generateToken(username). JwtServiceuses the JJWT library to:
- Create a set of Claims.
- Set the expiration time (30 minutes in your code).
- Sign the token using your
SECRETkey viagetSignKey().
- The final Base64-encoded JWT string is returned to the controller.
- The
UserControllersends the JWT string back to Postman as a plain text response.
| Order | Class | Method | Responsibility |
|---|---|---|---|
| 1 | UserController |
authenticateAndGetToken() |
Entry point; starts the process. |
| 2 | AuthenticationManager |
authenticate() |
Orchestrates the auth check. |
| 3 | DaoAuthenticationProvider |
authenticate() |
Compares password & user info. |
| 4 | UserInfoService |
loadUserByUsername() |
Fetches user from DB. |
| 5 | UserInfoRepository |
findByEmail() |
Executes the SQL query. |
| 6 | BCryptPasswordEncoder |
matches() |
Checks if the password is valid. |
| 7 | JwtService |
generateToken() |
Builds and signs the JWT. |
JwtAuthFilter: Even though this endpoint ispermitAll(), the request still passes through this filter. Since there is no "Bearer" token in the header yet, it simply callsfilterChain.doFilter()and moves on.UserInfoDetails: This is the bridge between your JPA Entity (UserInfo) and what Spring Security expects (UserDetails). It handles the logic of converting your comma-separated roles into aList<GrantedAuthority>.SecurityConfig: This class defines that/auth/generateTokendoes NOT require a token to be accessed (via.permitAll()), allowing users to log in for the first time.
Once you have your JWT from the /generateToken endpoint, you use it to access protected resources like /admin/adminProfile. This flow is different because it relies on the JwtAuthFilter to validate the token rather than checking a password against the database.
Here is the step-by-step "Authorization Flow" for a secured request.
- Postman sends a
GETrequest tohttp://localhost:8080/auth/admin/adminProfile. - Crucially, you must include the header:
Authorization: Bearer <your_token_here>. - The request hits the
JwtAuthFilter(yourOncePerRequestFilter).
JwtAuthFilter.doFilterInternal()runs:
- It pulls the
Authorizationheader and checks if it starts with "Bearer ". - It extracts the token and calls
jwtService.extractUsername(token). - It checks the
SecurityContextHolderto see if the user is already authenticated (at this point, it is empty/null).
- If a username exists and the context is empty, it calls
UserInfoService.loadUserByUsername()to get the user's latest roles and details from the database. - The filter calls
jwtService.validateToken(token, userDetails):
- It checks if the username in the token matches the DB.
- It checks
isTokenExpired()by looking at theexpirationclaim inside the JWT.
- If the token is valid, the filter creates a
UsernamePasswordAuthenticationToken.
- Crucial: It includes the
authorities(roles likeROLE_ADMIN) from theuserDetails.
- It calls
SecurityContextHolder.getContext().setAuthentication(authToken).
- Analogy: This is like the security guard at a building checking your ID and then handing you a "Visitor Badge" that says where you are allowed to go.
- The filter calls
filterChain.doFilter(), passing the request to the next step.
- The request reaches the
AuthorizationFilterin Spring's internal chain. - It looks at the
@PreAuthorize("hasAuthority('ROLE_ADMIN')")on youradminProfilemethod. - It checks the
SecurityContextHolderwe just filled. If the context containsROLE_ADMIN, the method executes. If it only containsROLE_USER, you get a 403 Forbidden.
| Order | Class | Method | Responsibility |
|---|---|---|---|
| 1 | JwtAuthFilter |
doFilterInternal() |
Intercepts the request and reads the Header. |
| 2 | JwtService |
extractUsername() |
Decodes the JWT to find out "Who is this?". |
| 3 | UserInfoService |
loadUserByUsername() |
Fetches the user's roles from the DB. |
| 4 | JwtService |
validateToken() |
Checks expiration and integrity. |
| 5 | SecurityContext |
setAuthentication() |
Stores the user's "Badge" for this specific request. |
| 6 | UserController |
adminProfile() |
The method finally runs and returns the "Welcome" string. |
Notice that in your SecurityConfig, you set SessionCreationPolicy.STATELESS.
This means Spring does not create an HTTP Session or store your login on the server. Every single time you call the /adminProfile endpoint, this entire process (extracting, validating, and fetching from DB) happens again. This is why JWT is perfect for scaling microservices—the server doesn't have to "remember" who you are; the token carries all the proof.
Send a POST request to http://localhost:8080/auth/addNewUser:
{
"name": "John Doe",
"email": "john@example.com",
"password": "password123",
"roles": "ROLE_USER"
}Send a POST request to http://localhost:8080/auth/generateToken:
{
"username": "john@example.com",
"password": "password123"
}Copy the long string returned as the response.
Try to access GET http://localhost:8080/auth/user/userProfile. It will fail with 403 Forbidden initially. Now, add the Authorization header:
Key: Authorization
Value: Bearer <paste_your_token_here>
Result: "Welcome to the User Profile! Your JWT is valid."

