Backend Development 22 min read

Implementing Login Authentication with Session and JWT in Spring Boot

This article provides a step‑by‑step guide to implementing login authentication in Spring Boot, covering both session‑based and JWT‑based approaches, including code for controllers, filters, interceptors, context utilities, and best‑practice considerations for secure and scalable backend development.

Top Architect
Top Architect
Top Architect
Implementing Login Authentication with Session and JWT in Spring Boot

The article introduces the fundamentals of login authentication, explaining why frameworks like Shiro and Spring Security exist and outlining the goal of the series: to demonstrate authentication and authorization without relying solely on security frameworks.

It first explains the principle of authentication, the stateless nature of HTTP, and the need for a credential (session ID or JWT) to maintain login state.

Session implementation

Using Spring Boot, a simple @RestController with login and logout endpoints stores the user object in HttpSession . Example code:

@RestController
public class SessionController {
    @PostMapping("login")
    public String login(@RequestBody User user, HttpSession session) {
        if ("admin".equals(user.getUsername()) && "admin".equals(user.getPassword())) {
            session.setAttribute("user", user);
            return "登录成功";
        }
        return "账号或密码错误";
    }

    @GetMapping("/logout")
    public String logout(HttpSession session) {
        session.removeAttribute("user");
        return "退出成功";
    }
}

Subsequent API endpoints retrieve the user from the session and return data only if the user is logged in.

A LoginFilter (extending OncePerRequestFilter ) is added to intercept all requests except the login URL, checking the session for a user and returning a JSON error when unauthenticated.

@Component
public class LoginFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if ("/login".equals(request.getRequestURI())) {
            filterChain.doFilter(request, response);
            return;
        }
        User user = (User) request.getSession().getAttribute("user");
        if (user != null) {
            filterChain.doFilter(request, response);
            return;
        }
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write("请先登录");
        out.flush();
        out.close();
    }
}

To avoid passing the session user through controller parameters, a RequestContext utility uses RequestContextHolder to obtain the current request and session, providing a static method to retrieve the logged‑in user.

public class RequestContext {
    public static HttpServletRequest getCurrentRequest() {
        return ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
    }
    public static User getCurrentUser() {
        return (User) getCurrentRequest().getSession().getAttribute("user");
    }
}

JWT implementation

The article then switches to a token‑based approach. It adds the jjwt dependency and creates a JwtUtil class with methods to generate and parse JWT strings using a secret key.

public final class JwtUtil {
    private static final String secretKey = "whatever";
    private static final Duration expiration = Duration.ofHours(2);
    public static String generate(String userName) {
        Date expiryDate = new Date(System.currentTimeMillis() + expiration.toMillis());
        return Jwts.builder()
                .setSubject(userName)
                .setIssuedAt(new Date())
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, secretKey)
                .compact();
    }
    public static Claims parse(String token) {
        if (StringUtils.isEmpty(token)) return null;
        try {
            return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();
        } catch (JwtException e) {
            System.err.println("解析失败!");
            return null;
        }
    }
}

A JwtController returns the generated token on successful login and validates the token on protected endpoints by reading the Authorization header.

@RestController
public class JwtController {
    @PostMapping("/login")
    public String login(@RequestBody User user) {
        if ("admin".equals(user.getUsername()) && "admin".equals(user.getPassword())) {
            return JwtUtil.generate(user.getUsername());
        }
        return "账号密码错误";
    }
    @GetMapping("api")
    public String api(HttpServletRequest request) {
        String jwt = request.getHeader("Authorization");
        if (JwtUtil.parse(jwt) == null) return "请先登录";
        return "api成功返回数据";
    }
}

To avoid repetitive token checks, a LoginInterceptor (extending HandlerInterceptorAdapter ) validates the JWT for every request except /login . When the token is valid, the interceptor stores the username in a thread‑local UserContext for later retrieval.

public class UserContext {
    private static final ThreadLocal
user = new ThreadLocal<>();
    public static void add(String userName) { user.set(userName); }
    public static void remove() { user.remove(); }
    public static String getCurrentUserName() { return user.get(); }
}
public class LoginInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if ("/login".equals(request.getRequestURI())) return true;
        Claims claims = JwtUtil.parse(request.getHeader("Authorization"));
        if (claims != null) {
            UserContext.add(claims.getSubject());
            return true;
        }
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write("请先登录");
        out.flush();
        out.close();
        return false;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserContext.remove();
        super.afterCompletion(request, response, handler, ex);
    }
}

The interceptor is registered in the Spring Boot application class by implementing WebMvcConfigurer and adding the interceptor to the registry.

@SpringBootApplication
public class LoginJwtApplication implements WebMvcConfigurer {
    public static void main(String[] args) { SpringApplication.run(LoginJwtApplication.class, args); }
    @Override
    public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()); }
}

Service layer code can now obtain the current username via UserContext.getCurrentUserName() without passing the session or token explicitly.

public void doSomething() {
    String currentUserName = UserContext.getCurrentUserName();
    System.out.println("Service层---当前用户登录名:" + currentUserName);
}

The article concludes with a comparison of Session vs. JWT, discussing their respective advantages and drawbacks, and provides practical tips such as protecting the JWT secret key and avoiding storing sensitive data in the token.

javaSpring BootSecurityJWTsessionLogin Authentication
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login 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.