DEV Community

DrSimple
DrSimple

Posted on

Java Security

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>
Enter fullscreen mode Exit fullscreen mode

config in properties


# JWT Configuration
app.jwt.secret=856B4FEB2CF84D145A231D7A28B41856B4FEB2CF84D145A231D7A28B41856B4FEB2CF84D145A231D7A28B41
app.jwt.expiration=3600000
app.jwt.refresh-expiration=86400000

Enter fullscreen mode Exit fullscreen mode

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;
    }
}

Enter fullscreen mode Exit fullscreen mode

Role.enum

package com.grazac.wisdom.user;

public enum Role {
    ROLE_USER,
    ROLE_ADMIN
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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));
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}

Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

LoginRequest

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginRequest {
    private String username;
    private String password;
}
Enter fullscreen mode Exit fullscreen mode

RefreshTokenRequest

@Data
@AllArgsConstructor
public class RefreshTokenRequest {
    private String refreshToken;
}
Enter fullscreen mode Exit fullscreen mode

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;
    }


}
Enter fullscreen mode Exit fullscreen mode

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;
    }

}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }

}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)