Backend Development 17 min read

Implementing Single Sign-On (SSO) with CAS and Session Management in Java

This article explains traditional session mechanisms, challenges in clustered environments, and presents solutions such as session replication and centralized storage, then details the design and implementation of a CAS-based Single Sign-On system in Java, including code for user forms, controllers, filters, and configuration.

Top Architect
Top Architect
Top Architect
Implementing Single Sign-On (SSO) with CAS and Session Management in Java

When a product matrix grows, users must log in to each system separately, causing poor experience and security issues. This article introduces traditional session mechanisms, their limitations in clustered environments, and presents solutions such as session replication and centralized storage using Redis.

Traditional Session Mechanism and Identity Authentication

HTTP is stateless; servers create a session identified by JSESSIONID stored in a cookie or URL. If the cookie is disabled, the session ID appears in the URL as sessionid=... . Sessions are stored in server memory and are not shared across browsers or servers.

Session Processing Flow

Server checks for the session cookie value.

Uses the session ID to retrieve session data from storage.

If not found, creates a new session and returns the cookie.

Session Sharing in Clustered Environments

Two main approaches are session replication and centralized storage. Replication copies session data to all nodes, which is costly and may cause latency. Centralized storage places session data in a single service, typically Redis, allowing all nodes to access the same session.

Single Sign-On (SSO) with CAS

CAS (Central Authentication Service) provides a ticket‑based SSO solution that works across different domains. The workflow includes redirecting unauthenticated requests to ouath.com , logging in, storing a ticket‑session mapping in Redis, and redirecting back with the ticket to obtain the session.

Key Code Implementation

UserForm.java

public class UserForm implements Serializable { private static final long serialVersionUID = 1L; private String username; private String password; private String backurl; // getters and setters }

IndexController.java (CAS login demo)

@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().length() == 0) { response.sendRedirect("/index"); } else { response.sendRedirect(user.getBackurl() + "?ticket=" + ticket); } } @GetMapping("/index") public ModelAndView index(HttpServletRequest request) { ModelAndView mv = new ModelAndView(); Object user = request.getSession().getAttribute(LoginFilter.USER_INFO); mv.setViewName("index"); mv.addObject("user", user); return mv; } }

LoginFilter.java

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) { request.getRequestDispatcher("/toLogin").forward(request, response); return; } chain.doFilter(request, response); } }

LoginConfig.java (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 template)

<!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 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>

SSOFilter.java (centralized session handling)

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().toString()); return; } UserForm user = (UserForm) userInfo; request.getSession().setAttribute(USER_INFO, user); redisTemplate.delete(ticket); } chain.doFilter(request, response); } }

The article also compares CAS‑based SSO with OAuth2, noting that CAS focuses on authenticating users for multiple client applications, while OAuth2 authorizes third‑party access to resources.

JavaBackend DevelopmentCASSession ManagementSSO
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.