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.

Architecture Digest
Architecture Digest
Architecture Digest
Technical Selection and Implementation of Authentication Using JWT and Session in a Java Backend

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.

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.

BackendJavaredisSpring BootJWTSession
Architecture Digest
Written by

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.

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.