Mastering Single Sign-On: From Traditional Sessions to CAS & OAuth2

This article explains the limitations of traditional HTTP session mechanisms, explores session sharing strategies in clustered environments, and provides a detailed walkthrough of implementing Single Sign-On using CAS, including code samples and a comparison with OAuth2 authentication.

Programmer DD
Programmer DD
Programmer DD
Mastering Single Sign-On: From Traditional Sessions to CAS & OAuth2

1 Traditional Session Mechanism and Authentication

1.1 Cookie and Server Interaction

HTTP is a stateless protocol, so each request creates a new thread on the server without automatic context preservation. To associate requests with the same user (e.g., a shopping cart), a session mechanism is introduced.

A session stores user context on the server, identified by a SessionID (commonly JSESSIONID) sent to the client via a cookie. If cookies are disabled, the SessionID can be passed through URL rewriting.

1.2 Server‑Side Session Mechanism

When the server receives a request, it checks for a JSESSIONID. If present, the corresponding session data is retrieved from memory; otherwise, a new session is created and the SessionID is returned to the client.

Typical processing steps for each HTTP request:

Server reads the cookie value (sessionId).

Server retrieves session data from its in‑memory store using the sessionId.

If no sessionId is found, the server creates a new session, generates a cookie, and adds it to the response header.

1.3 Session‑Based Authentication Flow

Because HTTP is stateless, most web applications rely on session‑based authentication. The flow is illustrated in the following diagram:

2 Session Challenges in Clustered Environments and Solutions

When traffic grows, a single server cannot handle the load, so applications are deployed across multiple machines behind a load balancer. A user’s successive requests may be routed to different servers, causing session loss because the session is stored locally on each server.

2.1 Session Sharing Approaches

Two common strategies:

Session replication – copy session data to all nodes.

Centralized session storage – store sessions in a shared store (e.g., Redis).

2.1.1 Session Replication

Session data is duplicated across servers. When a user logs in, modifies data, or logs out, the session information is propagated to other machines.

This approach has high implementation cost, maintenance difficulty, and can introduce latency.

2.1.2 Centralized Session Storage

All applications read and write session data to a dedicated service, typically Redis, eliminating the need for synchronization between nodes.

3 Multi‑Service Login Challenges and SSO Solutions

3.1 Background of SSO

Enterprises with many systems require users to log in separately to each one, leading to poor user experience and increased password management overhead. Single Sign‑On (SSO) allows a user to authenticate once and gain access to multiple systems.

3.2 CAS‑Based SSO Architecture

3.2.1 CAS Login Flow

Because cookies cannot be shared across different domains, a dedicated authentication domain (e.g., auth.com) issues a ticket that other systems use to obtain the user's session.

1. User accesses b.com → redirects to auth.com for login
2. User logs in on auth.com, which sets a cookie for auth.com
3. auth.com stores <ticket, sessionId> in Redis and redirects back to b.com with the ticket
4. b.com receives the ticket, retrieves the sessionId from Redis, creates its own cookie, and redirects to the original page
5. Subsequent requests to b.com include the cookie, and the server validates the login state

3.2.2 Core CAS Demo Code

1. 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
}

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

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

4. 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;
    }
}

5. 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 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>

3.2.2 SSO Filter (CAS) Demo

Filter that validates tickets and restores user sessions from Redis.

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;
            }
            request.getSession().setAttribute(USER_INFO, userInfo);
            redisTemplate.delete(ticket);
        }
        chain.doFilter(request, response);
    }
    // init and destroy omitted
}

3.2.3 CAS vs OAuth2

OAuth2 is an authorization protocol that lets third‑party applications access resources on behalf of a user without exposing credentials. CAS (Central Authentication Service) is a web‑based SSO framework that authenticates users for multiple web applications.

CAS protects client‑side resources; OAuth2 protects server‑side resources.

CAS determines whether a user has permission to access a client’s resource; OAuth2 determines whether the resource owner allows the client to access the resource.

Therefore, use CAS for unified authentication across internal systems, and OAuth2 when you need to grant third‑party services access to your resources.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaAuthenticationCASOAuth2SSOSession
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

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.