What is Authentication? 🔐 Authentication is the process of verifying the identity of users, ensuring they are who they claim to be before granting access to a system or application. In the context of web applications, this is crucial for protecting sensitive information and resources. Authentication involves: User Credentials:🔑 Users provide credentials, such as usernames and passwords. Verification Process:✔️ The system verifies the provided credentials against stored data. Successful verification grants access; otherwise, access is denied Spring Security🍃 For developers building Spring-based applications, Spring Security serves as the de-facto standard for securing their creations. This comprehensive framework provides robust authentication and authorization mechanisms, ensuring your application’s data and functionality remain safe from unauthorized access. Java JWT (JSON Web Token)Authentication: A Secure and Scalable 🔐📈 Approach JWT stands for JSON Web Token, a self-contained token that contains information about the user and is signed by the server for verification. It has become a popular choice for authorization due to its numerous advantages. Use Cases: JWTs are widely used in various scenarios: Public APIs: Securely access public APIs without requiring frequent logins. Mobile Applications: Store user information and authorization details within the token for offline use. Single Sign-On (SSO): This allows users to seamlessly access multiple applications with a single login. Practical application of JWT by implementing it in a Spring Boot application to secure our API endpoints⛏👷 Now, we will configure the in-memory user and JWT. We will create an API endpoint and secure it using Spring Boot security. Create a Spring Boot Project Use Spring Initializr to create a new Spring Boot project with the following dependencies: For Web: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> For Security <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> Lombok: <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> For JWT: <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> Let’s start with creating an endpoint to be secured: @RestController @RequestMapping("/home") public class Controller { @Autowired private UserService userService; @GetMapping("/users") public List<User> getUsers(){ System.out.println("getting users"); return userService.getUsers(); } } Create a user: @Getter @Setter @NoArgsConstructor @AllArgsConstructor @ToString public class User { private String userId; private String name; private String email; } Create a service class: @Service public class UserService { private List<User> store=new ArrayList<>(); public UserService() { store.add(new User(UUID.randomUUID().toString(), "Bhushan", "bhushan@gmail.com")); store.add(new User(UUID.randomUUID().toString(), "Ramesh", "ramesh@gmail.com")); store.add(new User(UUID.randomUUID().toString(), "Suresh", "suresh@gmail.com")); store.add(new User(UUID.randomUUID().toString(), "Paresh", "paresh@gmail.com")); } public List<User> getUsers(){ return this.store; } } Create an in-memory user with UserDetailService bean: @Configuration public class AppConfig { @Bean public UserDetailsService userDetailsService() { UserDetails userDetails = User.builder(). username("Bhushan") .password(passwordEncoder().encode("Nemade")).roles("ADMIN"). build(); return new InMemoryUserDetailsManager(userDetails); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration builder) throws Exception { return builder.getAuthenticationManager(); } } Start with creating the class JWTAthenticationEntryPoint that will be implementing : Used to handle authentication-related exceptions. Specifically, it is responsible for returning an unauthorized (401) response to clients who attempt to access protected resources without proper authentication. AuthenticationEntryPoint @Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); PrintWriter writer = response.getWriter(); writer.println("Access Denied !! " + authException.getMessage()); } } The helper class is often created to encapsulate the logic related to the generation, parsing, validation, and manipulation of JWTs in a more modular and maintainable way. It provides a set of methods and utilities to interact with JWTs. Create a JWT helper class: @Component public class JwtHelper { public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60; private String secret = "afafasfafafasfasfasfafacasdasfasxASFACASDFACASDFASFASFDAFASFASDAADSCSDFADCVSGCFVADXCcadwavfsfarvf"; 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(secret).parseClaimsJws(token).getBody(); } private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return doGenerateToken(claims, userDetails.getUsername()); } private String doGenerateToken(Map<String, Object> claims, String subject) { return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)) .signWith(SignatureAlgorithm.HS512, secret).compact(); } public Boolean validateToken(String token, UserDetails userDetails) { final String username = getUsernameFromToken(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } } Extending the class for a JWT authentication filter in Spring Security is a common approach to ensure that the filter is only executed once per request. This helps prevent unnecessary duplicate processing and ensures that the filter logic is applied consistently. Create a JwtAuthenticationFilter class which will extend OncePerRequestFilter: OncePerRequestFilter @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private Logger logger = LoggerFactory.getLogger(OncePerRequestFilter.class); @Autowired private JwtHelper jwtHelper; @Autowired private UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String requestHeader = request.getHeader("Authorization"); logger.info(" Header : {}", requestHeader); String username = null; String token = null; if (requestHeader != null && requestHeader.startsWith("Bearer")) { token = requestHeader.substring(7); try { username = this.jwtHelper.getUsernameFromToken(token); } catch (IllegalArgumentException e) { logger.info("Illegal Argument while fetching the username !!"); e.printStackTrace(); } catch (ExpiredJwtException e) { logger.info("Given jwt token is expired !!"); e.printStackTrace(); } catch (MalformedJwtException e) { logger.info("Some changed has done in token !! Invalid Token"); e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } else { logger.info("Invalid Header Value !! "); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); Boolean validateToken = this.jwtHelper.validateToken(token, userDetails); if (validateToken) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } else { logger.info("Validation fails !!"); } } filterChain.doFilter(request, response); } } Create a SecurityConfig class as spring security in the configuration file: @Configuration public class SecurityConfig { @Autowired private JwtAuthenticationEntryPoint point; @Autowired private JwtAuthenticationFilter filter; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf(csrf -> csrf.disable()) .authorizeRequests(). requestMatchers("/test").authenticated().requestMatchers("/auth/login").permitAll() .anyRequest() .authenticated() .and().exceptionHandling(ex -> ex.authenticationEntryPoint(point)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class); return http.build(); } } Create a login API to accept the username and password: @RestController @RequestMapping("/auth") public class AuthController { @Autowired private UserDetailsService userDetailsService; @Autowired private AuthenticationManager manager; @Autowired private JwtHelper helper; private Logger logger = LoggerFactory.getLogger(AuthController.class); @PostMapping("/login") public ResponseEntity<JwtResponse> login(@RequestBody JwtRequest request) { this.doAuthenticate(request.getEmail(), request.getPassword()); UserDetails userDetails = userDetailsService.loadUserByUsername(request.getEmail()); String token = this.helper.generateToken(userDetails); JwtResponse response = JwtResponse.builder() .jwtToken(token) .username(userDetails.getUsername()).build(); return new ResponseEntity<>(response, HttpStatus.OK); } private void doAuthenticate(String email, String password) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(email, password); try { manager.authenticate(authentication); } catch (BadCredentialsException e) { throw new BadCredentialsException(" Invalid Username or Password !!"); } } @ExceptionHandler(BadCredentialsException.class) public String exceptionHandler() { return "Credentials Invalid !!"; } } : Creating a JWT request and response involves defining structures for sending and receiving JWTs during the authentication process. Typically, a JWT request is used to send user credentials (e.g., username and password) to the server for authentication, and a JWT response is used to deliver a token upon successful authentication. Create a JWT Request and JWT Response to receive data and send login details @Getter @Setter @AllArgsConstructor @NoArgsConstructor @Builder @ToString public class JwtRequest { private String email; private String password; } @Getter @Setter @AllArgsConstructor @NoArgsConstructor @Builder @ToString public class JwtResponse { private String jwtToken; private String username; } Test the API using Postman to generate a JWT token, then use that token as a header in subsequent requests to access the protected API and verify successful authentication. Dive Deeper : youtube.com/watch?v=q2l91Ffc_8U&t=3299s https://springframework.guru/jwt-authentication-in-spring-microservices-jwt-token/ Thank you for reading😊 Also published here.