Integrating JWT and Spring Security with a Custom SM4 PasswordEncoder in Spring Boot

This article demonstrates how to secure a Spring Boot 2.7.7 application using JWT, Spring Security, and a custom SM4‑based PasswordEncoder, covering dependency setup, security configuration, custom authentication components, token validation filter, and a login endpoint implementation.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Integrating JWT and Spring Security with a Custom SM4 PasswordEncoder in Spring Boot

The example uses the following technology stack: Spring Boot 2.7.7, MyBatis‑Plus 3.5.3.1, Hutool 5.7.22, Redis, MySQL 8, the Chinese national encryption algorithm SM4, JWT, and Lombok.

JWT Overview

JSON Web Token (JWT) is a widely used solution for user authentication.

Basic JWT Generation and Authentication Flow

A simplified sequence diagram illustrates the process of creating and validating a JWT.

Spring Security Overview

Spring Security is a powerful and highly customizable authentication and access‑control framework.

Spring Security provides a robust and extensible security framework.

Adding Spring Security Dependency

Include the following dependency in pom.xml:

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

Spring Security Configuration Notes

Note: Starting with Spring Boot 2.7.0, WebSecurityConfigurerAdapter is deprecated.

Typical configuration items include:

AuthenticationProvider implementation: custom authentication logic.

Filter: validates token validity.

AuthenticationManager: processes authentication requests.

PasswordEncoder: handles password hashing and verification.

SecurityFilterChain: the filter chain.

Custom PasswordEncoder

The PasswordEncoder interface encrypts passwords and verifies them. Spring Security offers several implementations such as BCryptPasswordEncoder, NoOpPasswordEncoder, Pbkdf2PasswordEncoder, and MessageDigestPasswordEncoder.

Implementing a SM4‑based PasswordEncoder

Steps to create a custom SM4 PasswordEncoder:

Add the Bouncy Castle dependency:

<!-- SM4 dependency -->
<dependency>
  <groupId>org.bouncycastle</groupId>
  <artifactId>bcprov-jdk15to18</artifactId>
  <version>1.71</version>
</dependency>

Create Sm4PasswordEncoder.java:

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.crypto.SmUtil;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

public class Sm4PasswordEncoder implements PasswordEncoder {
    // key length must be 16
    private static final String KEY = "KeyMustBe16Size.";

    @Override
    public String encode(CharSequence rawPassword) {
        return SmUtil.sm4(KEY.getBytes(StandardCharsets.UTF_8)).encryptHex(rawPassword.toString());
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return Objects.equals(rawPassword.toString(),
            SmUtil.sm4(KEY.getBytes(StandardCharsets.UTF_8)).decryptStr(encodedPassword, StandardCharsets.UTF_8));
    }
}

Register the custom PasswordEncoder bean in the security configuration.

Custom Token Validation Filter

Implement UserDetailsService to load user details, then create a filter that extracts the JWT from the Authorization header, retrieves the username, loads the user, and sets the authentication in the security context.

import cn.ddcherry.springboot.demo.constant.AuthConstant;
import cn.ddcherry.springboot.demo.util.JwtUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Validate token validity
 */
@Slf4j
public class TokenFilter extends OncePerRequestFilter {
    @Resource
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String token = getToken(request);
        if (StrUtil.isNotEmpty(token)) {
            String username = JwtUtil.getUsernameFromToken(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request, response);
    }

    private String getToken(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StrUtil.isNotEmpty(bearerToken) && bearerToken.startsWith(AuthConstant.AUTHORIZATION_BEARER)) {
            return bearerToken.replace(AuthConstant.AUTHORIZATION_BEARER, StrUtil.EMPTY);
        }
        return null;
    }
}

AuthenticationProvider Configuration

Define a DaoAuthenticationProvider bean that uses the custom UserDetailsService and the SM4 PasswordEncoder:

@Bean
public DaoAuthenticationProvider authenticationProvider() {
    DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
    authProvider.setUserDetailsService(userDetailsService);
    authProvider.setPasswordEncoder(passwordEncoder());
    return authProvider;
}

AuthenticationManager Bean

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
    return authConfig.getAuthenticationManager();
}

Security Filter Chain

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.cors()
        .and()
        .csrf().disable()
        .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
        .and()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .authorizeRequests()
            .antMatchers("/api/test/**").permitAll()
            .antMatchers("/api/auth/**").permitAll()
            .anyRequest().authenticated();

    http.authenticationProvider(authenticationProvider());
    http.addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class);
    return http.build();
}

Login API

The LoginController receives username and password, authenticates via AuthenticationManager, generates a JWT, and returns the token together with the authenticated user information.

@RestController
@AllArgsConstructor
@RequestMapping("/api/auth")
public class LoginController {
    private final AuthenticationManager authenticationManager;

    @PostMapping("/login")
    public Result<Map<String, Object>> login(String username, String password) {
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(username, password);
        Authentication authentication = authenticationManager.authenticate(authenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        String token = JwtUtil.createToken(username, new HashMap<>());
        AuthUser authUser = (AuthUser) authentication.getPrincipal();
        Map<String, Object> resultMap = new HashMap<>(16);
        resultMap.put("token", token);
        resultMap.put("user", authUser);
        return Result.success(resultMap);
    }
}

Running the application and invoking the /api/auth/login endpoint returns a JWT on successful authentication; failure returns an error response.

Source: juejin.cn/post/7250357906713215037

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Backend DevelopmentSpring BootJWTspring-securitySM4Custom PasswordEncoder
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.