JWT vs Session: Pros, Cons, and How to Implement Secure Authentication

This article compares JWT and session-based authentication, detailing their differences, advantages, disadvantages, security considerations, performance impacts, and provides a complete Java implementation with token generation, Redis storage, login, logout, password update, and interceptor configuration.

Programmer DD
Programmer DD
Programmer DD
JWT vs Session: Pros, Cons, and How to Implement Secure Authentication
In recent work I was responsible for the user management module, which involves encryption and authentication; encryption was covered in a previous article. This piece discusses the technical choices and implementation of authentication, a straightforward yet valuable exercise for a newcomer.

Technical Selection

When implementing authentication, JWT and session are the obvious options, but what are their differences, pros and cons, and which should be chosen?

Difference

The main difference between session-based and JWT-based approaches 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

The user enters username and password in the browser; the server validates the password and creates a session stored in the database.

The server generates a sessionId and places a cookie containing the sessionId in the user's browser; subsequent requests carry this cookie.

The server retrieves the cookie, extracts the sessionId, looks it up in the database, and determines whether the request is valid.

JWT-based authentication flow

The user enters username and password; the server validates the password and generates a token stored in the database.

The front end receives the token and stores it in a cookie or local storage; subsequent requests include the token.

The server extracts the token, verifies it against the database, and determines its validity.

Advantages and Disadvantages

JWT is stored on the client, so in a distributed environment no extra work is needed; session requires multi‑node data sharing.

Session usually relies on cookies, which browsers must support, making it unsuitable for some mobile scenarios.

Security

The JWT payload is Base64‑encoded, so sensitive data should not be stored in JWT . Session data resides on the server, which is relatively more secure.

If sensitive information is stored in JWT, it can be decoded, posing a security risk.

Performance

After encoding, JWT can become very long; the typical cookie size limit is 4 KB, so JWT is usually stored in local storage. Each HTTP request carries the JWT in the header, making the request larger than a session‑based request where only a short sessionId is sent.

One‑time Nature

JWT is stateless, which means it is one‑time use; modifying its content requires issuing a new JWT.

Cannot be revoked: once issued, a JWT remains valid until expiration. Revocation can be handled by combining with Redis.

Renewal: traditional cookie renewal refreshes the session expiration on each access. For JWT, a new token must be issued, either on every request (inefficient) or by resetting its Redis expiration.

Choosing JWT or Session

I vote for JWT. Although JWT has drawbacks, it avoids the need for multi‑node session sharing in distributed systems. Session sharing can be achieved with sticky sessions, session replication, persistence, etc., but JWT works out‑of‑the‑box. Its one‑time limitation can be mitigated with Redis, so the project adopts JWT for authentication.

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 (milliseconds)
     */
    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 correctness
     */
    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

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

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

Update Password

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

Feel free to point out any omissions or errors.

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.

JavaredisJWT
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

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.