Mastering Single Sign-On (SSO) with CAS: From Session Basics to Full Demo
This article explains why unified authentication is essential for product suites, reviews traditional session mechanisms and their limitations in clustered environments, explores session‑sharing strategies, introduces CAS‑based SSO workflows, compares CAS with OAuth2, and provides a complete Spring Boot demo with code snippets and configuration details.
When a company offers many products, users often have to log in separately to each system, leading to poor experience and higher password‑management costs. Implementing a unified authentication (single sign‑on) improves usability and security.
Traditional Session Mechanism and Authentication
HTTP is stateless, so servers create a new session for each request. The session ID (usually JSESSIONID) is stored in a cookie or URL rewrite, allowing the server to retrieve user context on subsequent requests. However, the session ID is not shared across browser windows and cannot be used across different domains.
Server‑Side Session Process
The server checks the request for a JSESSIONID. If found, it loads the session from memory.
If the session ID is missing, the server creates a new session and returns the ID to the client.
Each request follows: read cookie → fetch session data → create session if absent.
Session‑Based Authentication Flow
Because HTTP is stateless, most web applications rely on this cookie‑based session approach, which works well for single‑server deployments.
Session Challenges in Clustered Environments
When traffic grows, applications are deployed on multiple servers behind a load balancer. A user’s consecutive requests may hit different servers, causing the session stored on one server to be unavailable on another.
Session Sharing Solutions
Session replication – copy session data to all nodes (high cost, latency).
Centralized session storage – store sessions in an external store such as Redis, allowing all nodes to read/write the same session data.
Login Difficulties in Multi‑Service Systems and SSO Solutions
Large enterprises often have many independent systems, each requiring its own login. Single Sign‑On (SSO) lets users authenticate once and access all systems without re‑logging.
CAS (Central Authentication Service) Workflow
User accesses b.com and is redirected to oauth.com for login.
After successful login, oauth.com sets a cookie for its domain.
The session ID is stored in Redis with a ticket key. b.com receives the ticket, retrieves the session ID from Redis, creates its own cookie, and redirects the user back.
Subsequent requests to b.com include the cookie, confirming the logged‑in state.
The diagram below (image) illustrates the full interaction.
CAS vs. OAuth2
OAuth2 is a third‑party authorization protocol that lets clients access resources without exposing user credentials.
CAS is a web‑SSO framework that authenticates users centrally and issues tickets for resource access.
CAS protects client‑side resources; OAuth2 protects server‑side resources.
CAS determines whether a user has permission to access a client; OAuth2 determines whether a client can access a user’s resources.
Therefore, use CAS for unified authentication and OAuth2 for third‑party resource delegation.
Demo Code (Spring Boot)
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 init(FilterConfig filterConfig) {}
@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) {
request.getRequestDispatcher("/toLogin").forward(request, response);
return;
}
chain.doFilter(request, response);
}
@Override public void destroy() {}
}SSO Filter (CAS)
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 init(FilterConfig filterConfig) {}
@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);
}
@Override public void destroy() {}
}Filter Configuration
@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>Web System Demo (Controller & View)
@Controller
public class IndexController {
@Autowired
private 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;
}
} <!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>enjoy index</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<div th:object="${user}">
<h1>cas-website:欢迎你</h1>
</div>
</body>
</html>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.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
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.
