Understanding Session-Based Authentication and Single Sign-On (SSO) with CAS and OAuth2
This article explains the challenges of multiple product systems, reviews traditional session mechanisms and their limitations in clustered environments, compares session replication and centralized storage using Redis, and details the design and implementation of Single Sign-On using CAS and OAuth2 with Java Spring code examples.
When a company has many products, users must repeatedly log in to each system, leading to poor experience and increased password management costs; unifying authentication across the product matrix can improve usability and security.
Traditional Session Mechanism
HTTP is stateless, so servers create a new session for each request; the session ID (often JSESSIONID) is stored in a cookie or URL rewrite. The server checks the cookie for a session ID, retrieves the session data from memory, or creates a new session if none exists.
In a clustered environment, a user's request may be routed to different servers, causing session loss because the session is stored locally on each server.
Session Sharing Solutions
Two main approaches are session replication (copying session data to all nodes) and centralized session storage (using a shared store such as Redis). Centralized storage avoids synchronization overhead and is commonly implemented with Redis.
Single Sign-On (SSO) Concept
SSO allows a user to log in once and access multiple systems without re-authenticating, typically by issuing a ticket that links the user’s identity across services.
CAS (Central Authentication Service) Flow
1. User accesses a protected service (b.com) and is redirected to the central login domain (ouath.com). 2. After successful login, a ticket is generated and stored in Redis as {ticket: sessionId}. 3. The user is redirected back to the original service with the ticket. 4. The service retrieves the session ID from Redis using the ticket, creates a local session, and redirects the user to the original URL.
Code Demonstration
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 {
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();
UserForm user = (UserForm) request.getSession().getAttribute(LoginFilter.USER_INFO);
mv.setViewName("index");
mv.addObject("user", user);
return mv;
}
}Login filter to protect resources:
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 url = request.getServletPath();
if (!"/toLogin".equals(url) && !url.startsWith("/login") && userInfo == null) {
request.getRequestDispatcher("/toLogin").forward(request, response);
return;
}
chain.doFilter(request, response);
}
}Spring configuration for the filter:
@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;
}
}Additional SSO filter (SSOFilter) demonstrates ticket validation against Redis and session creation, and the corresponding controller and view templates are provided.
CAS vs OAuth2
CAS is a centralized authentication service that issues tickets for web SSO, focusing on protecting client‑side resources; OAuth2 is an authorization framework that allows third‑party applications to access a resource server on behalf of a user, protecting server‑side resources.
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.
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
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.
