Understanding Traditional Session Mechanisms, Cluster Session Challenges, and SSO Solutions with CAS and OAuth2
The article explains why multiple independent systems hurt user experience, reviews traditional session and cookie based authentication, discusses session sharing problems in clustered environments, and presents centralized Redis storage and CAS‑based single sign‑on solutions with code examples and a comparison to OAuth2.
When a product portfolio grows, users must log in to each system separately, which leads to poor experience and higher password‑management costs; a unified authentication (single sign‑on) can greatly improve usability and security.
1. Traditional Session Mechanism and Authentication Scheme
HTTP is stateless, so servers create a new session for each request. The session is identified by a JSESSIONID cookie (or URL rewriting) and stored on the server side. The cookie lives only in browser memory; disabling cookies forces URL rewriting, exposing the session ID in the URL.
Session creation steps:
Server checks for a JSESSIONID in the request.
If present, the corresponding session data is retrieved from memory.
If absent, a new session and ID are generated and sent back to the client.
Session data is kept in a hash table in memory, which works for a single‑node deployment.
2. Session Problems in a Clustered Environment and Solutions
In a distributed setup, load balancers may route consecutive requests from the same user to different servers, causing the session stored on one node to be unavailable on another. Two main approaches to solve this are:
Session replication – copy session data to all nodes (high cost, latency).
Centralized session storage – store sessions in an external store such as Redis, allowing every node to read/write the same session data.
The article recommends the Redis‑based centralized approach and shows a diagram of the architecture.
3. Multi‑service Login Dilemma and SSO Solutions
Enterprises often have many independent systems, each with its own login page and session, which is cumbersome for users. Single sign‑on (SSO) solves this by using a central authentication domain (e.g., oauth.com ) that issues a ticket which other systems can exchange for a session ID.
3.2 CAS (Central Authentication Service) Implementation
The article details the CAS flow:
User accesses b.com , is redirected to oauth.com for login.
After successful login, oauth.com creates a ticket and stores <ticket, sessionId> in Redis.
b.com receives the ticket, retrieves the session ID from Redis, creates its own cookie, and redirects the user back.
Code snippets for the demo are provided.
3.2.2.1 CAS Login Service Demo Core Code – User Entity
public class UserForm implements Serializable { private static final long serialVersionUID = 1L; private String username; private String password; private String backurl; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getBackurl() { return backurl; } public void setBackurl(String backurl) { this.backurl = backurl; } }Login Controller
@Controller public class IndexController { @Autowired private RedisTemplate redisTemplate; @GetMapping("/toLogin") public String toLogin(Model model, HttpServletRequest request) { Object userInfo = request.getSession().getAttribute(LoginFilter.USER_INFO); if (userInfo != null) { String ticket = UUID.randomUUID().toString(); redisTemplate.opsForValue().set(ticket, userInfo, 2, TimeUnit.SECONDS); return "redirect:" + request.getParameter("url") + "?ticket=" + ticket; } UserForm user = new UserForm(); user.setUsername("laowang"); user.setPassword("laowang"); user.setBackurl(request.getParameter("url")); model.addAttribute("user", user); return "login"; } @PostMapping("/login") public void login(@ModelAttribute UserForm user, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { request.getSession().setAttribute(LoginFilter.USER_INFO, user); String ticket = UUID.randomUUID().toString(); redisTemplate.opsForValue().set(ticket, user, 20, TimeUnit.SECONDS); if (user.getBackurl() == null || user.getBackurl().isEmpty()) { response.sendRedirect("/index"); } else { response.sendRedirect(user.getBackurl() + "?ticket=" + ticket); } } @GetMapping("/index") public ModelAndView index(HttpServletRequest request) { ModelAndView mv = new ModelAndView(); UserForm user = (UserForm) request.getSession().getAttribute(LoginFilter.USER_INFO); mv.setViewName("index"); mv.addObject("user", user); request.getSession().setAttribute("test", "123"); return mv; } }Login Filter
public class LoginFilter implements Filter { public static final String USER_INFO = "user"; @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; Object userInfo = request.getSession().getAttribute(USER_INFO); String requestUrl = request.getServletPath(); if (!"/toLogin".equals(requestUrl) && !requestUrl.startsWith("/login") && userInfo == null) { request.getRequestDispatcher("/toLogin").forward(request, response); return; } filterChain.doFilter(request, servletResponse); } }Filter Registration
@Configuration public class LoginConfig { @Bean public FilterRegistrationBean sessionFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new LoginFilter()); registration.addUrlPatterns("/*"); registration.setName("sessionFilter"); registration.setOrder(1); return registration; } }Login Page (Thymeleaf)
<!DOCTYPE HTML><html xmlns:th="http://www.thymeleaf.org"><head><title>enjoy login</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/></head><body><div style="text-align:center"><h1>请登陆</h1><form th:action="@{/login}" th:object="${user}" method="post"><p>用户名: <input type="text" th:field="*{username}"/></p><p>密 码: <input type="text" th:field="*{password}"/></p><p><input type="submit" value="Submit"/> <input type="reset" value="Reset"/></p><input type="text" th:field="*{backurl}" hidden="hidden"/></form></div></body></html>3.2.3 Difference Between CAS SSO and OAuth2
CAS focuses on authenticating the user for the client application (protecting client resources), while OAuth2 authorizes third‑party applications to access the resource server on behalf of the user (protecting server resources). Use CAS for unified login, OAuth2 for delegated access.
In summary, the article walks through the evolution from simple cookie‑based sessions to distributed session sharing, introduces centralized Redis storage, and provides a complete CAS‑based SSO demo with code, concluding with a brief comparison to OAuth2.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
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.