Can Multiple Company Systems Share a Single Account? A Deep Dive into Session Sharing and SSO with CAS
This article examines the challenges of managing user authentication across many corporate systems, explains traditional session mechanisms, explores session sharing solutions for clustered environments, and provides a detailed walkthrough of implementing single sign‑on using CAS with Java and Redis, including code samples and a comparison with OAuth2.
Background
Multiple independent systems require users to log in repeatedly, leading to poor user experience, higher password‑management costs, and reduced overall security. A unified authentication mechanism is needed.
Traditional session mechanism and authentication
Cookie and server interaction
HTTP is stateless; a Session is created per user. The server generates a JSESSIONID cookie, stores session data in memory, and returns the cookie to the client. If cookies are disabled, the session ID can be passed via URL rewriting.
Server‑side session process
The server checks whether the request contains a JSESSIONID. If present, the corresponding session is retrieved from memory.
If the request lacks a session ID, the server creates a new session, generates a session ID, and sends it back in the response.
Session‑based authentication flow
Client request → server creates or retrieves a session → user information is stored in the session → subsequent requests use the session ID. This works for single‑node deployments.
Session challenges in clustered environments and solutions
Load balancers distribute requests across multiple servers. Because sessions are stored locally, a user may first hit server A (creating a session) and later hit server B, where the session cannot be found.
Session replication
Session data is copied to all servers whenever it changes. This ensures consistency but incurs high implementation cost, maintenance difficulty, and potential latency.
Centralized session storage
All servers read and write session data to a single external store, typically Redis. This eliminates synchronization overhead and avoids multiple session copies.
Single Sign‑On (SSO) with CAS
CAS fundamentals
CAS is a ticket‑based SSO framework. When a protected service (e.g., b.com) receives a request without a session, it redirects the user to an authentication domain (e.g., ouath.com). After successful login, CAS issues a ticket, stores <ticket, sessionId> in Redis, and redirects back to the original service. The service validates the ticket, retrieves the session ID from Redis, creates a local session, and grants access.
CAS vs OAuth2
OAuth2 is a third‑party authorization protocol that allows a client to access resources on behalf of a user without exposing the user’s credentials. CAS focuses on authenticating the user for web applications (Web SSO). CAS protects client‑side resources, while OAuth2 protects server‑side resources.
Implementation demo (Java Spring)
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
}Login controller (session‑based)
@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);
}
}
}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);
}
}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;
}
}SSO filter (CAS ticket validation)
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());
return;
}
request.getSession().setAttribute(USER_INFO, userInfo);
redisTemplate.delete(ticket);
}
chain.doFilter(request, response);
}
}SSO controller
@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);
request.getSession().setAttribute("test", "123");
return mv;
}
}Conclusion
Traditional in‑memory sessions do not scale across clustered deployments. Centralized session storage (e.g., Redis) combined with a CAS‑based SSO architecture provides a practical, maintainable approach to unified authentication. Understanding the functional differences between CAS and OAuth2 helps select the appropriate protocol for a given security requirement.
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 Architect Handbook
Focused on Java interview questions and practical article sharing, covering algorithms, databases, Spring Boot, microservices, high concurrency, JVM, Docker containers, and ELK-related knowledge. Looking forward to progressing together with you.
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.
