Table of Contents

Springboot - 03. JWT Authentication and Authorization

Requirement

  1. Spring Boot 3.2.1
  2. Spring Security
  3. Spring Data JPA (Hibernate 6)
  4. MySQL Database
  5. Postman
  6. Maven
  7. IntelliJ IDEA
  8. JWT Java library 0.12.3

Menambahkan Dependency

Tambahkan dependensi Maven di bawah ini ke proyek Spring Boot Anda:

				
					<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
	<groupId>com.mysql</groupId>
	<artifactId>mysql-connector-j</artifactId>
	<scope>runtime</scope>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
	<scope>test</scope>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-impl</artifactId>
	<version>0.12.3</version>
	<scope>runtime</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-api</artifactId>
	<version>0.12.3</version>
</dependency>

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-jackson</artifactId>
	<version>0.12.3</version>
	<scope>runtime</scope>
</dependency>

				
			

Konfigurasi Database MYSQL

Karena kita menggunakan MySQL sebagai database, kita perlu mengkonfigurasi URL database, nama pengguna, dan kata sandi agar Spring dapat membuat koneksi dengan database saat startup. Buka file src/main/resources/application.properties dan tambahkan properti berikut ke dalamnya:

				
					spring.application.name=springrestjwt
# setup database connection
# driver database
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# database url connection
spring.datasource.url=jdbc:mysql://localhost:3306/demo
# database username
spring.datasource.username=root
# database password
spring.datasource.password=root

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format-sql=true
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

# JWT Secret Key
jwt.secret=your-very-secure-secret-key-change-this-for-production
# 24h=86400000ms in 1m=60000ms
jwt.expiration=60000
				
			

Membuat Model - User & Role (Many-to-One Mapping)

Pada langkah ini, kita akan membuat entitas JPA User dan Role menggunakan manyToOne relationship.

User
				
					package com.ombagoes.springrestjwt.user;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.ombagoes.springrestjwt.role.Role;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "users")
public class User implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    @NotBlank(message = "Name is mandatory")
    @Pattern(regexp = "[a-zA-Z\\s]+$", message = "invalid name, char only")
    private String name;

    @Column(nullable = false, unique = true)
    @NotBlank(message = "Email is mandatory")
    @Email
    private String email;

    @Column(nullable = false)
    @NotBlank(message = "Password must fill.")
    @Pattern(regexp = "^(?=.*\\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[^\\w\\d\\s:])([^\\s]){8,}$", message = "Minimum eight characters, at least one uppercase letter, one lowercase letter, one number and one special character")
    private String password;

    @Column(nullable = false, columnDefinition = "boolean default true")
    private boolean enabled;

    @JsonIgnoreProperties("users")//avoid looping
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "role_id")
    private Role role;

    @Column(name = "created_at")
    private Date createdAt;

    @PrePersist
    public void addTimestamp() {
        createdAt = new Date();
    }

    @Column(name = "updated_at")
    private Date updatedAt;

    @PreUpdate
    public void updateTimestamp() {
        updatedAt = new Date();
    }
}

				
			
Role
				
					package com.ombagoes.springrestjwt.role;

import com.ombagoes.springrestjwt.user.User;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "roles")
public class Role{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;

    @OneToMany(mappedBy = "role", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    private List<User> users = new ArrayList<>();
}

				
			

Membuat Repository & Service

Selanjutnya, dalam paket repositori, kita membuat interface UserRepository dan RoleRepository.

UserRepository
				
					package com.ombagoes.thirdJwt.user;

import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

				
			
				
					package com.ombagoes.thirdJwt.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public List<User> allUsers() {
        return new ArrayList<>(userRepository.findAll());
    }
}

				
			
RoleRepository
				
					package com.ombagoes.thirdJwt.role;

import org.springframework.data.jpa.repository.JpaRepository;

public interface RoleRepository extends JpaRepository<Role, Integer> {
}

				
			

Role tidak menggunakan service karena tidak ada kebutuhan api untuk menampilkan data.

Membuat Controller

Controler nantinya akan berfungsi untuk menampilkan data user dan profile setelah login.

UserController
				
					package com.ombagoes.springrestjwt.user;

import com.ombagoes.springrestjwt.auth.CustomUserDetailsService;
import com.ombagoes.springrestjwt.user.dtos.UpdateProfileDto;
import com.ombagoes.springrestjwt.util.JwtUtil;
import com.ombagoes.springrestjwt.util.ValidationUtil;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@RestController
@Slf4j
public class UserController {
    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Autowired
    private UserService userService;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private ValidationUtil validationUtil;

    @GetMapping("/users")
    public List<User> allUsers() {
        return userService.allUsers();
    }

    @GetMapping("/profile")
    public UserDetails getProfile() {
        Authentication authenticationToken = SecurityContextHolder.getContext().getAuthentication();
        String username= authenticationToken.getName();
        return customUserDetailsService.loadUserByUsername(username);
    }

    @GetMapping("/me")
    public ResponseEntity<Object> getCurrentUser() {
        HashMap<String, Object> resultMap = new HashMap<>();
        Authentication authenticationToken = SecurityContextHolder.getContext().getAuthentication();
        String username = authenticationToken.getName();
        Optional<User> me=userService.getUser(username);
        resultMap.put("success", true);
        resultMap.put("user", Map.of(
            "username"  , me.orElseThrow().getEmail(),
            "role"  , me.orElseThrow().getRole().getId()
        ));
        return new ResponseEntity<>(resultMap, HttpStatus.OK);
    }

    @GetMapping("/refresh")
    public ResponseEntity<?> refreshToken() {
        HashMap<String, Object> resultMap = new HashMap<>();
        Authentication authenticationToken = SecurityContextHolder.getContext().getAuthentication();
        Optional<User> me=userService.getUser(authenticationToken.getName());
        resultMap.put("success", true);
        resultMap.put("access_token", jwtUtil.generateToken(me.orElseThrow().getId(), authenticationToken.getName()));
        return new ResponseEntity<>(resultMap, HttpStatus.OK);
    }

    @PutMapping("/profile")
    public ResponseEntity<?> update(@Valid @RequestBody UpdateProfileDto input, BindingResult bindingResult){
        String validationMessage=validationUtil.doValidation(bindingResult);
        HashMap<String, Object> resultMap = new HashMap<>();
        if(!validationMessage.isEmpty()) {
            resultMap.put("success", false);
            resultMap.put("message", validationMessage);
            return new ResponseEntity<>(resultMap, HttpStatus.BAD_REQUEST);
        }else{
            String message=userService.updateUser(input);
            if(message.isEmpty()){
                resultMap.put("success", true);
                resultMap.put("message", "Update success.");
                return new ResponseEntity<>(resultMap, HttpStatus.OK);
            }else{
                resultMap.put("success", false);
                resultMap.put("message", message);
                return new ResponseEntity<>(resultMap, HttpStatus.BAD_REQUEST);
            }
        }
    }

    @DeleteMapping("/profile")
    public ResponseEntity<?> delete(){
        HashMap<String, Object> resultMap = new HashMap<>();
        String message=userService.deleteUser();
        if(message.isEmpty()){
            resultMap.put("success", true);
            resultMap.put("message", "Drop success.");
            return new ResponseEntity<>(resultMap, HttpStatus.OK);
        }else{
            resultMap.put("success", false);
            resultMap.put("message", message);
            return new ResponseEntity<>(resultMap, HttpStatus.BAD_REQUEST);
        }
    }
}

				
			

Membuat Security Config, JWT Filter, JWTUtil

JWTFilter(Middleware)

Kelas ini akan digunakan untuk membaca token jwt dari header yang akam divalidasi oleh Security Config.

				
					package com.ombagoes.springrestjwt.filter;

import com.ombagoes.springrestjwt.auth.CustomUserDetailsService;
import com.ombagoes.springrestjwt.util.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;

import java.io.IOException;

@Slf4j
@Component // Add this annotation
public class JwtRequestFilter extends OncePerRequestFilter {
    private final HandlerExceptionResolver handlerExceptionResolver;
    private final JwtUtil jwtUtil;
    private final CustomUserDetailsService customUserDetailsService;

    @Autowired
    public JwtRequestFilter(JwtUtil jwtUtil, CustomUserDetailsService customUserDetailsService,HandlerExceptionResolver handlerExceptionResolver) {
        this.jwtUtil = jwtUtil;
        this.customUserDetailsService = customUserDetailsService;
        this.handlerExceptionResolver = handlerExceptionResolver;
    }

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            @NonNull HttpServletResponse response,
            @NonNull FilterChain chain) throws ServletException, IOException {
        log.info("doFilterInternal(-)");

        final String authorizationHeader = request.getHeader("Authorization");
        String username = null;
        String jwtToken = null;

        try {
            if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
                log.info("read bearer(-)");
                jwtToken = authorizationHeader.substring(7);
                username = jwtUtil.extractUsername(jwtToken);
                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
                    if (jwtUtil.validateToken(jwtToken, username)) {
                        log.info("validateToken(-)");
                        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                    }
                }else{
                    throw new AccessDeniedException(null);
                }
            }
            chain.doFilter(request, response);
        }catch (Exception e){
            handlerExceptionResolver.resolveException(request, response, null, e);
        }
    }
}

				
			
JWTUtil

Kelas berikutnya digunakan untuk mengclaim jwt setelah login berhasil.

				
					package com.ombagoes.thirdJwt.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@Slf4j
public class JwtUtil {

    @Value("${jwt.secret}")
    private String secretKey;

    @Value("${jwt.expiration}")
    private long expiration;

    public String generateToken(Long id, String username) {
        log.info("generateToken(-)");
        return Jwts.builder()
                .setId(id.toString())
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
    }

    public Claims extractClaims(String token) {
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody();
    }

    public String extractUsername(String token) {
        return extractClaims(token).getSubject();
    }

    public boolean isTokenExpired(String token) {
        return extractClaims(token).getExpiration().before(new Date());
    }

    public boolean validateToken(String token, String username) {
        return (username.equals(extractUsername(token)) && !isTokenExpired(token));
    }
}

				
			
Custom Security Config
				
					package com.ombagoes.thirdJwt.config;

import com.ombagoes.thirdJwt.filter.JwtRequestFilter;
import com.ombagoes.thirdJwt.auth.CustomUserDetailsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
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.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;

@Configuration
@EnableWebSecurity
@Slf4j
public class SecurityConfig {

    private final JwtRequestFilter jwtRequestFilter;
    private final CustomUserDetailsService customUserDetailsService;

    public SecurityConfig(JwtRequestFilter jwtRequestFilter, CustomUserDetailsService customUserDetailsService) {
        this.jwtRequestFilter = jwtRequestFilter;
        this.customUserDetailsService = customUserDetailsService;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        log.info("securityFilterChain(-)");

        http.csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/register","/login").permitAll()
                        .anyRequest().authenticated())
                .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService());
        authProvider.setPasswordEncoder(passwordEncoder());
        return new ProviderManager(authProvider);
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return customUserDetailsService;
    }
}

				
			

Kita menggunakan dua anotasi : “@Configuration” dan “@EnableWebSecurity”. Anotasi ini memberi tahu Spring Security untuk menggunakan konfigurasi keamanan custom, bukan bawaan.

Pada baris yang ditandai, mengatur route /login dan /register untuk tidak di protect.

Membuat Rest Api untuk Registrasi & Login

DTO
				
					package com.ombagoes.thirdJwt.auth;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AuthenticationRequest {
    private String username;
    private String password;
    private boolean enabled;
}

				
			
Service

Dalam konteks REST API, service merujuk pada bagian dari aplikasi atau sistem yang menyediakan fungsionalitas tertentu melalui protokol HTTP. Dengan kata lain, service adalah implementasi dari operasi atau fungsi yang dapat diakses melalui endpoint API. 

				
					package com.ombagoes.springrestjwt.auth;

import com.ombagoes.springrestjwt.role.RoleRepository;
import com.ombagoes.springrestjwt.user.User;
import com.ombagoes.springrestjwt.user.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
@Slf4j
public class AuthenticationService {
    @Autowired
    private  UserRepository userRepository;
    @Autowired
    private RoleRepository roleRepository;
    @Autowired
    private  PasswordEncoder passwordEncoder;
    @Autowired
    private  AuthenticationManager authenticationManager;

    public String signup(User input) {
        input.setPassword(passwordEncoder.encode(input.getPassword()));
        Optional<User> user = userRepository.findByEmail(input.getEmail());
        if (user.isPresent()){
            return "Username exists";
        }
        try {
            userRepository.save(input);
        }
        catch (Exception e) {
            log.info(e.getMessage());
            return "Signup Failed.";
        }
        return "";
    }

    public Long authenticate(AuthenticationRequest input) {
        try {
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            input.getUsername(),
                            input.getPassword()
                    )
            );
            return userRepository.findByEmail(input.getUsername()).orElseThrow().getId();
        }catch (BadCredentialsException e){
            log.info("Invalid username or password");
        }catch (Exception e){
            log.info(e.getMessage());
        }
        return 0L;
    }
}

				
			
Validation Util
				
					package com.ombagoes.springrestjwt.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;

import java.util.List;

@Component
@Slf4j
public class ValidationUtil {
    public String doValidation(BindingResult bindingResult) {
        StringBuilder bindErrorBuilder = new StringBuilder();
        List<FieldError> errors = bindingResult.getFieldErrors();
        for (FieldError error : errors ) {
            bindErrorBuilder.append(error.getField()).append(" : ").append(error.getDefaultMessage()).append(", ");
        }
        if(!bindErrorBuilder.isEmpty()) {
            return bindErrorBuilder.substring(0,bindErrorBuilder.length() - 2);
        }
        return "";
    }
}

				
			
AuthController
				
					package com.ombagoes.springrestjwt.auth;

import com.ombagoes.springrestjwt.user.User;
import com.ombagoes.springrestjwt.user.UserRepository;
import com.ombagoes.springrestjwt.util.JwtUtil;
import com.ombagoes.springrestjwt.util.ValidationUtil;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("auth")
@CrossOrigin("*")
public class AuthController {

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Autowired
    private AuthenticationService authenticationService;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private ValidationUtil validationUtil;

    @PostMapping("/register")
    public ResponseEntity<Object> addNewUser(@Valid @RequestBody User userInfo, BindingResult bindingResult) {
        String validationMessage=validationUtil.doValidation(bindingResult);
        HashMap<String, Object> resultMap = new HashMap<>();
        HttpStatus status=HttpStatus.OK;
        if(!validationMessage.isEmpty()) {
            resultMap.put("success", false);
            resultMap.put("message", validationMessage);
            status=HttpStatus.BAD_REQUEST;
        }else{
            String message=authenticationService.signup(userInfo);
            if(message.isEmpty()){
                resultMap.put("success", true);
                resultMap.put("message", "Registration success.");
            }else{
                resultMap.put("success", false);
                resultMap.put("message", message);
                status=HttpStatus.BAD_REQUEST;
            }
        }
        return new ResponseEntity<>(resultMap, status);
    }

    @PostMapping("/login")
    public ResponseEntity<Object> createToken(@Valid @RequestBody AuthenticationRequest request, BindingResult bindingResult) {
        String validationMessage=validationUtil.doValidation(bindingResult);
        if(!validationMessage.isEmpty()) {
            return new ResponseEntity<>(Map.of(
                    "success"  , false,
                    "message"  ,  validationMessage
            ),HttpStatus.BAD_REQUEST);
        }
        Long authenticatedUser = authenticationService.authenticate(request);
        if(authenticatedUser>0){
            log.info("success");
            // Generate the token
            String jwtToken = jwtUtil.generateToken(authenticatedUser, request.getUsername());
            return new ResponseEntity<>(Map.of(
                    "success"  , true,
                    "jwtToken"  , jwtToken,
                    "username"  , request.getUsername()
            ),HttpStatus.OK);
        }else{
            log.info("failed");
            return new ResponseEntity<>(Map.of(
                    "success"  , false,
                    "message"  , "Bad Credential"
            ),HttpStatus.UNAUTHORIZED);
        }
    }
}

				
			

Test Postman

Run Aplikasi
				
					./mvnw spring-boot:run
				
			
Register
Login
Me
SUCCESS
FAILED

Forbiidden tidak ada return apapun. Untuk menampilkan error(jika anda mau). Buka Class JwtFilter lalu tambahkan Try Catch sbb :

				
					try {
    if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
        log.info("read bearer(-)");
        jwtToken = authorizationHeader.substring(7);
        username = jwtUtil.extractUsername(jwtToken);
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
            if (jwtUtil.validateToken(jwtToken, username)) {
                log.info("validateToken(-)");
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }
    }
    chain.doFilter(request, response);
}catch (Exception e){
    handlerExceptionResolver.resolveException(request, response, null, e);
}
				
			

Lalu buat Class GlobalExceptionHandler sbb :

				
					package com.ombagoes.springrestjwt.exceptions;

import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ProblemDetail;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public ProblemDetail handleSecurityException(Exception exception) {
        ProblemDetail errorDetail = null;

        // TODO send this stack trace to an observability tool
        // exception.printStackTrace();

        if (exception instanceof BadCredentialsException) {
            errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(401), exception.getMessage());
            errorDetail.setProperty("description", "The username or password is incorrect");

            return errorDetail;
        }

        if (exception instanceof AccountStatusException) {
            errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
            errorDetail.setProperty("description", "The account is locked");
        }

        if (exception instanceof AccessDeniedException) {
            errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
            errorDetail.setProperty("description", "You are not authorized to access this resource");
        }

        if (exception instanceof ExpiredJwtException) {
            errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
            errorDetail.setProperty("description", "The JWT token has expired");
        }

        if (errorDetail == null) {
            errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(500), exception.getMessage());
            errorDetail.setProperty("description", "Unknown internal server error.");
        }

        return errorDetail;
    }
}

				
			

Hasilnya sbb :

Github

Reference