Backend Development 13 min read

Authentication Implementation: Choosing Between JWT and Session in Backend Development

This article explains the technical selection between JWT and session for authentication, compares their differences, advantages, and disadvantages, and provides a complete Java implementation—including token generation, Redis storage, login/logout, password update, and request interception—demonstrating why JWT was chosen for a distributed backend system.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Authentication Implementation: Choosing Between JWT and Session in Backend Development

Technical Selection

When implementing authentication, the two common approaches are JWT and session. The article compares their differences, advantages, and disadvantages to help decide which to use.

Difference

The main distinction is where the user state is stored: session is stored on the server side , while JWT is stored on the client side .

Authentication Process

Session-based Authentication Flow

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

The server generates a sessionId and places it in a cookie sent to the browser.

For subsequent requests, the server retrieves the cookie, extracts the sessionId , and checks the database to verify the request.

JWT-based Authentication Flow

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

The front‑end receives the token and stores it in a cookie or localStorage , sending it with each request.

The server extracts the token from the request, verifies it, and checks the database for validity.

Pros and Cons

JWT is client‑side, so no extra work is needed for distributed environments; session requires multi‑node data sharing.

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

Security

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

Storing sensitive information in JWT can be unsafe because it can be decoded.

Performance

JWT can become large, exceeding the typical 4KB cookie limit, so it is usually placed in localStorage . Each HTTP request carries the JWT in the header, increasing request size compared to a short sessionId .

One‑Time Use

JWT is stateless, which means once issued it remains valid until expiration and cannot be revoked without issuing a new token. A common solution is to combine JWT with Redis for revocation and renewal.

Cannot revoke : token stays valid until it expires.

Renewal : either refresh the token on every request or set an expiration in Redis and refresh the Redis entry when needed.

Choosing JWT or Session

The author votes for JWT because it avoids the extra work required for session sharing in distributed systems, and its revocation issue can be mitigated with Redis. Therefore, JWT is used for authentication in the project.

Implementation

JWT Dependency

<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 key
    private static final String TOKEN_SECRET = "123456";

    /**
     * Generate token with custom expiration (milliseconds)
     */
    public static String generateToken(UserTokenDTO userTokenDTO) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            Map
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
     */
    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 Service Implementation

public final class RedisServiceImpl implements RedisService {
    private final Long DURATION = 1 * 24 * 60 * 60 * 1000L; // 1 day
    @Resource
    private RedisTemplate redisTemplate;
    private ValueOperations
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 Logic

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 (!result) {
        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. Validate request
    if (redisService.get(userTokenDTO.getId()) == null ||
        !redisService.get(userTokenDTO.getId()).equals(token)) {
        return false;
    }
    // 2. Check if token needs renewal
    if (redisService.getExpireTime(userTokenDTO.getId()) < 1 * 60 * 30) { // less than 30 minutes
        redisService.set(userTokenDTO.getId(), token);
        log.error("update token info, id is:{}, user info is:{}", userTokenDTO.getId(), token);
    }
    return true;
}

Interceptor Configuration

@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();
    }
}

References

1. User Management Module: How to Ensure User Data Security – https://juejin.cn/post/6916150628955717646

Source: https://juejin.cn/post/6932702419344162823

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