🖍️
Developer Note
  • Welcome
  • Git
    • Eslint & Prettier & Stylelint & Husky
  • Programming Language
    • JavaScript
      • Script Async vs Defer
      • Module
      • Const VS Let VS Var
      • Promise
      • Event Loop
      • Execution Context
      • Hoisting
      • Closure
      • Event Buddling and Capturing
      • Garbage Collection
      • This
      • Routing
      • Debounce and Throttle
      • Web Component
      • Iterator
      • Syntax
      • String
      • Array
      • Object
      • Proxy & Reflect
      • ProtoType
      • Class
      • Immutability
      • Typeof & Instanceof
      • Npm (Node package manager)
    • TypeScript
      • Utility Type
      • Type vs Interface
      • Any vs Unknown vs Never
      • Void and undefined
      • Strict Mode
      • Namespace
      • Enum
      • Module
      • Generic
    • Python
      • Local Development
      • Uv
      • Asyncio & Event loop
      • Context Manager
      • Iterator & Generator
      • Fast API
      • Pydantic & Data Class
    • Java
      • Compilation and Execution
      • Data Type
      • Enumeration
      • Data Structure
      • Try Catch
      • InputStream and OutputStream
      • Concurrent
      • Unicode Block
      • Build Tools
      • Servlet
      • Java 8
  • Coding Pattern
    • MVC vs MVVM
    • OOP vs Functional
    • Error Handling
    • MVC vs Flux
    • Imperative vs Declarative
    • Design Pattern
  • Web Communication
    • REST API
      • Web Hook
      • CORS issue
    • HTTPS
    • GraphQL
      • REST API vs GraphQL
      • Implementation (NodeJS + React)
    • Server-Sent Event
    • Web Socket
    • IP
    • Domain Name System (DNS)
  • Frontend
    • Progressive Web App (PWA)
    • Single Page & Multiple Page Application
    • Search Engine Optimiaztion (SEO)
    • Web bundling & Micro-frontend
      • Webpack
        • Using Webpack to build React Application
        • Using Webpack to build react library
      • Vite
      • Using rollup to build react library
      • Implementing micro frontend
    • Web Security
      • CSRF & Nonce
      • XSS
      • Click hijacking
    • Cypress
    • CSS
      • Core
        • Box Model
        • Inline vs Block
        • Flexbox & Grid
        • Pseudo Class
        • Position
      • Tailwind CSS
        • Shadcn
      • CSS In JS
        • Material UI
    • React
      • Core
        • Component Pattern
        • React Lazy & Suspense
        • React Portal
        • Error Boundary
        • Rendering Methods
        • Environment Variable
        • Conditional CSS
        • Memo
        • Forward Reference
        • High Order Component (HOC) & Custom Hook
        • TypeScript
      • State Management
        • Redux
        • Recoil
        • Zustand
      • Routing
        • React Router Dom
      • Data Fetching
        • Axios & Hook
        • React Query
        • Orval
      • Table
        • React Table
      • Form & Validation
        • React Hook Form
        • Zod
      • NextJS
        • Page Router
        • App Router
      • React Native
    • Angular
    • Svelte
      • Svelte Kit
  • Backend
    • Cache
      • Browser Cache
      • Web Browser Storage
      • Proxy
      • Redis
    • Rate limit
    • Monitoring
      • Logging
      • Distributed Tracing
    • Load Test
    • Encryption
    • Authentication
      • Password Protection
      • Cookie & Session
      • JSON Web Token
      • SSO
        • OAuth 2.0
        • OpenID Connect (OIDC)
        • SAML
    • Payment
      • Pre-built
      • Custom
    • File Handling
      • Upload & Download (Front-end)
      • Stream & Buffer
    • Microservice
      • API Gateway
      • Service Discovery
      • Load Balancer
      • Circuit Breaker
      • Message Broker
      • BulkHead & Zipkin
    • Elastic Search
    • Database
      • SQL
        • Group By vs Distinct
        • Index
        • N + 1 problem
        • Normalization
        • Foreign Key
        • Relationship
        • Union & Join
        • User Defined Type
      • NOSQL (MongoDB)
      • Transaction
      • Sharding
      • Lock (Concurrency Control)
    • NodeJS
      • NodeJS vs Java Spring
      • ExpressJS
      • NestJS
        • Swagger
        • Class Validator & Validation Pipe
        • Passport (Authentication)
      • Path Module
      • Database Connection
        • Integrating with MYSQL
        • Sequalize
        • Integrating with MongoDB
        • Prisma
        • MikroORM
        • Mongoose
      • Streaming
      • Worker Thread
      • Passport JS
      • JSON Web Token
      • Socket IO
      • Bull MQ
      • Pino (Logging)
      • Yeoman
    • Spring
      • Spring MVC
      • Spring REST
      • Spring Actuator
      • Aspect Oriented Programming (AOP)
      • Controller Advice
      • Filter
      • Interceptor
      • Concurrent
      • Spring Security
      • Spring Boot
      • Spring Cloud
        • Resilience 4j
      • Quartz vs Spring Batch
      • JPA and Hibernate
      • HATEOS
      • Swagger
      • Unit Test (Java Spring)
      • Unit Test (Spring boot)
  • DevOp
    • Docker
    • Kubernetes
      • Helm
    • Nginx
    • File System
    • Cloud
      • AWS
        • EC2 (Virtual Machine)
        • Network
        • IAM
          • Role-Service Binding
        • Database
        • Route 53
        • S3
        • Message Queue
        • Application Service
        • Serverless Framework
        • Data Analysis
        • Machine Learning
        • Monitoring
        • Security
      • Azure
        • Identity
        • Compute Resource
        • Networking
        • Storage
        • Monitoring
      • Google Cloud
        • IAM
          • Workload Identity Federation
        • Compute Engine
        • VPC Network
        • Storage
        • Kubernetes Engine
        • App Engine
        • Cloud function
        • Cloud Run
        • Infra as Code
        • Pub/Sub
    • Deployment Strategy
    • Jenkins
    • Examples
      • Deploy NextJS on GCP
      • Deploy Spring on Azure
      • Deploy React on Azure
  • Domain Knowledge
    • Web 3
      • Blockchain
      • Cryptocurrency
    • AI
      • Prompt
      • Chain & Agent
      • LangChain
      • Chunking
      • Search
      • Side Products
Powered by GitBook
On this page
  • Introduction
  • Pre-Action
  • Configuration
  • Authorization Filter
  • Handler of failed authentication
  • User Details
  • User Details Service
  • Json Web Token Utils
  • Enum
  • Modal
  • ORM
  • Request
  • Response
  • Exception and Exception handler
  • Repository
  • Controller
  • Service
  • Flow
  • Login
  • Access Secured Api End Point
  • Reference

Was this helpful?

  1. Backend
  2. Spring

Spring Security

PreviousConcurrentNextSpring Boot

Last updated 1 year ago

Was this helpful?

Introduction

  • Spring Security just like a filter, it will pre-process between the user request and the controller

  • The secured api end point will be cover by a spring security filter. In order to access the secured api end point, user is required to login with their correct role to pass the authorization which is a kind of filter

Pre-Action

  • Install the maven dependency

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

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

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

		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.9.1</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>6.2.0.Final</version>
		</dependency>
	</dependencies>

Configuration

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // Enable the PreAuthorize for specific api end point
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationEntryPoint unauthorizedHandler;
    
    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    @Resource(name = "userDetailsServiceImpl")
    private UserDetailsService userDetailsService;

    // Declare the data source
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public AuthTokenFilter authenticationJwtTokenFilter(){
        return new AuthTokenFilter();
    }

    // Create authentication to authorize the username and password whether it is correct or not
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

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

    // Allow CORS ,cancel session, and declare which api end point path should be authenticated
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint(unauthorizedHandler).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests().antMatchers("/api/user/**").authenticated()
                .anyRequest().permitAll();

        // Apply custom filter to to do authentication
        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}
  • To declare the data source of user

  • To register the custom filter for authorization as a bean

  • To register the authentication manager which act as an entry point of the login request as a bean

  • To register the password encoder to do hashing for password as a bean

  • To allow CORS and disable CSRF (Cross-site request forgery)

  • To declare which API end point would be secured

  • To declare handling method for failed authentication not access right issue

@Configuration
@EnableWebMvc
public class CorConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**");
    }
}
  • To allow all API end point can be cross-origin

Authorization Filter

public class AuthTokenFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUtils jwtUtils;

    @Resource(name = "userDetailsServiceImpl")
    private UserDetailsService userDetailsService;

    // If the secured api end point is called, the username will be obtained from jwt and check it is existed in db , successful authorization will be set manually
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        try{
            String jwt = parseJwt(httpServletRequest);
            logger.info("jwt: "+ jwt);
            if(jwt != null && jwtUtils.validateJwtToken(jwt)){
                String username = jwtUtils.getUserNameFromJwtToken(jwt);

                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }
        catch (Exception e){
            logger.error("Failed to do user authentication");
        }

        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }

    private String parseJwt(HttpServletRequest request) {
        String headerAuth = request.getHeader("Authorization");

        if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
            return headerAuth.substring(7, headerAuth.length());
        }

        return null;
    }

}
  • To declare the logic of authentication which is a filter and the method of obtaining token from header of request

  • Obtain the token from header, then validate the token

  • If the token is null or not valid , the handler of failed authentication will be triggered to return the error response

  • If the token is valid, the authorization will be set

Handler of failed authentication

@Component
public class AuthEntryPointJwt implements AuthenticationEntryPoint {

    private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        logger.error("Unauthorized error: {}", authException.getMessage());
        ObjectMapper objectMapper = new ObjectMapper();
        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        AuthMessageResponse authMessageResponse = new AuthMessageResponse(HttpServletResponse.SC_UNAUTHORIZED, "Failed to authorize", System.currentTimeMillis(),true);
        response.getOutputStream().println(objectMapper.writeValueAsString(authMessageResponse));
    }

}
  • To declare the handling and the return response for failed authentication including user failed to login or access secured API end point without login

@Component
public class AccessHandler implements AccessDeniedHandler {
    private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        logger.error("AccessDenied error: {}", e.getMessage());
        ObjectMapper objectMapper = new ObjectMapper();
        httpServletResponse.setContentType("application/json");
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        AuthMessageResponse authMessageResponse = new AuthMessageResponse(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied!!!", System.currentTimeMillis(),true);
        httpServletResponse.getOutputStream().println(objectMapper.writeValueAsString(authMessageResponse));
    }
}
  • To declare the handling and the return response for the issue that user have not enough access right to access secured API end point , even though he is logged in

User Details

public class UserDetailsImpl implements UserDetails {

    private String username;

    private String email;

    @JsonIgnore
    private String password;

    private Collection<? extends GrantedAuthority> authorities;

    public UserDetailsImpl(String username, String email, String password, Collection<? extends GrantedAuthority> authorities) {
        this.username = username;
        this.email = email;
        this.password = password;
        this.authorities = authorities;
    }

    public static UserDetailsImpl build (Account account){
        List<GrantedAuthority> authorities = account.getRoleList().stream().map(role -> new SimpleGrantedAuthority(role.getAuthority().name())).collect(Collectors.toList());
        return new UserDetailsImpl(
                account.getUsername(),
                account.getEmail(),
                account.getPassword(),
                authorities
        );
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    public String getEmail() {
        return email;
    }

    @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 boolean equals(Object obj) {
        if (obj == null || getClass() != obj.getClass())
            return false;
        UserDetailsImpl user = (UserDetailsImpl) obj;
        return Objects.equals(username, user.username);
    }
}
  • User details is the data modal that will be returned if authentication is passed

  • Since the default user detail only include username , password and role

  • In order to extend the modal ( in this case, also include email), we customize the modal

User Details Service

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private AccountRepository accountRepository;

    private static final Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class);

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        logger.info("load User by Username: " + username);
        Account account = accountRepository.findAccountByUsername(username).orElseThrow(()-> new UsernameNotFoundException("User not found !!!"));
        return UserDetailsImpl.build(account);
    }
}
  • User details service is to declare how to get the user details

  • From here, we declare the method that get the user detail by searching table on database by using username

Json Web Token Utils

@Component
public class JwtUtils {
    private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);

    @Value("${petercheng.app.jwtSecret}")
    private String jwtSecret;

    public String generateJwtToken(Authentication authentication){
        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
        return Jwts.builder()
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public String getUserNameFromJwtToken(String token) {
        return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
    }

    public boolean validateJwtToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            logger.error("Invalid JWT signature: {}", e.getMessage());
        } catch (MalformedJwtException e) {
            logger.error("Invalid JWT token: {}", e.getMessage());
        } catch (ExpiredJwtException e) {
            logger.error("JWT token is expired: {}", e.getMessage());
        } catch (UnsupportedJwtException e) {
            logger.error("JWT token is unsupported: {}", e.getMessage());
        } catch (IllegalArgumentException e) {
            logger.error("JWT claims string is empty: {}", e.getMessage());
        }

        return false;
    }
}
  • To declare the method of generating token based on the user details from authentication

  • To declare the method of obtaining username from token

  • To do validate the token whether it is generated from our application or not

Enum

  • Declare the type of role

public enum ERole {
    ROLE_USER,
    ROLE_ADMIN
}

Modal

ORM

  • Declare the modal mapped with table of database

@Entity(name = "account")
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long userId;

    @Column(name = "name")
    private String username;

    @Column(name = "email")
    private String email;

    @Column(name = "password")
    private String password;

    @OneToMany(mappedBy = "account", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Role> roleList;

    ....
}
@Entity(name = "role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long roleId;

    @ManyToOne
    @JoinColumn(name = "account_id")
    private Account account;

    @Enumerated(EnumType.STRING)
    private ERole authority;

    ...
}

Request

public class LoginRequest {
    @NotBlank(message = "Cannot be empty !!!")
    private String username;

    @NotBlank(message = "Cannot be empty !!!")
    private String password;

    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;
    }
}
public class SignupRequest {

    @NotBlank(message = "Cannot be empty !!!")
    private String username;

    @NotBlank(message = "Cannot be empty !!!")
    @Pattern(regexp = "^.+@([a-z]+\\.)+[a-z]{2,4}$", message = "Wrong Format")
    private String email;

    @NotBlank(message = "Cannot be empty !!!")
    private String password;

    public SignupRequest() {
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

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

}

Response

public class AuthMessageResponse {
    private Integer status;
    private String message;
    private Long timeStamp;
    private boolean isError;

    public AuthMessageResponse() {
    }

    public AuthMessageResponse(Integer status, String message, Long timeStamp, boolean isError) {
        this.status = status;
        this.message = message;
        this.timeStamp = timeStamp;
        this.isError = isError;
    }
    ....
}
public class JwtResponse {
    private String token;
    private String username;
    private String email;
    private List<String> roles;

    public JwtResponse(String token, String username, String email, List<String> roles) {
        this.token = token;
        this.username = username;
        this.email = email;
        this.roles = roles;
    }
    ...
}

Exception and Exception handler

public class ValidationErrorException extends RuntimeException{
    public ValidationErrorException(String message) {
        super(message);
    }
}
@ControllerAdvice
public class CustomExceptionHandler {

    @ExceptionHandler({ValidationErrorException.class})
    public ResponseEntity<AuthMessageResponse> handleException(Exception exception){
        AuthMessageResponse authMessageResponse = new AuthMessageResponse();
        authMessageResponse.setStatus(HttpStatus.BAD_REQUEST.value());
        authMessageResponse.setMessage(exception.getMessage());
        authMessageResponse.setTimeStamp(System.currentTimeMillis());
        authMessageResponse.setError(true);
        return new ResponseEntity<>(authMessageResponse, HttpStatus.BAD_REQUEST);
    }

}

Repository

@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
    Boolean existsAccountByUsername(String username);
    Boolean existsAccountByEmail(String email);
    Optional<Account> findAccountByUsername(String username);
}

Controller

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

    private static final Logger logger = LoggerFactory.getLogger(AuthController.class);

    @Autowired
    private AuthService authService;

    @InitBinder
    public void initBinder(WebDataBinder dataBinder) {

        StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);

        dataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
    }

    @GetMapping("/test")
    public String test(){
        return "test";
    }

    @GetMapping("/preauthorize")
    @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
    public String preauthorize(){
        return "preauthorize";
    }

    @PostMapping("/signup")
    public ResponseEntity<AuthMessageResponse> registerUser(@Valid @RequestBody SignupRequest signupRequest, BindingResult theBindingResult){
        logger.info("SignUp Request: "+ signupRequest.toString());
        // If the validation is failed, return error message object
        if(theBindingResult.hasErrors()){
            throw new ValidationErrorException(theBindingResult.getFieldError().getDefaultMessage());
        }
        authService.signUpUser(signupRequest);
        return ResponseEntity.ok(new AuthMessageResponse(HttpStatus.ACCEPTED.value(), "User registered successfully", System.currentTimeMillis(), false));
    }

    @PostMapping("/signupAdmin")
    public ResponseEntity<AuthMessageResponse> registerAdmin(@Valid @RequestBody SignupRequest signupRequest, BindingResult theBindingResult){
        logger.info("SignUp Request: "+ signupRequest.toString());
        if(theBindingResult.hasErrors()){
            throw new ValidationErrorException(theBindingResult.getFieldError().getDefaultMessage());
        }
        authService.signUpAdmin(signupRequest);
        return ResponseEntity.ok(new AuthMessageResponse(HttpStatus.ACCEPTED.value(), "Admin registered successfully", System.currentTimeMillis(), false));
    }


    @PostMapping("/login")
    public ResponseEntity<JwtResponse> userLogin(@Valid @RequestBody LoginRequest loginRequest, BindingResult theBindingResult){
        logger.info("Login Request: " + loginRequest.toString());
        if(theBindingResult.hasErrors()){
            throw new ValidationErrorException(theBindingResult.getFieldError().getDefaultMessage());
        }
        JwtResponse jwtResponse = authService.userLogin(loginRequest);
        return ResponseEntity.ok(jwtResponse);
    }

Service

@Service
public class AuthServiceImpl implements AuthService {
    private static Logger logger = LoggerFactory.getLogger(AuthServiceImpl.class);

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private AccountRepository accountRepository;

    @Autowired
    private PasswordEncoder encoder;

    @Autowired
    private JwtUtils jwtUtils;

    @Override
    public void signUpUser(SignupRequest signupRequest){
        // Check whether the user is existed or not
        if(accountRepository.existsAccountByUsername(signupRequest.getUsername())){
            throw new ValidationErrorException("Error: Username is already taken ");
        }
        if(accountRepository.existsAccountByEmail(signupRequest.getEmail())){
            throw new ValidationErrorException("Error: Email is already taken ");
        }
        Account account = new Account(signupRequest.getUsername(), signupRequest.getEmail(), encoder.encode(signupRequest.getPassword()));
        List<Role> roleList = new ArrayList<>();
        roleList.add(new Role(account, ERole.ROLE_USER));
        account.setRoleList(roleList);
        accountRepository.save(account);
    }

    @Override
    public void signUpAdmin(SignupRequest signupRequest){
        if(accountRepository.existsAccountByUsername(signupRequest.getUsername())){
            throw new ValidationErrorException("Error: Username is already taken ");
        }
        if(accountRepository.existsAccountByEmail(signupRequest.getEmail())){
            throw new ValidationErrorException("Error: Email is already taken ");
        }
        Account account = new Account(signupRequest.getUsername(), signupRequest.getEmail(), encoder.encode(signupRequest.getPassword()));
        List<Role> roleList = new ArrayList<>();
        roleList.add(new Role(account, ERole.ROLE_USER));
        roleList.add(new Role(account, ERole.ROLE_ADMIN));
        account.setRoleList(roleList);
        accountRepository.save(account);
    }


    @Override
    public JwtResponse userLogin(LoginRequest loginRequest){
        logger.info("user Login");
        // Do the authentication according to username and password
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
        
        // If passed, set the state to be authenticated
        SecurityContextHolder.getContext().setAuthentication(authentication);
        String jwt = jwtUtils.generateJwtToken(authentication);

        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
        List<String> roles = userDetails.getAuthorities().stream()
                .map(item -> item.getAuthority())
                .collect(Collectors.toList());
        
        // return the token to frontend
        return new JwtResponse(jwt,
                userDetails.getUsername(),
                userDetails.getEmail(),
                roles);
    }

Flow

Login

  1. Post username and password

  2. Put it into UsernamePasswordAuthToken

  3. Do authentication by authentication manager which is help by user details service and password encoder

  4. If failed , go to error handler

  5. If success, return token

Access Secured Api End Point

  1. User make a request

  2. Enter custom authentication filter which is a single execution for each request to api

  3. Extract token from header of request

  4. Validate the token

  5. If failed, go to error handler

  6. If success, access the API end point

Reference

LogoSpring Boot Token based Authentication with Spring Security & JWT - BezKoderBezKoder
https://github.com/headshootcheng/springsecuritygithub.com