Comparison of JWT and Session Authentication and Their Java Implementation

This article compares JWT and session authentication, explains their differences, outlines the authentication flows, evaluates advantages, disadvantages, security, performance and one‑time use issues, and provides a complete Java implementation with JWT, Redis, interceptor and utility classes.

Architecture Digest
Architecture Digest
Architecture Digest
Comparison of JWT and Session Authentication and Their Java Implementation

Technical Selection

When implementing authentication, developers often consider JWT or session; this section introduces the question of which to pick.

Differences

The main difference lies in where the user state is stored: session data is kept on the server , while JWT is stored on the client .

Authentication Process

Session‑based Authentication Flow

User submits username and password; the server validates and creates a session stored in the database.

The server generates a sessionId and sends it to the browser as a cookie.

On subsequent requests the server reads the cookie, looks up the sessionId in the database and verifies the request.

JWT‑based Authentication Flow

User submits credentials; the server validates and creates a token saved in the database.

The front‑end stores the token in a cookie or localStorage and sends it with every request.

The server retrieves the token, checks its validity in the database, and processes the request.

Advantages and Disadvantages

JWT is client‑side, so no extra work is needed in a distributed environment; session requires data sharing across machines.

Session relies on cookies, which are not supported on some mobile clients.

Security

The JWT payload is only Base64‑encoded, so sensitive data should not be stored in it; session data stays on the server and is more secure.

Performance

JWT strings can be large, often exceeding the 4 KB cookie limit, so they are usually stored in localStorage and sent in HTTP headers, increasing request size compared with a short sessionId.

One‑time Use

JWT is stateless, which means it cannot be modified without issuing a new token; it also cannot be revoked before expiration unless combined with a store such as Redis.

Choosing JWT or Session

The author prefers JWT for its simplicity in distributed systems, while acknowledging that session can be made work through sticky sessions, session sharing, replication, persistence, or Terracotta.

Feature Implementation

JWT Dependencies

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.3</version>
</dependency>

JWT Utility Class

public class JWTUtil {
    private static final Logger logger = LoggerFactory.getLogger(JWTUtil.class);
    private static final String TOKEN_SECRET = "123456";

    /** Generate token with custom expiration (ms) */
    public static String generateToken(UserTokenDTO userTokenDTO) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            Map<String, Object> header = new HashMap<>(2);
            header.put("Type", "Jwt");
            header.put("alg", "HS256");
            return JWT.create()
                .withHeader(header)
                .withClaim("token", JSONObject.toJSONString(userTokenDTO))
                .sign(algorithm);
        } catch (Exception e) {
            logger.error("generate token occur error, error is:{}", e);
            return null;
        }
    }

    /** Verify token and parse payload */
    public static UserTokenDTO parseToken(String token) {
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
        JWTVerifier verifier = JWT.require(algorithm).build();
        DecodedJWT jwt = verifier.verify(token);
        String tokenInfo = jwt.getClaim("token").asString();
        return JSON.parseObject(tokenInfo, UserTokenDTO.class);
    }
}

Redis Utility Class

public final class RedisServiceImpl implements RedisService {
    private final Long DURATION = 1 * 24 * 60 * 60 * 1000L;
    @Resource
    private RedisTemplate redisTemplate;
    private ValueOperations<String, String> valueOperations;

    @PostConstruct
    public void init() {
        RedisSerializer redisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(redisSerializer);
        redisTemplate.setValueSerializer(redisSerializer);
        redisTemplate.setHashKeySerializer(redisSerializer);
        redisTemplate.setHashValueSerializer(redisSerializer);
        valueOperations = redisTemplate.opsForValue();
    }

    @Override
    public void set(String key, String value) {
        valueOperations.set(key, value, DURATION, TimeUnit.MILLISECONDS);
        log.info("key={}, value is: {} into redis cache", key, value);
    }

    @Override
    public String get(String key) {
        String redisValue = valueOperations.get(key);
        log.info("get from redis, value is: {}", redisValue);
        return redisValue;
    }

    @Override
    public boolean delete(String key) {
        boolean result = redisTemplate.delete(key);
        log.info("delete from redis, key is: {}", key);
        return result;
    }

    @Override
    public Long getExpireTime(String key) {
        return valueOperations.getOperations().getExpire(key);
    }
}

Business Implementation

Login Function

public String login(LoginUserVO loginUserVO) {
    // 1. Validate username and password
    UserPO userPO = userMapper.getByUsername(loginUserVO.getUsername());
    if (userPO == null) {
        throw new UserException(ErrorCodeEnum.TNP1001001);
    }
    if (!loginUserVO.getPassword().equals(userPO.getPassword())) {
        throw new UserException(ErrorCodeEnum.TNP1001002);
    }
    // 2. Generate token
    UserTokenDTO userTokenDTO = new UserTokenDTO();
    PropertiesUtil.copyProperties(userTokenDTO, loginUserVO);
    userTokenDTO.setId(userPO.getId());
    userTokenDTO.setGmtCreate(System.currentTimeMillis());
    String token = JWTUtil.generateToken(userTokenDTO);
    // 3. Store token in Redis
    redisService.set(userPO.getId(), token);
    return token;
}

Logout Function

public boolean loginOut(String id) {
    boolean result = redisService.delete(id);
    if (!redisService.delete(id)) {
        throw new UserException(ErrorCodeEnum.TNP1001003);
    }
    return result;
}

Update Password Function

public String updatePassword(UpdatePasswordUserVO updatePasswordUserVO) {
    // 1. Update password in DB
    UserPO userPO = UserPO.builder().password(updatePasswordUserVO.getPassword())
            .id(updatePasswordUserVO.getId())
            .build();
    UserPO user = userMapper.getById(updatePasswordUserVO.getId());
    if (user == null) {
        throw new UserException(ErrorCodeEnum.TNP1001001);
    }
    if (userMapper.updatePassword(userPO) != 1) {
        throw new UserException(ErrorCodeEnum.TNP1001005);
    }
    // 2. Generate new token
    UserTokenDTO userTokenDTO = UserTokenDTO.builder()
            .id(updatePasswordUserVO.getId())
            .username(user.getUsername())
            .gmtCreate(System.currentTimeMillis()).build();
    String token = JWTUtil.generateToken(userTokenDTO);
    // 3. Update token in Redis
    redisService.set(user.getId(), token);
    return token;
}

Interceptor Class

public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                         Object handler) throws Exception {
    String authToken = request.getHeader("Authorization");
    String token = authToken.substring("Bearer".length() + 1).trim();
    UserTokenDTO userTokenDTO = JWTUtil.parseToken(token);
    // 1. Verify request validity
    if (redisService.get(userTokenDTO.getId()) == null
            || !redisService.get(userTokenDTO.getId()).equals(token)) {
        return false;
    }
    // 2. Refresh token if expiration is near (30 min)
    if (redisService.getExpireTime(userTokenDTO.getId()) < 1 * 60 * 30) {
        redisService.set(userTokenDTO.getId(), token);
        log.error("update token info, id is:{}, user info is:{}", userTokenDTO.getId(), token);
    }
    return true;
}

Interceptor Configuration Class

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticateInterceptor())
                .excludePathPatterns("/logout/**")
                .excludePathPatterns("/login/**")
                .addPathPatterns("/**");
    }

    @Bean
    public AuthenticateInterceptor authenticateInterceptor() {
        return new AuthenticateInterceptor();
    }
}

Final Note

If there are any shortcomings, feel free to point them out.

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.

BackendJavaredisspringJWTSession
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

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.