Backend Development 14 min read

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:

<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
> 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
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

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

login 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.