How Architects Can Achieve Unified Login Across Company Products

The article explains why traditional session mechanisms break in clustered and multi‑service environments, compares session replication and centralized storage, introduces CAS‑based single sign‑on with ticket flow, contrasts it with OAuth2, and provides a complete Spring‑Boot demo with Redis‑backed session handling.

Architect's Guide
Architect's Guide
Architect's Guide
How Architects Can Achieve Unified Login Across Company Products

Introduction

When a company launches many products, users must log in repeatedly to each system, which hurts user experience and raises password‑management costs. Unifying authentication across the product matrix can improve usability and security.

Traditional Session Mechanism and Authentication

HTTP is stateless, so a server creates a new thread for each request and does not retain client context. To associate requests with a user, a Session is created on the server side. Each Session has a unique JSESSIONID stored in a cookie (or, if cookies are disabled, appended to the URL).

When a request arrives, the server checks whether the request contains a JSESSIONID. If found, the corresponding session data is retrieved from an in‑memory hash table; otherwise a new session is created and the ID is sent back to the client.

Server looks up the cookie value (sessionId).

Retrieves the session data from the server‑side store.

If the ID is missing, creates a new session and writes the cookie into the response header.

Session Problems in a Clustered Environment

In a distributed deployment, a load balancer forwards requests to multiple servers. Because the session resides in the memory of a single server, a user may hit server A on the first request (session created) and server B on the next request, where the session cannot be found.

Two common solutions are:

Session replication : copy session data to all nodes whenever it changes.

Centralized session storage : store all sessions in a shared service, typically Redis.

Session replication incurs high implementation cost, maintenance difficulty, and latency. Centralized storage avoids synchronization overhead and is easier to manage; the article recommends using Redis for this purpose.

Multi‑Service Login Challenges and SSO Solution

Enterprises often have many independent systems, each with its own authentication. Users must log in to each system separately, which is cumbersome. Single Sign‑On (SSO) solves this by allowing a user to authenticate once and obtain a ticket that can be exchanged for session information across all systems.

CAS‑Based SSO Flow

System B (e.g., b.com) detects no local login and redirects the user to the central domain ouath.com.

The user logs in at ouath.com, and a cookie is set for that domain.

The login service stores <ticket, sessionId> in Redis.

After login, the user is redirected back to the original system with the ticket as a query parameter.

When the original system receives the request, it looks up the ticket in Redis, retrieves the sessionId, loads the session, sets a cookie for its own domain, and redirects to the original URL.

The system now finds the user logged in and proceeds normally.

The article includes a diagram of the complete interaction.

CAS Demo Code

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 (Spring MVC) :

@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().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;
    }
}

Login filter (checks session, redirects to login if missing):

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) {
            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
}

Configuration registers the filter for all URLs.

CAS vs. OAuth2

OAuth2 is a third‑party authorization protocol that lets a client access resources without the user providing credentials directly. It focuses on protecting the resource server.

CAS (Central Authentication Service) is a Kerberos‑style ticket system for web SSO, ensuring the client’s access to its own resources is authenticated.

In short, use CAS when you need a unified username/password authentication across your own services; use OAuth2 when you need to grant third‑party applications limited access to your resources.

Conclusion

The article walks through the shortcomings of traditional session handling in distributed systems, presents two practical sharing strategies, details a CAS‑based SSO implementation with Redis ticket storage, and clarifies the conceptual difference between CAS and OAuth2, providing a ready‑to‑run Spring Boot demo.

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.

JavaRedisspringAuthenticationCASOAuth2SSOsession
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

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.