Choosing Between JWT and Session: Pros, Cons, and Implementation Guide
This article compares JWT and session authentication, outlines their differences, advantages, security considerations, performance impacts, and provides a complete Java implementation with Redis integration, helping developers decide the best approach for their projects.
In recent work I was responsible for the user management module, which involves encryption and authentication. The encryption part was covered earlier; this article discusses the technology selection and implementation of the authentication feature.
Technical Selection
When implementing authentication, JWT and session are the two obvious choices. What are their differences, advantages, and disadvantages?
Difference
The main difference between session‑based and JWT‑based authentication 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 process
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 process
User enters username and password; the server validates and creates a token stored in the database.
The front end receives the token and stores it in a cookie or local storage; subsequent requests carry the token.
The server extracts the token, looks it up in the database, and checks its validity.
Advantages and Disadvantages
JWT is stored on the client, so in a distributed environment no extra work is needed; session is stored on the server, requiring data sharing across machines.
Session usually relies on cookies, which browsers must support, making it unsuitable for mobile clients.
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 and is unsafe.
Performance
After encoding, JWT can become very long; the typical cookie size limit is 4 KB, so JWT often cannot be placed in a cookie and is stored in local storage. Each HTTP request carries the JWT in the header, which may be larger than the request body, while a sessionId is a short string, making JWT requests heavier.
One‑time Use
JWT is stateless, which means it is one‑time use; to modify its content a new JWT must be issued.
JWT cannot be revoked before expiration; a common solution is to combine it with Redis.
Renewal: traditional cookie renewal refreshes the session expiration on each access. For JWT, the common approach is to issue a new token on each request or to store the token in Redis with an expiration time that is refreshed on access.
Choosing JWT or Session
I vote for JWT. Although JWT has many drawbacks, it does not require extra work for data sharing in distributed environments, unlike session which needs sticky sessions, session sharing, replication, persistence, or Terracotta. JWT’s one‑time drawback can be mitigated with Redis, so in practice I choose 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 key
private static final String TOKEN_SECRET = "123456";
/**
* Generate token with custom expiration (milliseconds)
* @param userTokenDTO
* @return token string
*/
public static String generateToken(UserTokenDTO userTokenDTO) {
try {
// Private key and algorithm
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
// Header
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
* @param token
* @return UserTokenDTO
*/
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);
}
}Explanation:
The generated token does not contain an expiration time; token expiration is managed by Redis.
UserTokenDTO does not carry sensitive fields such as passwords.
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);
}
}RedisTemplate simple wrapper.
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;
}Explanation: check credentials, generate JWT, store it in Redis.
Logout Function
public boolean loginOut(String id) {
boolean result = redisService.delete(id);
if (!redisService.delete(id)) {
throw new UserException(ErrorCodeEnum.TNP1001003);
}
return result;
}Delete the corresponding Redis key.
Update Password Function
public String updatePassword(UpdatePasswordUserVO updatePasswordUserVO) {
// 1. Update password
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;
}Explanation: after changing the password, a new token is generated and stored in Redis, allowing the front end to update its stored token without forcing the user to log in again.
Other Notes
In a real project, users are divided into normal and admin users; only admins can delete users (demo omitted).
Password transmission should be encrypted in production.
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 renewal is needed
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;
}Explanation: the interceptor validates the token and decides whether to renew it. Validation checks that the token exists in Redis and matches the stored value; renewal occurs when the remaining expiration is less than 30 minutes.
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 omissions or issues, feel free to point them out.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
