Mastering Single Sign-On: From Session Basics to CAS Implementation
This article walks through the fundamentals of HTTP session handling, the challenges of session sharing in clustered environments, and presents a step‑by‑step design of a Single Sign‑On solution using CAS, including concrete code examples, Redis‑based session storage, and a comparison with OAuth2.
Problem: Multiple independent systems require separate logins
When a product portfolio grows, users must log in to each system separately, which degrades user experience and increases password‑management overhead. A unified authentication mechanism—Single Sign‑On (SSO)—is needed.
Traditional HTTP session mechanism
Cookie‑based JSESSIONID
HTTP is stateless, so the server creates a new thread for each request. To associate requests with the same user, the server generates a JSESSIONID cookie and stores it in the browser memory. If cookies are disabled, the session ID is appended to the URL (e.g., sessionid=KWJHUG6JJM65HS2K6).
Server‑side session handling
Server reads the cookie value (sessionId).
Looks up session data in an in‑memory hash table.
If not found, creates a new session, generates a cookie, and writes it to the response header.
Authentication flow
Client sends a request → server checks for a valid session → if absent, redirects to a login page → after successful login, server creates a session and returns the JSESSIONID cookie. Subsequent requests carry this cookie, allowing the server to identify the user.
Session sharing challenges in a clustered deployment
In a distributed deployment, a load balancer may route consecutive requests from the same user to different servers. Because the session is stored locally on each server, the second request may not find the original session, causing authentication loss.
Session replication
Each server copies session data to the others on login, update, or logout. This approach incurs high implementation cost, maintenance difficulty, and latency.
Centralized session store (Redis)
All servers read and write session data to a shared Redis store, eliminating synchronization issues and providing a single source of truth for session state.
Single Sign‑On with CAS
CAS login flow (step‑by‑step)
Client accesses b.com without a login; b.com redirects to the central domain ouath.com.
User logs in on ouath.com; a cookie is set for ouath.com.
The backend stores <ticket, sessionId> in Redis and redirects back to the original system.
When b.com receives the request with the ticket, it looks up the sessionId in Redis, synchronizes its own session, sets its own cookie, and redirects to the original page.
The request now carries a valid cookie; the backend validates the login state successfully.
CAS vs. OAuth2
OAuth2 is a third‑party authorization protocol that lets a client access resources without exposing user credentials; it protects server‑side resources. CAS is a web‑SSO framework that authenticates users centrally; it protects client‑side resource access. Use CAS when a unified login is required, and OAuth2 when delegated authorization to third‑party services is needed.
Demo implementation (Spring MVC + Redis)
User entity
public class UserForm implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private String password;
private String backurl;
// getters and setters omitted for brevity
}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 {
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);
return mv;
}
}Login filter
public class LoginFilter implements Filter {
public static final String USER_INFO = "user";
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
Object userInfo = request.getSession().getAttribute(USER_INFO);
String requestUrl = request.getServletPath();
if (!"/toLogin".equals(requestUrl) && !requestUrl.startsWith("/login") && userInfo == null) {
String ticket = request.getParameter("ticket");
if (ticket != null) {
userInfo = redisTemplate.opsForValue().get(ticket);
}
if (userInfo == null) {
response.sendRedirect("http://127.0.0.1:8080/toLogin?url=" + request.getRequestURL());
return;
}
request.getSession().setAttribute(USER_INFO, userInfo);
redisTemplate.delete(ticket);
}
chain.doFilter(request, response);
}
// init and destroy omitted
}SSO filter (alternative implementation)
public class SSOFilter implements Filter {
private RedisTemplate redisTemplate;
public static final String USER_INFO = "user";
public SSOFilter(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; }
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
Object userInfo = request.getSession().getAttribute(USER_INFO);
String requestUrl = request.getServletPath();
if (!"/toLogin".equals(requestUrl) && !requestUrl.startsWith("/login") && userInfo == null) {
String ticket = request.getParameter("ticket");
if (ticket != null) {
userInfo = redisTemplate.opsForValue().get(ticket);
}
if (userInfo == null) {
response.sendRedirect("http://127.0.0.1:8080/toLogin?url=" + request.getRequestURL());
return;
}
request.getSession().setAttribute(USER_INFO, userInfo);
redisTemplate.delete(ticket);
}
chain.doFilter(request, res);
}
// init and destroy omitted
}Thymeleaf login page
<!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>Please Log In</h1>
<form th:action="@{/login}" th:object="${user}" method="post">
<p>Username: <input type="text" th:field="*{username}"/></p>
<p>Password: <input type="text" th:field="*{password}"/></p>
<p><input type="submit" value="Submit"/> <input type="reset" value="Reset"/></p>
<input type="hidden" th:field="*{backurl}"/>
</form>
</div>
</body>
</html>Conclusion
The transition from a simple cookie‑based session model to a robust SSO architecture uses CAS for centralized authentication and Redis as a shared session store, solving the loss‑of‑session problem in clustered environments. The comparison with OAuth2 clarifies that CAS secures client‑side login while OAuth2 handles delegated authorization of server‑side resources, guiding architects to choose the appropriate protocol for their ecosystem.
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.
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.
