Mastering Session Management and SSO: From Cookies to CAS with Redis
This article analyzes the limitations of traditional HTTP session mechanisms in single‑server and clustered environments, explains why session sharing is essential for multi‑system products, and presents practical solutions such as session replication and centralized Redis storage, followed by a detailed walkthrough of CAS‑based single sign‑on implementation with Java Spring code examples and a comparison to OAuth2.
Session Mechanism and Challenges
HTTP is a stateless protocol; servers create a Session identified by a JSESSIONID cookie. The typical flow is: check the incoming request for the cookie, retrieve the session data from server memory, or create a new session and set the cookie if none exists. The cookie is scoped to a single domain and is not shared across browser windows.
Session in a Clustered Environment
When multiple application instances sit behind a load balancer, a user's first request may create a session on server A while subsequent requests are routed to server B, which cannot find the session. Two common remedies are:
Session replication : copy session data to all nodes on each change (high cost and complexity).
Centralized session store : store session data in an external store such as Redis, allowing every node to read and write the same session.
Single Sign‑On (SSO) Overview
SSO lets a user authenticate once with a central authentication server and then access multiple downstream systems using a ticket that is exchanged between the client and the server.
CAS (Central Authentication Service) Demo with Spring MVC and 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
}Login Controller
@Controller
public class IndexController {
@Autowired
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, response);
}
// init and destroy omitted
}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)
<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">
用户名: <input type="text" th:field="*{username}"/>
密码: <input type="text" th:field="*{password}"/>
<input type="submit" value="Submit"/>
<input type="reset" value="Reset"/>
<input type="hidden" th:field="*{backurl}"/>
</form>
</div>
</body>
</html>Web System Demo (SSO with Redis)
The demo adds an SSOFilter that looks for a ticket parameter, retrieves the associated user object from Redis, stores it in the HTTP session, and removes the ticket.
SSO Filter
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 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) {
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);
}
filterChain.doFilter(request, response);
}
// init and destroy omitted
}Controller
@Controller
public class IndexController {
@Autowired
RedisTemplate redisTemplate;
@GetMapping("/index")
public ModelAndView index(HttpServletRequest request) {
ModelAndView mv = new ModelAndView();
UserForm user = (UserForm) request.getSession().getAttribute(SSOFilter.USER_INFO);
mv.setViewName("index");
mv.addObject("user", user);
return mv;
}
}CAS vs OAuth2
CAS provides Web‑SSO authentication for internal services; it verifies the user's identity for the client application.
OAuth2 is an authorization framework that enables third‑party applications to access a resource owner's data without exposing credentials, protecting the resource server.
Choose CAS for unified login across internal systems and OAuth2 when delegated access to external APIs is required.
Conclusion
The article walks through the progression from simple cookie‑based sessions to Redis‑backed session sharing and finally to a full CAS‑based SSO implementation, offering concrete Spring MVC code, filter configurations, and ticket handling to build secure, scalable authentication across multiple services.
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.
IT Architects Alliance
Discussion and exchange on system, internet, large‑scale distributed, high‑availability, and high‑performance architectures, as well as big data, machine learning, AI, and architecture adjustments with internet technologies. Includes real‑world large‑scale architecture case studies. Open to architects who have ideas and enjoy sharing.
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.
