# How to Implement JWT Authentication and Token Refresh in the Mall Project

> Learn to implement JWT authentication and token refresh in the mall project. Discover how Spring Security, JwtTokenUtil, and JwtAuthenticationTokenFilter secure your API with HS512 tokens and a 30-minute refresh window.

- Repository: [macro/mall](https://github.com/macrozheng/mall)
- Tags: how-to-guide
- Published: 2026-02-28

---

**The mall project implements stateless JWT authentication using Spring Security, where `JwtTokenUtil` generates HS512-signed tokens, `JwtAuthenticationTokenFilter` validates them on every request, and a dedicated refresh endpoint re-issues tokens while enforcing a 30-minute safety window.**

The **mall** project (macrozheng/mall) is a comprehensive e-commerce system built on Spring Boot. Its authentication layer resides in the `mall-security` module and follows a classic **stateless JWT architecture** that eliminates server-side session storage.

## JWT Authentication Architecture Overview

The authentication flow consists of six distinct steps orchestrated across multiple components:

1. **Login Controller** (`UmsAdminController.login`) receives **username** and **password**, validates credentials against the database, and delegates token creation to `JwtTokenUtil`.

2. **Token Generation** (`JwtTokenUtil.generateToken`) constructs a JWT payload containing the username (`sub`) and a `created` timestamp, signs it with **HS512** using the configured secret, and sets the expiration defined by `jwt.expiration`.

3. **Request Filtering** (`JwtAuthenticationTokenFilter`) intercepts every incoming request, extracts the token from the **Authorization** header, strips the **Bearer** prefix, and validates the token signature.

4. **Security Context Population** occurs when the filter builds a `UsernamePasswordAuthenticationToken` and stores it in `SecurityContextHolder`, making the user authenticated for the duration of the request.

5. **Spring Security Configuration** (`SecurityConfig`) registers the JWT filter before `UsernamePasswordAuthenticationFilter` and enforces `SessionCreationPolicy.STATELESS`.

6. **Token Refresh** (`UmsAdminController.refreshToken`) allows clients to obtain a new token before expiration, provided the current token is valid and was not refreshed within the last 30 minutes.

## Token Generation and Validation

In [`mall-security/src/main/java/com/macro/mall/security/util/JwtTokenUtil.java`](https://github.com/macrozheng/mall/blob/main/mall-security/src/main/java/com/macro/mall/security/util/JwtTokenUtil.java), the `generateToken(UserDetails userDetails)` method creates tokens using the following structure:

```java
public String generateToken(UserDetails userDetails) {
    Map<String, Object> claims = new HashMap<>();
    claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
    claims.put(CLAIM_KEY_CREATED, new Date());
    return generateToken(claims);
}

```

The method packs two critical claims into the JWT payload: `sub` containing the username and `created` storing the generation timestamp. The token is signed with the **HS512** algorithm using the secret key defined in `jwt.secret` and expires after the duration specified in `jwt.expiration` (typically 604800 seconds or 7 days).

Validation occurs through `validateToken(String token, UserDetails userDetails)`, which extracts the username from the token, compares it with the provided `UserDetails`, and verifies the token has not expired via `isTokenExpired(token)`.

## Stateless Security Filter Implementation

The `JwtAuthenticationTokenFilter` class in [`mall-security/src/main/java/com/macro/mall/security/component/JwtAuthenticationTokenFilter.java`](https://github.com/macrozheng/mall/blob/main/mall-security/src/main/java/com/macro/mall/security/component/JwtAuthenticationTokenFilter.java) extends `OncePerRequestFilter` to ensure every request undergoes authentication checks:

```java
@Override
protected void doFilterInternal(HttpServletRequest request,
                                HttpServletResponse response,
                                FilterChain chain) throws ServletException, IOException {
    String header = request.getHeader(tokenHeader);
    if (header != null && header.startsWith(tokenHead)) {
        String token = header.substring(tokenHead.length());
        String username = jwtTokenUtil.getUserNameFromToken(token);
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            if (jwtTokenUtil.validateToken(token, userDetails)) {
                UsernamePasswordAuthenticationToken auth =
                    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }
    }
    chain.doFilter(request, response);
}

```

This filter extracts the token from the header defined by `jwt.tokenHeader` (typically **Authorization**), removes the **Bearer** prefix defined by `jwt.tokenHead`, and populates the security context only if validation succeeds. Because the filter runs on every request, the system remains completely **stateless** with no server-side session storage.

## Token Refresh Mechanism

The mall project implements a controlled refresh strategy to prevent token abuse while ensuring continuous sessions. The refresh endpoint in [`UmsAdminController.java`](https://github.com/macrozheng/mall/blob/main/UmsAdminController.java) delegates to `JwtTokenUtil.refreshHeadToken`:

```java
public String refreshHeadToken(String oldToken) {
    if (StrUtil.isEmpty(oldToken)) return null;
    String token = oldToken.substring(tokenHead.length());
    Claims claims = getClaimsFromToken(token);
    if (claims == null || isTokenExpired(token)) return null;
    if (tokenRefreshJustBefore(token, 30 * 60)) return token; // 30 min window
    claims.put(CLAIM_KEY_CREATED, new Date());
    return generateToken(claims);
}

```

The refresh logic enforces three critical rules:

- **Expiration Check**: Returns `null` if the token has already expired, forcing re-authentication.
- **Rate Limiting**: Returns the original token unchanged if it was refreshed within the last **30 minutes** (1800 seconds), preventing excessive token churn.
- **Timestamp Update**: Generates a new token with an updated `created` claim when refresh is permitted, effectively resetting the expiration window.

The controller endpoint exposes this functionality via a GET request to `/refreshToken`:

```java
@GetMapping("/refreshToken")
@ResponseBody
public CommonResult refreshToken(HttpServletRequest request) {
    String oldToken = request.getHeader(tokenHeader);
    String newToken = adminService.refreshToken(oldToken);
    if (newToken == null) return CommonResult.failed("token已经过期！");
    Map<String, String> map = new HashMap<>();
    map.put("token", newToken);
    map.put("tokenHead", tokenHead);
    return CommonResult.success(map);
}

```

## Spring Security Configuration

The `SecurityConfig` class in [`mall-security/src/main/java/com/macro/mall/security/config/SecurityConfig.java`](https://github.com/macrozheng/mall/blob/main/mall-security/src/main/java/com/macro/mall/security/config/SecurityConfig.java) wires the components together:

```java
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers(HttpMethod.OPTIONS).permitAll()
        .anyRequest().authenticated()
        .and()
        .csrf().disable()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .exceptionHandling()
            .accessDeniedHandler(restfulAccessDeniedHandler)
            .authenticationEntryPoint(restAuthenticationEntryPoint)
        .and()
        .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    return http.build();
}

```

This configuration disables CSRF protection (appropriate for stateless APIs), prevents session creation, and inserts the JWT filter before the standard username/password authentication filter. Public URLs (login, register, Swagger endpoints) are typically configured separately via `ignoreUrlsConfig`.

## Summary

- **Token Generation**: `JwtTokenUtil.generateToken` creates HS512-signed JWTs with username and timestamp claims in [`mall-security/src/main/java/com/macro/mall/security/util/JwtTokenUtil.java`](https://github.com/macrozheng/mall/blob/main/mall-security/src/main/java/com/macro/mall/security/util/JwtTokenUtil.java).
- **Request Authentication**: `JwtAuthenticationTokenFilter` validates tokens on every request and populates `SecurityContextHolder` without server-side sessions.
- **Refresh Safety**: The refresh mechanism in `UmsAdminController` enforces a 30-minute minimum interval between refreshes to prevent abuse while allowing session extension.
- **Stateless Architecture**: `SecurityConfig` ensures no sessions are created, making the system horizontally scalable and suitable for microservices deployment.

## Frequently Asked Questions

### How does the mall project store user authentication state?

The mall project uses a **completely stateless architecture** with no server-side session storage. Authentication state lives entirely inside the JWT token, which the client sends with every request in the **Authorization** header. The `JwtAuthenticationTokenFilter` validates this token and temporarily populates `SecurityContextHolder` for the duration of the single request.

### What algorithm does JwtTokenUtil use to sign tokens?

According to the source code in [`mall-security/src/main/java/com/macro/mall/security/util/JwtTokenUtil.java`](https://github.com/macrozheng/mall/blob/main/mall-security/src/main/java/com/macro/mall/security/util/JwtTokenUtil.java), the implementation uses **HS512** (HMAC with SHA-512) to sign tokens. The secret key is configured via the `jwt.secret` property in the application configuration.

### Why does the refresh token endpoint return the same token sometimes?

The `refreshHeadToken` method implements a safety mechanism that returns the original token unchanged if it was refreshed within the last **30 minutes** (configurable via the `tokenRefreshJustBefore` check). This prevents clients from generating excessive new tokens and reduces computational overhead when a valid token is still relatively fresh.

### Can I modify the token expiration time in the mall project?

Yes. Token expiration is controlled by the `jwt.expiration` configuration property (specified in seconds), which feeds into `JwtTokenUtil.generateToken`. Additionally, you can adjust the refresh rate-limiting window by modifying the `30 * 60` seconds parameter in the `tokenRefreshJustBefore` method call within [`JwtTokenUtil.java`](https://github.com/macrozheng/mall/blob/main/JwtTokenUtil.java).