Spring Security With JWT

1. How to implement Spring Security With JWT – Complete Solution

What is Spring Security

Spring Security With JWT is a robust and flexible framework that provides authentication, authorization, and other security features for Java applications. It is part of the larger Spring ecosystem and is designed to integrate seamlessly with Spring applications.

  1. Authentication: Spring Security with JWT supports a variety of authentication methods, including form-based login, HTTP Basic, OAuth2, and more. It can authenticate users against various backends, such as databases, LDAP directories, and in-memory user stores.
  2. Authorization: It provides fine-grained access control through configuration, annotations, and expressions. You can define security rules based on roles, user attributes, and other criteria, ensuring that only authorized users can access certain resources.
  3. Cross-Site Request Forgery (CSRF) Protection: It includes built-in protection against CSRF attacks, ensuring that requests are only executed when they originate from a trusted source.
  4. Password Management: It provides utilities for securely hashing and storing passwords, as well as password policy enforcement, ensuring user credentials are protected.
  5. Security for APIs: It supports securing RESTful APIs through OAuth2 and JWT (JSON Web Token), making it ideal for modern web applications and microservices architectures.


JSON Web Token (JWT)

JWT is an open standard (RFC 7519) for securely transmitting information between parties as a JSON object. The information can be verified and trusted because it is digitally signed. JWTs are commonly used for authentication and authorization purposes in web applications and APIs.

  • Self-contained: JWTs contain all the necessary information about a user or claim within the token itself, reducing the need to query a database multiple times. This self-containment also makes JWTs stateless, as they don’t rely on server-side sessions.
  • Secure: JWTs are signed using a cryptographic algorithm to ensure that the claims cannot be altered after the token is issued. JWTs can be signed using either a secret (with HMAC algorithm) or a public/private key pair (using RSA or ECDSA algorithms).

Structure of a JWT

A JWT consists of three parts, separated by dots (.), which are:

  1. Header: The header typically consists of two parts: the type of token (which is JWT) and the signing algorithm being used, such as HMAC SHA256 or RSA.
{
"alg": "HS256",
"typ": "JWT"
}

2. Payload: The payload contains the claims. Claims are statements about an entity (typically, the user) and additional data.

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

3. Signature: To create the signature part, the encoded header, the encoded payload, and a secret key (or a public/private key pair) are used with the algorithm specified in the header to create a signature. This signature is used to verify that the sender of the JWT is who it says it is and to ensure that the message wasn’t changed along the way.

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

How JWT Works

  1. User Authentication: When a user logs in, the server validates the credentials and issues a JWT signed with a secret key or a private key.
  2. Client Stores JWT: The client stores the JWT (typically in local storage or a cookie).
  3. Client Sends JWT with Requests: For subsequent requests to protected routes or resources, the client sends the JWT along with the HTTP request in the Authorization header.
  4. Server Validates JWT: The server verifies the JWT’s signature using the secret or public key. If the token is valid, the server processes the request; otherwise, it responds with an error.
  5. Token Expiration: JWTs usually have an expiration date (exp claim) to limit their validity period. After expiration, the user must log in again to obtain a new token.

How to implement Spring Security With JWT in Project

Project Story: Covid19 REST API

The Covid19 REST API with Spring Security With JWT is a project designed to provide a set of dummy APIs for managing and accessing COVID-19 data. The application supports two types of users:

  • Admin: Admin users have full access to the API, allowing them to manage data, such as adding, updating, or deleting COVID-19 statistics.
  • User: Regular users have limited access and can only retrieve COVID-19 data for informational purposes.

Required code components list to implement Spring Security With JWT in Project

Covid19-REST-API/
│
├── pom.xml                                   // Maven configuration file
│
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── api
│   │   │           ├── config
│   │   │           │   ├── JwtAuthenticationFilter.java   // Filter for JWT authentication
│   │   │           │   └── SecurityConfig.java            // Configuration for Spring Security
│   │   │           │
│   │   │           ├── constants
│   │   │           │   └── JwtConstant.java               // Constants used for JWT configuration
│   │   │           │
│   │   │           ├── controller
│   │   │           │   ├── AdminController.java           // Controller for admin-specific endpoints
│   │   │           │   ├── AuthController.java            // Controller for authentication endpoints
│   │   │           │   └── UserController.java            // Controller for user-specific endpoints
│   │   │           │
│   │   │           ├── dao
│   │   │           │   └── UserDao.java                   // Data Access Object interface for user operations
│   │   │           │
│   │   │           ├── daoimpl
│   │   │           │   └── UserDaoImpl.java               // Implementation of the UserDao interface
│   │   │           │
│   │   │           ├── entity
│   │   │           │   ├── Role.java                      // Entity representing a user's role
│   │   │           │   └── User.java                      // Entity representing a user
│   │   │           │
│   │   │           ├── model
│   │   │           │   └── JwtResponse.java               // Model representing the JWT response
│   │   │           │
│   │   │           ├── security
│   │   │           │   ├── CustomUserDetail.java          // User details implementation for Spring Security
│   │   │           │   └── CustomUserDetailService.java   // Service for loading user-specific data
│   │   │           │
│   │   │           ├── service
│   │   │           │   └── UserService.java               // Interface for user-related business logic
│   │   │           │
│   │   │           ├── serviceimpl
│   │   │           │   └── UserServiceImpl.java           // Implementation of the UserService interface
│   │   │           │
│   │   │           └── utility
│   │   │               └── JwtUtil.java                   // Utility class for JWT-related operations
│   │   │
│   │   └── resources
│   │       └── application.yml                            // Configuration file for the application
│
└── [Other project files and directories]

Code Component Explanation

1. pom.xml:

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.5.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	
	<dependencies>
	
	<dependency>
		<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>

		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.9.1</version>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.31</version>
		</dependency>
		
	  <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		
	</dependencies>
  • Parent Version (Spring Boot Starter Parent 2.5.2): Sets up default configurations for Spring Boot applications, managing versions and compatibility for all dependencies.
  • Spring Boot Starter Security: Provides essential security features like authentication and authorization.
  • JJWT (JSON Web Token): Library for creating and validating JWTs, enabling token-based authentication.
  • Spring Boot Starter Data JPA: Simplifies database operations and ORM with Java Persistence API (JPA).
  • MySQL Connector/J: JDBC driver for MySQL, allowing Java applications to connect to MySQL databases.
  • Spring Boot Starter Web: Essential for building RESTful web services and web applications.

2. application.yml or properties

server:
  port: 8091
  hostname: localhost

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/covid19?createDatabaseIfNotExist=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: update           # Automatically update the database schema
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect   # MySQL 8 dialect
        show_sql: true                          # Show SQL statements in logs

3. JwtAuthenticationFilter.java

package com.api.config;


@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

	@Autowired
	private UserDetailsService userDetailsService;

	@Autowired
	private JwtUtil jwtTokenUtil;

	@Override
	protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		String header = req.getHeader(JwtConstatnt.HEADER_STRING);
		String username = null;
		String authToken = null;
		if (header != null && header.startsWith(JwtConstatnt.TOKEN_PREFIX)) {

			// header= Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJyYW0iLCJzY29wZXMiOiJST0xFX0FETUlOIiwiaWF0IjoxNjgxMTEyOTUyLCJleHAiOjE2ODExMzA5NTJ9.SHNB6bgI_HgWFBZyhNnG0nQO7SWvJLfaxmSDAeRkZiw
			// 

			authToken = header.replace(JwtConstatnt.TOKEN_PREFIX, "");
			try {
				username = jwtTokenUtil.getUsernameFromToken(authToken);
				// System.out.println("User from token "+username);
			} catch (IllegalArgumentException e) {
				System.err.println("an error occured during getting username from token");
			} catch (ExpiredJwtException e) {
				System.err.println("the token is expired and not valid anymore");
			} catch (SignatureException e) {
				System.err.println("Authentication Failed. Username or Password not valid.");
			}

			catch (Exception e) {
				 e.printStackTrace();
			}

		} else {
			System.err.println("couldn't find bearer string, will ignore the header ! Logged In With Credientials");
		}
		if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

			UserDetails userDetails = userDetailsService.loadUserByUsername(username);

			if (jwtTokenUtil.validateToken(authToken, userDetails)) {
				UsernamePasswordAuthenticationToken authentication = jwtTokenUtil.getAuthentication(authToken,
						SecurityContextHolder.getContext().getAuthentication(), userDetails);
				authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
				System.err.println("authenticated user " + username + ", setting security context");
				SecurityContextHolder.getContext().setAuthentication(authentication);
			}
		}

		chain.doFilter(req, res);
	}
}

Here’s a summary of the JwtAuthenticationFilter class with key points:

  • Purpose: Extends OncePerRequestFilter to intercept HTTP requests and validate JWT tokens for authentication.
  • Dependencies:
    1. UserDetailsService: Loads user-specific data for authentication.
    2. JwtUtil: Utility class for JWT operations, including token parsing and validation.
  • Token Extraction:
    1. Extracts the JWT token from the Authorization header if it starts with the prefix Bearer .
    2. Handles potential exceptions such as IllegalArgumentException, ExpiredJwtException, and SignatureException during token extraction and validation.
  • Username Extraction: Retrieves the username from the token using JwtUtil.
  • Authentication Setup:
    1. If the username is valid and no existing authentication context is found, it loads user details and validates the token.
    2. Creates and sets up a UsernamePasswordAuthenticationToken for the authenticated user and updates the SecurityContextHolder.
  • Logging: Prints error messages and debugging information for various authentication steps and token issues.

4. SecurityConfig.java


package com.api.config;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	public CustomUserDetailService customUserDetilService;

	@Autowired
	private JwtAuthenticationFilter jwtFilter;

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(customUserDetilService).passwordEncoder(bCryptPasswordEncoder());
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.csrf().disable().authorizeRequests()
		.antMatchers("/auth/**").permitAll()
		.and()
		.authorizeRequests()
		.antMatchers("/admin/**").hasRole("ADMIN") // ADMIN USER
				.antMatchers("/user/**").hasRole("USER") // RECEPTIONIST USER
				
				.anyRequest().authenticated().and().httpBasic();
		http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
	}

	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources/**",
				"/configuration/security", "/webjars/**");
	}

	@Bean
	public BCryptPasswordEncoder bCryptPasswordEncoder() {
		return new BCryptPasswordEncoder(10);
	}

	@Bean
	public AuthenticationManager authenticationManager() throws Exception {
		return super.authenticationManager();
	}

}

Here’s a brief summary of the SecurityConfig class:

  • Purpose: Configures Spring Security with JWT for the application, managing authentication and authorization.
  • Custom User Details Service: Utilizes CustomUserDetailService to load user-specific data for authentication.
  • Password Encoding: Uses BCryptPasswordEncoder for hashing passwords, providing a security layer for password storage.
  • JWT Authentication Filter: Adds a custom JwtAuthenticationFilter before the default UsernamePasswordAuthenticationFilter to handle JWT-based authentication.
  • Security Rules:
    1. Disables CSRF protection (csrf().disable()) since the API is stateless.
    2. Allows unrestricted access to endpoints under /auth/**.
    3. Restricts access to /admin/** for users with the ADMIN role and /user/** for users with the USER role.
    4. All other requests require authentication (anyRequest().authenticated()).
  • Web Security Configuration: Excludes specific paths from Spring Security filters, such as Swagger UI and API documentation endpoints, to allow open access for development purposes.
  • Beans Provided:
    1. BCryptPasswordEncoder: For password encoding.
    2. AuthenticationManager: Manages authentication processes and is necessary for custom authentication implementations.

5. JwtConstatnt.java

package com.api.constants;

public interface JwtConstatnt 
{
	public static final String HEADER_STRING = "Authorization";
	public static final String TOKEN_PREFIX = "Bearer ";
	public static final String SIGNING_KEY = "MyHealthifyApplicationSigningKEY702019272609876654321";
	public static final int ACCESS_TOKEN_VALIDITY_SECONDS = 5*60*60;
	public static final String AUTHORITIES_KEY = "scopes";
}

Here’s a brief summary of the JwtConstatnt interface:

  • Purpose: Provides constant values related to JWT configuration and handling, used throughout the application for security purposes.
  • Constants Defined:
    1. HEADER_STRING: Specifies the HTTP header key "Authorization" where the JWT token is expected.
    2. TOKEN_PREFIX: Denotes the prefix "Bearer " used in the Authorization header to indicate the type of token.
    3. SIGNING_KEY: Represents the secret key "MyHealthifyApplicationSigningKEY702019272609876654321" used for signing JWT tokens, ensuring their integrity and authenticity.
    4. ACCESS_TOKEN_VALIDITY_SECONDS: Sets the validity duration of the JWT token to 5 hours (5 * 60 * 60 seconds).
    5. AUTHORITIES_KEY: Defines the key "scopes" used to store user roles or authorities in the JWT token payload.

6. Define All Endpoints in RestControllers

package com.api.controller;

@RestController
@RequestMapping("/admin")
public class AdminController {
	
	@GetMapping("/get-all-center")
	public ResponseEntity<String> getAllCenter(){
		return new ResponseEntity<String>("Pune Center, Mumbai Center",HttpStatus.OK) ;
			
		
	}

}

////////////////////////////////////////////////////////////////////////////////

package com.api.controller;

@RestController
@RequestMapping("/user")
public class UserController {
	
	@GetMapping("/search-vancine/{name}")
	public ResponseEntity<String> getCenter(){
		return new ResponseEntity<String>("Pune Center",HttpStatus.OK) ;
			
		
	}

}

package com.api.controller;

@RestController
@RequestMapping("/auth")
public class AuthController {

	@Autowired
	UserService userService;
	
	@Autowired
	CustomUserDetailService customUserDetailService;
	
	@Autowired
	JwtUtil jwtUtil;
	
	@Autowired
	private AuthenticationManager authenticationManager;

	// completed
	@PostMapping("/login-user")
	
	public ResponseEntity<?> loginAdmin(@RequestBody User user,HttpServletResponse response) throws AuthenticationException {

			
        final Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        user.getUsername(),
                        user.getPassword()
              )
        );
        SecurityContextHolder.getContext().setAuthentication(authentication); //check 
        final String token = jwtUtil.generateToken(authentication); 
        response.addHeader("token", token);
       return ResponseEntity.ok(new JwtResponse(token));
    
    }
}

Here’s a brief summary of the AuthController class:

  • Purpose: Provides endpoints for authentication and login functionality in the API.
  • Dependencies:
    1. UserService: Handles user-related operations (not directly used in the provided code).
    2. CustomUserDetailService: Custom service for loading user details (not directly used in the provided code).
    3. JwtUtil: Utility class for generating JWT tokens after successful authentication.
    4. AuthenticationManager: Manages authentication processes by validating user credentials.
  • Endpoint:
    1. /login-user (POST): Authenticates the user using provided credentials (username and password).
  • Authentication Process:
    1. Utilizes AuthenticationManager to authenticate the user with UsernamePasswordAuthenticationToken.
    2. If authentication is successful, sets the authentication context in SecurityContextHolder.
  • Token Generation:
    1. Generates a JWT token using JwtUtil for the authenticated user.
    2. Adds the generated JWT token to the HTTP response header as "token".
  • Response:
    1. Returns an OK (200 OK) response with a JwtResponse object containing the JWT token.

7. UserServiceImpl.java

package com.api.serviceimpl;

@Service
public class UserServiceImpl implements UserService {

	@Autowired
	public BCryptPasswordEncoder passwordEncoder;

	@Autowired
	private UserDao dao;

	@Override
	public User loginUser(User user) {

		return dao.loginUser(user);
	}

	@Override
	public CustomUserDetail loadUserByUserId(String userId) {
		return dao.loadUserByUserId(userId);
	}
}

Here’s a brief summary of the UserServiceImpl class:

  • Purpose: Implements the UserService interface, providing business logic for user-related operations.
  • Dependencies:
    1. BCryptPasswordEncoder: Used for encoding and validating user passwords securely (though not used directly in the provided methods).
    2. UserDao: Data Access Object interface for interacting with the data source to perform user-related operations.
  • Methods:
    1. loginUser(User user): Delegates to UserDao to handle user login by returning a User object if the login is successful.
    2. loadUserByUserId(String userId): Retrieves a CustomUserDetail object based on the provided userId, leveraging the UserDao.
  • Implementation:
    1. Acts as a service layer to abstract the data access logic and provide a cleaner interface for higher-level components like controllers.

8. UserDaoImpl.java

package com.api.daoimpl;

@Repository
public class UserDaoImpl implements UserDao {
	private static Logger LOG = LogManager.getLogger(UserDaoImpl.class);

	@Autowired
	private SessionFactory sf;

	@Autowired
	public PasswordEncoder passwordEncoder;

	@Override
	public User loginUser(User user) {
		Session session = sf.getCurrentSession();
		User usr = null;
		try {
			usr = session.get(User.class, user.getUsername());
			boolean matches = passwordEncoder.matches(user.getPassword(), usr.getPassword());
			if (matches) {
				return usr;
			} else {
				usr = null;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return usr;

	}

	@Override
	public CustomUserDetail loadUserByUserId(String userId) {
		Session session = sf.getCurrentSession();
		CustomUserDetail user = new CustomUserDetail();
		User usr = null;
		try {
			usr = session.get(User.class, userId);
			if (usr != null) {
				user.setUserid(usr.getUsername());
				user.setPassword(usr.getPassword());
				user.setRoles(usr.getRoles());
			}
			System.out.println("load user ..." + user);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return user;
	}
}

Here’s a brief summary of the UserDaoImpl class:

  • Purpose: Implements the UserDao interface, providing data access methods for user-related operations, including user login and loading user details by user ID.
  • Dependencies:
    1. SessionFactory: Used to obtain Hibernate sessions for interacting with the database.
    2. PasswordEncoder: Used to verify that the provided password matches the encoded password stored in the
  • Methods:
    1. loginUser(User user):database.
      • Fetches a User object from the database based on the provided username.
      • Verifies the provided password against the stored password using PasswordEncoder.
      • Returns the User object if the password matches; otherwise, returns null.
    2. loadUserByUserId(String userId):
      • Retrieves a User entity from the database based on the user ID.
      • Converts the User entity to a CustomUserDetail object, setting the user ID, password, and roles.
      • Returns the CustomUserDetail object, or a default one if the user is not found.
  • Exception Handling: Both methods include try-catch blocks to handle exceptions, ensuring that errors are logged and do not crash the application.
  • Logging: Uses LogManager for logging activities, although it seems to only log exceptions in this context.

9. User.java

package com.api.entity;



/**
 * @author RAM
 */
 
@Entity
public class User {

	@Id
	@Column(name = "UserName",unique = true,nullable = false)
	@Pattern(regexp = "^[a-zA-Z0-9_]{3,20}$", message = "Invalid username format")	
	private String username;

	@NotBlank(message = "First name is manadtory")
	@Column(name = "FirstName",nullable = false)
    @Pattern(regexp = "^[a-zA-Z]+(\\s[a-zA-Z]+)*$", message = "Invalid first name")
	private String firstname;

	@Column(name = "LastName",nullable = false)
    @Pattern(regexp = "^[a-zA-Z]+(\\s[a-zA-Z]+)*$", message = "Invalid last name")
	private String lastname;

	@Column(name = "EmailId", unique = true,nullable = false)
	@NotEmpty(message = "Email should not be empty")
    @Email(message = "Invalid email format")
	private String emailid;

	@Column(name = "Password",nullable = false)
    //@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$",message = "At least 8 characters long, one uppercase,lowercase,digit,special character")
	private String password;

	@Column(name = "MobileNo", unique = true,nullable = false)
	@Pattern(regexp = "^[0-9]{10}$",message = "Invalid mobile number")
	private String mobileno;

	@Column(name = "Street")
	private String street;

	@Column(name = "City")
	private String city;

	@Column(name = "Pincode")
	private String pincode;

	@Column(name = "Question",nullable = false)
	@NotBlank(message = "Question is manadtory")
	private String Question;

	@Column(name = "Answer",nullable = false)
	@NotBlank(message = "Answer is manadtory")
	private String answer;

	@DateTimeFormat(pattern = "yyyy-MM-dd")
	@JsonFormat(pattern = "yyyy-MM-dd")
	@Column(name = "CreatedDate",nullable = false)
	private Date createdDate;

	@ManyToMany(fetch = FetchType.EAGER)
	@JoinTable(name = "user_role", joinColumns = { @JoinColumn(name = "USER_ID") }, inverseJoinColumns = {
			@JoinColumn(name = "ROLE_ID") })
	private Set<Role> roles;

	public User() {

	}

	// setter & getter
}

Here’s a summary of the User entity class:

  • Purpose: Represents a user entity for a system, mapping user data to the database using JPA annotations.
  • Annotations:
    1. @Entity: Specifies that this class is a JPA entity.
    2. @Id: Marks the username field as the primary key.
    3. @Column: Defines properties for database columns, including constraints such as unique and nullable.
    4. @Pattern, @NotBlank, @NotEmpty, @Email: Used for input validation with specific regex patterns and constraints.
    5. @DateTimeFormat and @JsonFormat: Formats date fields for input/output.
    6. @ManyToMany and @JoinTable: Defines a many-to-many relationship with the Role entity, using a join table named user_role.

10. Role.java

package com.api.entity;

@Entity
public class Role {
	@Id
	private int id;
	private String name;
	

	public Role() {
		// TODO Auto-generated constructor stub
	}
// setter & getter
}

11.CustomUserDetail.java

package com.api.security;

public class CustomUserDetail implements UserDetails {

	private String userid;
	private String password;

	private Set<Role> roles;

	public CustomUserDetail() {
		// TODO Auto-generated constructor stub
	}

	public CustomUserDetail(String userid, String password, Set<Role> roles) {
		super();
		this.userid = userid;
		this.password = password;
		this.roles = roles;
	}

	public String getUserid() {
		return userid;
	}

	public void setUserid(String userid) {
		this.userid = userid;
	}

	public Set<Role> getRoles() {
		return roles;
	}

	public void setRoles(Set<Role> roles) {
		this.roles = roles;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {

		List<SimpleGrantedAuthority> autorities = this.roles.stream()
				.map((role) -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());
		return autorities;

	}

	@Override
	public String getUsername() {
		return this.userid;
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}

	@Override
	public String getPassword() {
		// TODO Auto-generated method stub
		return this.password;
	}

	@Override
	public String toString() {
		return "CustomUserDetail [userid=" + userid + ", password=" + password + ", roles=" + roles + "]";
	}

}

Here’s a summary of the CustomUserDetail class:

  • Purpose: Implements UserDetails to integrate custom user details with Spring Security for authentication and authorization.
  • Fields:
    1. userid: Stores the user’s ID or username.
    2. password: Stores the user’s password.
    3. roles: A set of Role objects representing user roles.
  • Methods:
    1. getAuthorities(): Returns a collection of GrantedAuthority objects, derived from user roles, used by Spring Security to manage user roles and permissions.
    2. getUsername(): Returns the userid, implementing the UserDetails interface method.
    3. getPassword(): Returns the user’s password.
    4. isAccountNonExpired(): Returns true, indicating the account is not expired.
    5. isAccountNonLocked(): Returns true, indicating the account is not locked.
    6. isCredentialsNonExpired(): Returns true, indicating the credentials are not expired.
    7. isEnabled(): Returns true, indicating the account is enabled.
    8. toString(): Provides a string representation of the CustomUserDetail object, including userid, password, and roles.

12. CustomUserDetailService.java


package com.api.security;

@Service
public class CustomUserDetailService implements UserDetailsService {

	@Autowired
	private UserService service;

	@Override
	public UserDetails loadUserByUsername(String username) throws ResourceNotFoundException {
		// loading user from db
		CustomUserDetail user = service.loadUserByUserId(username);
		if (user != null) {
			return user;
		} else {
			throw new ResourceNotFoundException("User not found with username: " + username);
		}

	}

}

Here are the key points for the CustomUserDetailService class:

  • Purpose: Implements UserDetailsService to provide a custom way of loading user details for authentication in Spring Security.
  • UserService service: Injected using @Autowired, this dependency is used to access user-related operations, such as loading user details from the database.
  • Functionality: Calls service.loadUserByUserId(username) to fetch user details from the database.

13. JwtUtil.java

package com.api.utility;

@Component
public class JwtUtil implements Serializable {

	public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    private Claims getAllClaimsFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(JwtConstatnt.SIGNING_KEY)
                .parseClaimsJws(token)
                .getBody();
    }

    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    public String generateToken(Authentication authentication) {
        final String authorities = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(","));
        return Jwts.builder()
                .setSubject(authentication.getName())
                .claim(JwtConstatnt.AUTHORITIES_KEY, authorities)
                .signWith(SignatureAlgorithm.HS256, JwtConstatnt.SIGNING_KEY)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + JwtConstatnt.ACCESS_TOKEN_VALIDITY_SECONDS*1000))
                .compact();
    }

    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = getUsernameFromToken(token);
        return (
              username.equals(userDetails.getUsername())
                    && !isTokenExpired(token));
    }

    public UsernamePasswordAuthenticationToken getAuthentication(final String token, final Authentication existingAuth, final UserDetails userDetails) {

        final JwtParser jwtParser = Jwts.parser().setSigningKey(JwtConstatnt.SIGNING_KEY);

        final Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);

        final Claims claims = claimsJws.getBody();

        final Collection<? extends GrantedAuthority> authorities =
                Arrays.stream(claims.get(JwtConstatnt.AUTHORITIES_KEY).toString().split(","))
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toList());

        return new UsernamePasswordAuthenticationToken(userDetails, "", authorities);
    }

}

Here are the key points for the JwtUtil class:

  • Purpose: Utility class for handling JWT (JSON Web Token) operations such as generating, validating, and parsing tokens.
  • Key Methods:
    • getUsernameFromToken(String token):
      1. Purpose: Extracts the username (subject) from the JWT token.
      2. Returns: The username embedded in the token.
    • getExpirationDateFromToken(String token):
      1. Purpose: Retrieves the expiration date from the JWT token.
      2. Returns: A Date object representing the token’s expiration time.
    • getClaimFromToken(String token, Function<Claims, T> claimsResolver):
      1. Purpose: General method to extract any claim from the token using a specified resolver function.
      2. Returns: The claim value extracted by the resolver function.
    • getAllClaimsFromToken(String token):
      1. Purpose: Parses the JWT token to retrieve all claims.
      2. Returns: A Claims object containing all the token’s claims.
    • isTokenExpired(String token):
      1. Purpose: Checks if the JWT token has expired.
      2. Returns: true if the token is expired; false otherwise.
    • generateToken(Authentication authentication):
      1. Purpose: Generates a new JWT token based on user authentication details.
      2. Returns: A string representing the newly created JWT token.
    • validateToken(String token, UserDetails userDetails):
      1. Purpose: Validates the JWT token by checking the username and ensuring it hasn’t expired.
      2. Returns: true if the token is valid and matches the provided user details; false otherwise.
    • getAuthentication(String token, Authentication existingAuth, UserDetails userDetails):
      1. Purpose: Creates an Authentication object from the JWT token, using the token’s claims to determine the user’s authorities.
      2. Returns: A UsernamePasswordAuthenticationToken object containing user details and authorities extracted from the token.
  • Token Operations:
    • Uses Jwts.builder() and Jwts.parser() from the io.jsonwebtoken library for creating and parsing JWT tokens.
    • SignatureAlgorithm.HS256: Specifies the hashing algorithm used to sign the JWT token.
Other Resources:
  1. install-SonarQube-server-on-windows
  2. how-to-do-validation-in-spring-boot-tested
  3. Click here to download

Ram Chadar

Hello! I'm Ram Chadar, a passionate software developer and freelancer based in Pune. Welcome to my blog, where I share my experiences, insights, and knowledge in the world of software development, different technologies, freelancing, and more.

View all posts by Ram Chadar →

4 thoughts on “1. How to implement Spring Security With JWT – Complete Solution

  1. Very nice post. I just stumbled upon your blog and wanted to say that I’ve really enjoyed surfing around your blog posts. In any case I will be subscribing to your rss feed and I hope you write again soon!

Leave a Reply

Your email address will not be published. Required fields are marked *