Information Security 15 min read

How to Build Single Sign‑On (SSO) with CAS and Session Sharing in Java

This article explains why multiple independent login systems hurt user experience and security, reviews traditional session mechanisms and their limitations in clustered environments, and then presents two session‑sharing strategies and a complete CAS‑based SSO solution with Java code examples.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
How to Build Single Sign‑On (SSO) with CAS and Session Sharing in Java

Background

When a company has many products, users must switch between systems, which leads to poor experience and higher password‑management costs. The lack of a unified authentication also reduces overall product security.

Traditional Session Mechanism and Authentication Scheme

HTTP is a stateless protocol, so each request creates a new thread on the server without preserving client context. To identify a user across requests, a session is created on the server and a

JSESSIONID

is sent to the browser via a cookie (or URL rewriting if cookies are disabled).

Typical flow for each HTTP request:

The server looks for the cookie containing the session ID.

If found, it retrieves the session data from the server‑side store.

If not found, a new session is created and the session ID is written back as a cookie.

Sessions are stored in memory as hash tables on the server. Because the session ID is stored in the browser’s memory, it cannot be shared across different browser windows or across different servers in a cluster.

Session Sharing Problems in a Cluster

When traffic grows, applications are deployed on multiple servers behind a load balancer. A user’s first request may be handled by server A, creating a session there; a subsequent request may be routed to server B, which cannot find the session, resulting in a failed login state.

Session Sharing Solutions

Two common approaches are used:

Session replication : copy session data to all servers. This incurs high implementation cost and latency.

Centralized session storage : store sessions in a dedicated service (e.g., Redis) that all servers read from and write to, eliminating the need for replication.

Multi‑Service Login Dilemma and SSO Solution

Enterprises often have many independent systems, each with its own login page. Users must log in repeatedly, which is cumbersome. Single Sign‑On (SSO) solves this by allowing a user to log in once and gain access to all systems via a shared ticket.

CAS – The Underlying SSO Mechanism

CAS (Central Authentication Service) implements SSO using a ticket‑based workflow:

System A redirects the user to

ouath.com

for login.

User authenticates; CAS creates a ticket and stores

{ticket: sessionId}

in Redis.

CAS redirects back to System A with the ticket.

System A retrieves the session ID from Redis using the ticket, creates a cookie, and grants access.

The overall interaction diagram is shown below:

CAS SSO flow diagram
CAS SSO flow diagram

CAS Implementation Demo (Java Spring)

User entity :

<code>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
}</code>

Login controller (handles login and redirects with ticket):

<code>@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();
        Object user = request.getSession().getAttribute(LoginFilter.USER_INFO);
        mv.setViewName("index");
        mv.addObject("user", user);
        return mv;
    }
}</code>

Login filter (protects resources):

<code>public class LoginFilter implements Filter {
    public static final String USER_INFO = "user";
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        Object userInfo = request.getSession().getAttribute(USER_INFO);
        String url = request.getServletPath();
        if (!"/toLogin".equals(url) && !url.startsWith("/login") && userInfo == null) {
            request.getRequestDispatcher("/toLogin").forward(request, response);
            return;
        }
        chain.doFilter(request, response);
    }
    // init and destroy omitted
}</code>

Filter registration (Spring configuration) :

<code>@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;
    }
}</code>

CAS SSO filter (centralized ticket handling) :

<code>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 resp, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        Object userInfo = request.getSession().getAttribute(USER_INFO);
        String url = request.getServletPath();
        if (!"/toLogin".equals(url) && !url.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
}</code>

CAS vs. OAuth2

OAuth2 is a third‑party authorization protocol that lets a client access resources without the user providing credentials directly.

CAS (Central Authentication Service) is a Kerberos‑style ticket system that provides web‑based SSO, focusing on authenticating the user once for multiple web applications.

In short, CAS secures the client‑side user identity, while OAuth2 secures server‑side resource access.

Choosing the right solution depends on whether you need a unified login (CAS) or delegated resource access for third‑party apps (OAuth2).

JavaredisSpringauthenticationCASSession ManagementSSO
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

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.