Technical Selection and Implementation of Authentication Using JWT and Session in a Java Backend
This article compares session‑based and JWT‑based authentication, discusses their advantages, disadvantages, security and performance considerations, and provides a complete Java Spring Boot implementation—including dependency configuration, utility classes, login/logout/password update logic, and interceptor handling—using Redis for token management.
Technical Selection
The author, who previously implemented a user‑management module, explains the choice between JWT and session for authentication, outlining their differences, pros and cons, security, performance, and one‑time usage characteristics.
Difference
Session stores user state on the server side, while JWT stores it on the client side.
Authentication Process
Session‑Based Authentication Process
User submits username and password; server validates and creates a session stored in the database.
Server generates a sessionId and places it in a cookie sent to the browser.
For subsequent requests, the server reads the cookie, extracts the sessionId, and checks the database for validity.
JWT‑Based Authentication Process
User submits username and password; server validates and creates a token stored in the database.
Frontend receives the token and stores it in a cookie or local storage, attaching it to every request.
Server extracts the token from the request, verifies it against the database, and determines validity.
Advantages and Disadvantages
JWT is client‑side, requiring no extra work in distributed environments; session requires data sharing across servers.
Session relies on cookies, which are not supported on many mobile clients.
Security
JWT payload is only Base64‑encoded, so sensitive data should not be stored in it; session data resides on the server and is more secure.
Performance
JWT can become large after encoding, often exceeding cookie size limits, so it is usually stored in local storage and sent in headers, increasing request size compared to a short sessionId.
One‑Time Use
JWT is stateless and cannot be altered without issuing a new token; it cannot be revoked before expiration unless combined with a store such as Redis.
Choosing JWT or Session
The author votes for JWT because it avoids the need for multi‑machine session sharing in distributed systems, and its revocation issue can be mitigated with Redis.
Functional 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 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 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. Modify 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;
}Additional Notes
In a real project, administrators have delete‑user permissions, and passwords are transmitted encrypted, but these details are omitted from the demo.
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. Refresh token if near expiration
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();
}
}The article concludes with a brief promotional block for an architecture discussion group, which is unrelated to the technical content.
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.
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.
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.
