JWT json web token - used for authentication
instead of storing login sessions on the server, the client sends token with every request
flow
User Login -> Server verifies credentials -> Server creates JWT -> Clients stores JWT
Client sends JWT in request -> Server verifies and grants access
Authorization: Bearer token
Jwt structure
HEADER.PAYLOAD.SIGNATURE jwt.io
Header contains algorithm:
{
"alg":"HS256"
"typ":"JWT"
}
payload :
{
"sub":"a@c.com",
"iat":"8765456,
"exp":98765456
}
Signature: created using a scret key
Dependecies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
config in properties
# JWT Configuration
app.jwt.secret=856B4FEB2CF84D145A231D7A28B41856B4FEB2CF84D145A231D7A28B41856B4FEB2CF84D145A231D7A28B41
app.jwt.expiration=3600000
app.jwt.refresh-expiration=86400000
Create user
User entity
User.java
package com.grazac.wisdom.user;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import java.time.LocalDateTime;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "application_user", indexes = {
@Index(name = "idx_user_email", columnList = "email")
})
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(
name = "full_name",
nullable = false
)
private String fullName;
@Column(
name = "email",
unique = true,
nullable = false
)
private String email;
@Column(
nullable = false,
unique = true
)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = true)
private String profilePic;
@Column(nullable = true)
private String accountNumber;
@Enumerated(EnumType.STRING)
private Role role;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
public User(String fullName, String username, String password, Role role) {
this.fullName = fullName;
this.username = username;
this.password = password;
this.role = role;
}
}
Role.enum
package com.grazac.wisdom.user;
public enum Role {
ROLE_USER,
ROLE_ADMIN
}
UserRepository
package com.grazac.wisdom.user;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Boolean existsByUsername(String username);
Optional<User> findByEmail(String username);
}
CurrentUser
@Component // Marks this class as a Spring Bean so Spring can automatically detect and manage it
public class CurrentUserUtil {
// Repository used to fetch the User entity from the database
private final UserRepository userRepository;
// Constructor injection: Spring automatically injects the UserRepository bean here
public CurrentUserUtil(UserRepository userRepository) {
this.userRepository = userRepository;
}
// Method to get the username of the currently authenticated user
public String getLoggedInUsername() {
// SecurityContextHolder stores security information for the current request/thread
// getContext() returns the current security context
// getAuthentication() returns the authentication object of the logged-in user
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// getName() returns the username (principal name) of the authenticated user
return authentication.getName();
}
// Method to get the full User entity of the currently logged-in user
public User getLoggedInUser() {
// Get the username from the security context
String username = getLoggedInUsername();
// Search for the user in the database using the username
return userRepository.findByUsername(username)
// If the user is not found, throw a custom exception
.orElseThrow(() -> new UserNotFoundException("User not found: " + username));
}
}
in utils
ApiResponse.java
import lombok.Data;
@Data
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
private ApiResponse(boolean success, String message, T data) {
this.success = success;
this.message = message;
this.data = data;
}
public static <T> ApiResponse<T> success(String message, T data) {
return new ApiResponse<>(true, message, data);
}
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(true, "Request successful", data);
}
public static <T> ApiResponse<T> fail(String message) {
return new ApiResponse<>(false, message, null);
}
public static <T> ApiResponse<T> fail(String message, T data) {
return new ApiResponse<>(false, message, data);
}
}
CustomDetailsService.java
package com.grazac.wisdom.user;
// Declares the package where this class belongs
import org.springframework.security.core.GrantedAuthority;
// Represents a permission or role granted to a user
import org.springframework.security.core.authority.SimpleGrantedAuthority;
// A basic implementation of GrantedAuthority used for roles like ROLE_ADMIN
import org.springframework.security.core.userdetails.UserDetails;
// Spring Security interface that represents an authenticated user
import org.springframework.security.core.userdetails.UserDetailsService;
// Interface used by Spring Security to load user-specific data during login
import org.springframework.security.core.userdetails.UsernameNotFoundException;
// Exception thrown when a user cannot be found during authentication
import org.springframework.stereotype.Service;
// Marks this class as a Spring service bean so it can be automatically detected
import java.util.Collection;
// Represents a group of objects (used for authorities)
import java.util.List;
// Used to create a list of authorities
@Service
// Registers this class as a Spring-managed bean so Spring Security can use it
public class CustomDetailsService implements UserDetailsService {
// Repository used to fetch users from the database
private final UserRepository userRepository;
// Constructor injection: Spring automatically injects UserRepository here
public CustomDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
// Method required by UserDetailsService
// Spring Security calls this method automatically during login
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Search for the user in the database using email
// If the user does not exist, throw UsernameNotFoundException
User user = userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
// Return a Spring Security User object
// This object contains authentication information used by Spring Security
return new org.springframework.security.core.userdetails.User(
// Username used by Spring Security (principal)
user.getUsername(),
// Password stored in the database (usually encrypted with BCrypt)
user.getPassword(),
// List of roles/authorities granted to the user
getAuthority(user)
);
}
// Method to convert the user's role into a Spring Security authority
private Collection<? extends GrantedAuthority> getAuthority(User user) {
// Create an authority using the role name from the User entity
// Example: ROLE_ADMIN or ROLE_USER
GrantedAuthority authority = new SimpleGrantedAuthority(user.getRole().name());
// Return the authority as a list (Spring expects a collection)
return List.of(authority);
}
}
Token Pair
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class TokenPair {
private String accessToken;
private String refreshToken;
public TokenPair() {
}
public TokenPair(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
}
JwtService.java
package com.grazac.wisdom.user;
// Defines the package where this class belongs
import dto.TokenPair;
// Custom DTO used to return both access token and refresh token together
import io.jsonwebtoken.*;
// JWT library classes for building and parsing tokens
import io.jsonwebtoken.io.Decoders;
// Utility class for decoding Base64 encoded secrets
import io.jsonwebtoken.security.Keys;
// Used to generate cryptographic signing keys
import lombok.extern.slf4j.Slf4j;
// Lombok annotation that automatically creates a logger
import org.springframework.beans.factory.annotation.Value;
// Used to inject values from application.properties or application.yml
import org.springframework.security.core.Authentication;
// Represents the authenticated user in Spring Security
import org.springframework.security.core.userdetails.UserDetails;
// Interface representing user information in Spring Security
import org.springframework.stereotype.Service;
// Marks this class as a Spring service bean
import javax.crypto.SecretKey;
// Represents the secret key used to sign the JWT
import java.util.Date;
// Used for token creation time and expiration time
import java.util.HashMap;
import java.util.Map;
@Service
// Registers this class as a Spring-managed service
@Slf4j
// Adds a logger automatically (log.info(), log.error(), etc.)
public class JwtService {
// Inject the JWT secret key from application.properties
@Value("${app.jwt.secret}")
private String jwtSecret;
// Access token expiration time (milliseconds)
@Value("${app.jwt.expiration}")
private long jwtExpirationMs;
// Refresh token expiration time (milliseconds)
@Value("${app.jwt.refresh-expiration}")
private long refreshExpirationMs;
// Generates both access and refresh tokens for a user
public TokenPair generateTokenPair(Authentication authentication) {
// Create access token
String accessToken = generateAccessToken(authentication);
// Create refresh token
String refreshToken = generateRefreshToken(authentication);
// Return both tokens wrapped in a DTO
return new TokenPair(accessToken, refreshToken);
}
// Generate short-lived access token
public String generateAccessToken(Authentication authentication) {
// Calls general token generator with normal expiration
return generateToken(authentication, jwtExpirationMs, new HashMap<>());
}
// Generate refresh token (longer expiration)
public String generateRefreshToken(Authentication authentication) {
// Create claims map to store extra data in the token
Map<String, String> claims = new HashMap<>();
// Mark token as refresh token
claims.put("tokenType", "refresh");
// Generate token with refresh expiration time
return generateToken(authentication, refreshExpirationMs, claims);
}
// Generic method used to generate any token
public String generateToken(Authentication authentication, long expirationInMs, Map<String, String> claims) {
// Extract logged-in user from authentication object
UserDetails userPrincipal = (UserDetails) authentication.getPrincipal();
// Current time
Date now = new Date();
// Token expiration time
Date expiryDate = new Date(now.getTime() + expirationInMs);
// Build the JWT token
return Jwts.builder()
// Add header information
.header()
.add("typ", "JWT")
.and()
// Subject is usually the username
.subject(userPrincipal.getUsername())
// Add custom claims (e.g. tokenType)
.claims(claims)
// Time token was issued
.issuedAt(now)
// Expiration time
.expiration(expiryDate)
// Sign the token using the secret key
.signWith(getSignInKey())
// Build the token string
.compact();
}
// Validate token by checking username matches the user
public boolean validateTokenForUser(String token, UserDetails userDetails) {
// Extract username stored in the token
final String username = extractUsernameFromToken(token);
// Token is valid if username matches the user
return username != null
&& username.equals(userDetails.getUsername());
}
// Checks if token is valid (not malformed or expired)
public boolean isValidToken(String token) {
// If claims can be extracted, token is valid
return extractAllClaims(token) != null;
}
// Extract username from token
public String extractUsernameFromToken(String token) {
// Get all token claims
Claims claims = extractAllClaims(token);
if(claims != null) {
// Subject contains the username
return claims.getSubject();
}
return null;
}
// Check if token is specifically a refresh token
public boolean isRefreshToken(String token) {
Claims claims = extractAllClaims(token);
if(claims == null) {
return false;
}
// Check custom claim "tokenType"
return "refresh".equals(claims.get("tokenType"));
}
// Extract all claims from the token
private Claims extractAllClaims(String token) {
Claims claims = null;
try {
// Parse and validate the JWT
claims = Jwts.parser()
.verifyWith(getSignInKey()) // verify using secret key
.build()
.parseSignedClaims(token) // parse token
.getPayload(); // extract claims
} catch (JwtException | IllegalArgumentException e) {
// Token invalid or malformed
throw new RuntimeException(e);
}
return claims;
}
// Generate signing key from secret
private SecretKey getSignInKey() {
// Decode the base64 secret
byte[] keyBytes = Decoders.BASE64.decode(jwtSecret);
// Create HMAC SHA signing key
return Keys.hmacShaKeyFor(keyBytes);
}
// Calculate how much time remains before token expires
public long getRemainingValidity(String token) {
Claims claims = extractAllClaims(token);
// Return remaining milliseconds before expiration
return claims.getExpiration().getTime() - System.currentTimeMillis();
}
}
RegisterRequest.java
package dto;
import com.grazac.wisdom.user.Role;
import lombok.*;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class RegisterRequest {
private String fullName;
private String email;
private String password;
private Role role;
}
LoginRequest
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginRequest {
private String username;
private String password;
}
RefreshTokenRequest
@Data
@AllArgsConstructor
public class RefreshTokenRequest {
private String refreshToken;
}
SecurityConfig.java
package com.grazac.wisdom.utils;
import com.grazac.wisdom.filters.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
private final UserDetailsService userDetailsService;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfig(UserDetailsService userDetailsService, JwtAuthenticationFilter jwtAuthenticationFilter) {
this.userDetailsService = userDetailsService;
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
public static final String[] WHITE_LIST_URL = {
"/togglz-console", "/togglz-console/**",
"/api/terminals/**",
"/api/courses/**",
"/ws/**",
"/api/posts",
"/electronics/**", // FIXED
"/activity/**",
"/actuator/**",
"/login/**",
"/booking/**",
"/login/oauth2/code/google",
"/api/auth/**",
"/api/orders/**",
"/api/sample/**",
"/api/external/**",
"/info",
"/blogs/**",
"/posts/**",
"/v2/api-docs",
"/v3/api-docs",
"/v3/api-docs/**",
"/swagger-resources",
"/swagger-resources/**",
"/configuration/ui",
"/configuration/security",
"/swagger-ui/**",
"/webjars/**",
"/swagger-ui.html",
"/error"
};
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors(c -> c.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(
req -> req
.requestMatchers(WHITE_LIST_URL).permitAll()
.anyRequest().authenticated()
)
.sessionManagement(
session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
).addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.authenticationProvider(authenticationProvider());
return http.build();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of(
"http://localhost:63342",
"http://localhost:3000",
"https://secsystem-emr.vercel.app",
"https://emr-sigma-ten.vercel.app",
"Postman"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("Authorization", "Content-Type"));
configuration.setAllowCredentials(true); // Allow cookies or Authorization headers
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
JwtAuthenticationFilter.java
package com.grazac.wisdom.utils;
// Defines the package where this configuration class belongs
import com.grazac.wisdom.filters.JwtAuthenticationFilter;
// Custom filter that reads JWT from request and authenticates user
import org.springframework.context.annotation.Bean;
// Used to define Spring-managed beans
import org.springframework.context.annotation.Configuration;
// Marks this class as a Spring configuration class
import org.springframework.security.authentication.AuthenticationManager;
// Main interface responsible for processing authentication
import org.springframework.security.authentication.AuthenticationProvider;
// Strategy interface used by Spring Security to authenticate users
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
// Authentication provider that retrieves user details from database
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
// Provides access to the AuthenticationManager built by Spring
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
// Enables method-level security annotations like @PreAuthorize
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
// Main class used to configure web security
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
// Enables Spring Security for the web application
import org.springframework.security.config.http.SessionCreationPolicy;
// Defines how sessions should be handled
import org.springframework.security.core.userdetails.UserDetailsService;
// Service used to load user details from database
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
// Password encoder implementation using BCrypt hashing
import org.springframework.security.crypto.password.PasswordEncoder;
// Interface for password encoding
import org.springframework.security.web.SecurityFilterChain;
// Represents the security filter chain that processes requests
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
// Default filter used for username/password authentication
import org.springframework.web.client.RestTemplate;
// Spring HTTP client (not used in this class but imported)
import org.springframework.web.cors.CorsConfiguration;
// Defines Cross-Origin Resource Sharing configuration
import org.springframework.web.cors.CorsConfigurationSource;
// Provides CORS configuration to Spring Security
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
// Maps CORS configuration to URL patterns
import java.util.List;
// Used to create lists
@Configuration
// Marks this as a Spring configuration class
@EnableWebSecurity
// Enables Spring Security configuration for web applications
@EnableMethodSecurity
// Allows method-level security annotations like:
// @PreAuthorize, @PostAuthorize, @Secured
public class SecurityConfig {
// Service used to load user details from database
private final UserDetailsService userDetailsService;
// Custom filter that validates JWT tokens in requests
private final JwtAuthenticationFilter jwtAuthenticationFilter;
// Constructor injection: Spring automatically injects these beans
public SecurityConfig(UserDetailsService userDetailsService, JwtAuthenticationFilter jwtAuthenticationFilter) {
this.userDetailsService = userDetailsService;
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
// List of API endpoints that DO NOT require authentication
public static final String[] WHITE_LIST_URL = {
"/togglz-console", "/togglz-console/**",
"/api/terminals/**",
"/api/courses/**",
"/ws/**",
"/api/posts",
"/electronics/**",
"/activity/**",
"/actuator/**",
"/login/**",
"/booking/**",
"/login/oauth2/code/google",
"/api/auth/**",
"/api/orders/**",
"/api/sample/**",
"/api/external/**",
"/info",
"/blogs/**",
"/posts/**",
// Swagger API documentation endpoints
"/v2/api-docs",
"/v3/api-docs",
"/v3/api-docs/**",
"/swagger-resources",
"/swagger-resources/**",
"/configuration/ui",
"/configuration/security",
"/swagger-ui/**",
"/webjars/**",
"/swagger-ui.html",
"/error"
};
// Bean for password encoding using BCrypt
@Bean
public PasswordEncoder passwordEncoder(){
// BCrypt automatically salts and hashes passwords
return new BCryptPasswordEncoder();
}
// Authentication provider that uses database user details
@Bean
public AuthenticationProvider authenticationProvider() {
// DaoAuthenticationProvider loads user details from UserDetailsService
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(userDetailsService);
// Set password encoder to verify hashed passwords
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
// AuthenticationManager bean used for login authentication
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
// Returns the default authentication manager configured by Spring
return authConfig.getAuthenticationManager();
}
// Main security configuration
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// Enable CORS using custom configuration
.cors(c -> c.configurationSource(corsConfigurationSource()))
// Disable CSRF (safe for stateless APIs using JWT)
.csrf(csrf -> csrf.disable())
// Authorization rules
.authorizeHttpRequests(
req -> req
// Allow public endpoints
.requestMatchers(WHITE_LIST_URL).permitAll()
// Any other request must be authenticated
.anyRequest().authenticated()
)
// Configure session management
.sessionManagement(
session -> session
// Stateless means no HTTP session is stored
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// Add JWT authentication filter BEFORE default login filter
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
// Use our custom authentication provider
.authenticationProvider(authenticationProvider());
// Build and return the security filter chain
return http.build();
}
// Configure CORS (Cross-Origin Resource Sharing)
@Bean
CorsConfigurationSource corsConfigurationSource() {
// Create CORS configuration
CorsConfiguration configuration = new CorsConfiguration();
// Allowed frontend origins
configuration.setAllowedOrigins(List.of(
"http://localhost:63342",
"http://localhost:3000",
"https://secsystem-emr.vercel.app",
"https://emr-sigma-ten.vercel.app",
"Postman"
));
// Allowed HTTP methods
configuration.setAllowedMethods(List.of(
"GET",
"POST",
"PUT",
"DELETE",
"OPTIONS"
));
// Allowed headers from client
configuration.setAllowedHeaders(List.of(
"Authorization",
"Content-Type"
));
// Allow cookies or Authorization headers
configuration.setAllowCredentials(true);
// Map CORS configuration to all endpoints
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
AuthService
package com.grazac.wisdom.service;
import com.grazac.wisdom.exceptions.ConflictException;
import com.grazac.wisdom.user.CurrentUserUtil;
import com.grazac.wisdom.user.JwtService;
import com.grazac.wisdom.user.User;
import com.grazac.wisdom.user.UserRepository;
import dto.LoginRequest;
import dto.RefreshTokenRequest;
import dto.RegisterRequest;
import dto.TokenPair;
import jakarta.transaction.Transactional;
import lombok.AllArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@Service
@AllArgsConstructor
public class AuthService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final AuthenticationManager authenticationManager;
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
private final CurrentUserUtil currentUserUtil;
@Transactional
public void registerUser(RegisterRequest registerRequest) {
// Check if user with the same username already exist
if(userRepository.existsByUsername(registerRequest.getEmail())) {
throw new ConflictException("Username is already in use");
}
// Create new user
User user = User
.builder()
.createdAt(LocalDateTime.now())
.fullName(registerRequest.getFullName())
.username(registerRequest.getEmail())
.email(registerRequest.getEmail())
.password(passwordEncoder.encode(registerRequest.getPassword()))
.role(registerRequest.getRole())
.build();
userRepository.save(user);
}
public TokenPair login(LoginRequest loginRequest) {
// Ask Spring Security to authenticate the user
Authentication authentication = authenticationManager.authenticate(
// Create an authentication request object containing username and password
new UsernamePasswordAuthenticationToken(
// Username from the login request
loginRequest.getUsername(),
// Password from the login request
loginRequest.getPassword()
)
);
// If authentication succeeds, store the authentication object
// in Spring Security's SecurityContext (represents the logged-in user)
SecurityContextHolder.getContext().setAuthentication(authentication);
// Generate both Access Token and Refresh Token for the authenticated user
return jwtService.generateTokenPair(authentication);
}
public TokenPair refreshToken(RefreshTokenRequest request) {
// Extract the refresh token sent by the client
String refreshToken = request.getRefreshToken();
// Check if the token is actually a refresh token
// (your JwtService checks the claim "tokenType = refresh")
if(!jwtService.isRefreshToken(refreshToken)) {
// If not a refresh token, reject the request
throw new IllegalArgumentException("Invalid refresh token");
}
// Extract the username stored inside the token
String user = jwtService.extractUsernameFromToken(refreshToken);
// Load the user details from the database
UserDetails userDetails = userDetailsService.loadUserByUsername(user);
// If user cannot be found, stop the process
if (userDetails == null) {
throw new IllegalArgumentException("User not found");
}
// Create an authentication object manually
// (this represents an authenticated user without needing password)
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
// Authenticated user
userDetails,
// No password required since token already verified
null,
// User roles/authorities
userDetails.getAuthorities()
);
// Generate a new access token using the authenticated user
String accessToken = jwtService.generateAccessToken(authentication);
// Return the new access token and the existing refresh token
return new TokenPair(accessToken, refreshToken);
}
}
AuthController
package com.grazac.wisdom;
import com.grazac.wisdom.service.AuthService;
import com.grazac.wisdom.utils.ApiResponse;
import dto.LoginRequest;
import dto.RefreshTokenRequest;
import dto.RegisterRequest;
import dto.TokenPair;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthService authService;
public AuthController(AuthService authService) {
this.authService = authService;
}
@PostMapping("/register")
public ResponseEntity<ApiResponse<String>> registerUser(@RequestBody RegisterRequest request) {
// Save the new user to the database and return success response.
authService.registerUser(request);
return ResponseEntity.ok(ApiResponse.success("User registered successfully"));
}
@PostMapping("/login")
public ResponseEntity<ApiResponse<Object>> login(@RequestBody LoginRequest loginRequest) {
TokenPair tokenPair = authService.login(loginRequest);
return ResponseEntity.ok(ApiResponse.success(tokenPair));
}
@PostMapping("/refresh-token")
public ResponseEntity<?> refreshToken( @RequestBody RefreshTokenRequest request) {
TokenPair tokenPair = authService.refreshToken(request);
return ResponseEntity.ok(tokenPair);
}
}
Top comments (0)