Spring Security
: A powerful security framework that provides authentication, authorization, and protection against common attacks (e.g., CSRF).
JWT (JSON Web Token)
: Used to securely transmit information between client and server as a JSON object. It is widely used for stateless authentication in RESTful services.
Angular
: We use Angular for the frontend where we implement the login form and manage user sessions using JWT.
BCrypt
: For securely hashing passwords before storing them in the database.
Spring Boot
: The framework that simplifies Spring application development, making it quick to build RESTful services.
pom.xml
Dependencies:We need specific dependencies for Spring Security, JWT, and password hashing (BCrypt). Below is the pom.xml file setup:
The User entity represents users in the system. We include a password field for authentication.
package com.emsi.serviceSecurity.models;
import jakarta.persistence.*;
@Entity
@Table(name = "users") // Optional: specify the table name if needed
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true) // Ensures username is unique and not null
private String username;
@Column(nullable = false) // Ensures password is not null
private String password;
// Default constructor
public User() {
}
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
UserRepository
package com.emsi.serviceSecurity.repositories;
import org.springframework.data.jpa.repository.JpaRepository;
import com.emsi.serviceSecurity.models.User;
public interface UserRepository extends JpaRepository {
User findByUsername(String username);
}
JWT Utility Class
generateToken: Creates a token based on the user's email.
validateToken
: Ensures that the token is valid and not expired.
extractUsername
: Retrieves the username from the token.
generating and validating JWT tokens.
/*
* The JwtUtil class is responsible for handling JSON Web Tokens (JWT) in your application. It provides methods for generating, extracting information from, and validating JWTs. This utility class is essential for implementing authentication and securing API endpoints using JWTs.
*/
package com.emsi.serviceSecurity.utils;
// Import necessary classes from the JWT library and Java utilities.
import io.jsonwebtoken.*;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
// The @Component annotation indicates that this class is a Spring-managed bean.
@Component
public class JwtUtil {
// Generate a secure key for signing the JWT using the HS256 algorithm.
private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256); // Secure key for signing JWTs
// Define the expiration time for the token (1 hour in milliseconds).
private final long EXPIRATION_TIME = 1000 * 60 * 60; // 1 hour
// Generate a token for the given username.
public String generateToken(String username) {
// Create an empty claims map for additional information if needed.
Map claims = new HashMap<>();
// Call the createToken method to create a JWT with the provided claims and username.
return createToken(claims, username);
}
// Create a JWT using claims and a subject (username).
private String createToken(Map claims, String subject) {
// Build the JWT using the Jwts builder with claims, subject, issued date, expiration date, and signing key.
return Jwts.builder()
.setClaims(claims) // Set additional claims (if any)
.setSubject(subject) // Set the subject (username)
.setIssuedAt(new Date(System.currentTimeMillis())) // Set the token issue date
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // Set expiration date
.signWith(SignatureAlgorithm.HS256, SECRET_KEY) // Sign the token with the secret key
.compact(); // Compact the JWT into a string
}
// Extract the username from the provided token.
public String extractUsername(String token) {
// Call the method to extract all claims from the token and get the subject.
return extractAllClaims(token).getSubject();
}
// Extract all claims from the provided token.
private Claims extractAllClaims(String token) {
// Parse the token using the signing key and return the claims body.
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
// Validate the token by checking the extracted username and whether the token is expired.
public boolean validateToken(String token, String username) {
// Extract the username from the token.
final String extractedUsername = extractUsername(token);
// Check if the extracted username matches the provided username and if the token is not expired.
return (extractedUsername.equals(username) && !isTokenExpired(token));
}
// Check if the token is expired.
private boolean isTokenExpired(String token) {
// Get the expiration date from the token claims and compare it with the current date.
return extractAllClaims(token).getExpiration().before(new Date());
}
}
JwtRequestFilter
/*Its primary purpose is to intercept incoming HTTP requests to check for a JWT (JSON Web Token) in the Authorization header. If a valid JWT is found, it extracts the username from the token, retrieves the corresponding user details, and sets the authentication in the Spring Security context, allowing the user to access protected resources.
* */
package com.emsi.serviceSecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
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.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import com.emsi.serviceSecurity.utils.JwtUtil;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
//This annotation indicates that this class is a Spring component, allowing it to be
//automatically detected and managed by Spring's application context.
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
// Automatically inject the JwtUtil class, which provides utility methods for
// working with JWTs, such as extracting information and validating tokens.
@Autowired
private JwtUtil jwtUtil;
// Automatically inject the UserDetailsService, which is used to retrieve user
// details from the database based on the username.
@Autowired
private UserDetailsService userDetailsService;
// The doFilterInternal method is overridden to perform filtering logic on each
// incoming HTTP request.
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
// Retrieve the "Authorization" header from the incoming request.
final String authorizationHeader = request.getHeader("Authorization");
// Initialize variables to hold the username and JWT token.
String username = null;
String jwt = null;
// Check if the Authorization header is present and starts with "Bearer ".
// This is the standard format for passing JWTs.
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
// Extract the JWT token from the header (removing the "Bearer " prefix).
jwt = authorizationHeader.substring(7);
// Use the JwtUtil class to extract the username from the JWT.
username = jwtUtil.extractUsername(jwt);
}
// If a username is extracted and there is no existing authentication in the context,
// proceed to validate the token and set the authentication.
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// Load user details from the database using the username.
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// Validate the token to ensure it is still valid for the user.
if (jwtUtil.validateToken(jwt, userDetails.getUsername())) {
// If the token is valid, create an authentication object.
var authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
// Set additional details in the authentication object (e.g., IP address).
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// Set the authentication object in the SecurityContext, allowing
// Spring Security to recognize the user as authenticated.
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
// Continue the filter chain, allowing the request to proceed to the next filter
// or the requested resource.
chain.doFilter(request, response);
}
}
SecurityConfig
/*
* It sets up the security filters, authentication management, and access rules for the application. By extending WebSecurityConfigurerAdapter, it allows customization of the security settings. In this class, you define how the application should handle authentication, which endpoints are public, and how JWT authentication is processed.
*/
package com.emsi.serviceSecurity.config;
// Import necessary Spring Security and Servlet classes for configuration.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.emsi.serviceSecurity.services.CustomUserDetailsService;
import jakarta.servlet.http.HttpServletResponse;
// The @Configuration annotation indicates that this class is a source of bean definitions.
// The @EnableWebSecurity annotation enables Spring Security’s web security support.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// Automatically inject the custom UserDetailsService for loading user-specific data.
@Autowired
private CustomUserDetailsService userDetailsService;
// Automatically inject the JwtRequestFilter to handle JWT validation for incoming requests.
@Autowired
private JwtRequestFilter jwtRequestFilter;
// This bean provides a password encoder that will be used to encode passwords.
@Bean
public PasswordEncoder passwordEncoder() {
// Return a new instance of BCryptPasswordEncoder, a strong password hashing algorithm.
return new BCryptPasswordEncoder();
}
// This bean defines the security filter chain, which contains the security configurations.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// Disable CSRF protection as it is not required for stateless APIs using JWT.
http.csrf().disable()
// Configure which requests are authorized.
.authorizeRequests()
.requestMatchers("/auth/login", "/auth/register").permitAll() // Allow public access to login and register endpoints.
.anyRequest().authenticated() // Require authentication for all other requests.
.and()
// Configure exception handling for unauthorized requests.
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> {
// Send an HTTP 401 Unauthorized response when authentication fails.
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
})
.and()
// Configure session management to be stateless, as we are using JWT for authentication.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add the JWT request filter before the UsernamePasswordAuthenticationFilter to handle JWT validation.
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
// Build and return the security filter chain.
return http.build();
}
// This bean provides an AuthenticationManager for managing authentication.
@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
// Get the shared AuthenticationManagerBuilder from the HttpSecurity context.
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
// Set the custom UserDetailsService and the password encoder for the authentication manager.
authenticationManagerBuilder
.userDetailsService(userDetailsService) // Use the custom UserDetailsService for user data retrieval.
.passwordEncoder(passwordEncoder()); // Use the BCrypt password encoder for password matching.
// Build and return the AuthenticationManager.
return authenticationManagerBuilder.build();
}
}
AuthController Controller
This REST controller exposes the login API, handles login requests, and returns the JWT token if the credentials are valid.
/*The AuthController class is a Spring REST controller responsible for handling user authentication-related operations such as registration, login, and logout. It utilizes JWT for authentication and BCrypt for password hashing to ensure secure handling of user credentials.
* */
package com.emsi.serviceSecurity.controllers;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired; // Allows for dependency injection
import org.springframework.http.HttpStatus; // Represents HTTP status codes
import org.springframework.http.ResponseEntity; // Used to return HTTP responses
import org.springframework.security.crypto.password.PasswordEncoder; // Interface for password encoding
import org.springframework.web.bind.annotation.*; // Contains annotations for RESTful web services
import com.emsi.serviceSecurity.models.User; // User model class
import com.emsi.serviceSecurity.repositories.UserRepository; // Repository for User data access
import com.emsi.serviceSecurity.utils.JwtUtil; // Utility class for JWT operations
@CrossOrigin(origins = "http://localhost:63828") // Allows requests from specified origin
@RestController // Indicates that this class is a REST controller
@RequestMapping("/auth") // Maps all requests under /auth to this controller
public class AuthController {
@Autowired
private UserRepository userRepository; // Injected repository for accessing user data
@Autowired
private JwtUtil jwtUtil; // Injected JWT utility for token generation and validation
private final PasswordEncoder passwordEncoder; // Inject PasswordEncoder for hashing passwords
// Constructor to initialize userRepository and passwordEncoder
public AuthController(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository; // Assign UserRepository
this.passwordEncoder = passwordEncoder; // Assign PasswordEncoder
}
// Handles user registration
@PostMapping("/register") // Maps POST requests to /auth/register
public ResponseEntity
Angular
AuthService Service for Authentication
The AuthService handles login, storing tokens, and checking authentication status.import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private baseUrl = 'http://localhost:8080/auth'; // Base URL for your backend API
private jwtToken: string | null = null; // Store JWT token
constructor(private http: HttpClient) {}
register(user: any): Observable {
return this.http.post(`${this.baseUrl}/register`, user).pipe(
catchError((error) => {
// Handle registration error
console.error('Registration error:', error);
return throwError(() => new Error(error.error?.message || 'Registration failed'));
})
);
}
login(user: any): Observable {
// Specify responseType as 'text' to avoid JSON parsing errors
return this.http.post(`${this.baseUrl}/login`, user, { responseType: 'text' as 'json' }).pipe(
catchError((error) => {
// Handle login error
console.error('Login error:', error);
return throwError(() => new Error(error.error?.message || 'Login failed'));
})
);
}
setToken(token: string): void {
this.jwtToken = token; // Store the JWT token
localStorage.setItem('token', token); // Optionally store it in local storage
}
getToken(): string | null {
return this.jwtToken || localStorage.getItem('token'); // Retrieve the token
}
logout(): void {
this.jwtToken = null; // Clear the stored token
localStorage.removeItem('token'); // Remove it from local storage
console.log('User logged out successfully!');
}
isLoggedIn(): boolean {
const token = this.getToken();
// Check if the token exists and is not expired (you can implement additional checks here)
return !!token;
}
}
The AuthGuard protects certain routes by checking if the user is authenticated.
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) { }
// This function determines whether the route can be activated
canActivate(): boolean {
if (this.authService.isAuthenticated()) {
return true; // Allow access to the route if the user is authenticated
} else {
this.router.navigate(['/login']); // Redirect to login if not authenticated
return false;
}
}
}
App.module.ts )
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { FormsModule } from '@angular/forms'; // Import FormsModule
import { HttpClientModule } from '@angular/common/http';
import { LoginComponent } from './components/login/login.component';
import { RegisterComponent } from './components/register/register.component';
@NgModule({
declarations: [
AppComponent,
LoginComponent,
RegisterComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }