Technical Selection and Implementation of Authentication: JWT vs Session
This article compares JWT and session-based authentication, detailing their differences, certification processes, advantages, disadvantages, security considerations, performance impacts, token renewal, and revocation strategies, and provides a complete Java implementation using Spring, Redis, and custom utility classes.
In recent work on a user‑management module, the author needed to implement authentication and chose to compare JWT and session‑based approaches.
Technical Selection
Difference
Session stores user state on the server, while JWT stores it on the client.
Authentication Process
Session‑based flow
User logs in, server creates a session and stores it in the database.
Server returns a sessionId in a cookie.
Subsequent requests include the cookie; server validates the session by looking up the sessionId .
JWT‑based flow
User logs in, server creates a token and stores it in the database.
Frontend stores the token in a cookie or local storage and sends it in request headers.
Server validates the token by checking its presence and validity in the database.
Pros and Cons
JWT is stateless and works well in distributed environments without extra session sharing, but it cannot be used on mobile browsers that block cookies; session requires server‑side storage and cookie support.
Security
JWT payload is only Base64‑encoded, so sensitive data should not be stored inside; session data remains on the server, offering better protection.
Performance
JWTs can become large, increasing HTTP header size, whereas a session ID is a short string, making session‑based requests lighter.
One‑time Use
JWTs are immutable; to modify contents a new token must be issued.
Revocation
Once issued, a JWT remains valid until expiration; revocation can be handled by tracking tokens in Redis.
Renewal
To extend JWT validity, either issue a new token on each request (inefficient) or refresh its Redis expiration.
Choice
The author prefers JWT for its simplicity in distributed systems, supplementing it with Redis to handle revocation and renewal.
Implementation
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";
public static String generateToken(UserTokenDTO userTokenDTO) { ... }
public static UserTokenDTO parseToken(String token) { ... }
}Redis Service Wrapper
public final class RedisServiceImpl implements RedisService {
private final Long DURATION = 1 * 24 * 60 * 60 * 1000L;
@Resource private RedisTemplate redisTemplate;
@PostConstruct public void init() { ... }
public void set(String key, String value) { ... }
public String get(String key) { ... }
public boolean delete(String key) { ... }
public Long getExpireTime(String key) { ... }
}Business Logic
Login
public String login(LoginUserVO loginUserVO) {
// validate credentials
// generate token
// store token in Redis
return token;
}Logout
public boolean loginOut(String id) {
boolean result = redisService.delete(id);
if (!result) { throw new UserException(ErrorCodeEnum.TNP1001003); }
return result;
}Update Password
public String updatePassword(UpdatePasswordUserVO vo) {
// update password in DB
// generate new token
// replace token in Redis
return token;
}Interceptor for Token Validation and Renewal
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 dto = JWTUtil.parseToken(token);
if (redisService.get(dto.getId()) == null || !redisService.get(dto.getId()).equals(token)) {
return false;
}
if (redisService.getExpireTime(dto.getId()) < 30 * 60) {
redisService.set(dto.getId(), token);
}
return true;
}Interceptor Configuration
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticateInterceptor())
.excludePathPatterns("/logout/**", "/login/**")
.addPathPatterns("/**");
}
@Bean
public AuthenticateInterceptor authenticateInterceptor() {
return new AuthenticateInterceptor();
}
}The article concludes with a note that admin users have extra permissions and that passwords should be transmitted encrypted in production.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
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.