How GrowingIO Unified OAuth2, LDAP, and CAS for Seamless SSO Integration
GrowingIO’s server-side solution integrates three distinct authentication protocols—OAuth2, LDAP, and CAS—into a unified SSO flow, detailing each protocol’s process, the overall architecture, role of the IAM and Gateway components, and providing configuration and code examples for implementation.
Background
GrowingIO, a professional data operation solution provider, serves customers from various industries who share common security requirements. Many customers have their own account authentication systems, so GrowingIO needed a simple way to integrate these systems. Currently GrowingIO supports three protocols: CAS, OAuth2, and LDAP.
Authentication Protocols Overview
OAuth2
OAuth2 is typically implemented using the authorization code grant type. The standard flow includes:
User accesses the client, which redirects the user to the authorization server.
User decides whether to grant the client access.
If granted, the authorization server redirects the user to the client’s registered redirect URI with an authorization code.
The client exchanges the authorization code for an access token (and refresh token) on its backend.
The authorization server returns the tokens to the client.
LDAP
LDAP (Lightweight Directory Access Protocol) is mainly used for authentication. The LDAP authentication flow is illustrated below.
CAS
CAS is an open‑source Java server component that provides web single sign‑on. The CAS server, built on Spring Framework, issues and validates tickets to authenticate users and grant access to CAS‑enabled services. After a successful login, a Ticket‑Granting Ticket (TGT) creates an SSO session, and Service Tickets (ST) are issued per service request.
Overall Architecture
Overall Idea
The three authentication methods are integrated into the OAuth2 authorization code flow. The IAM system plays different roles depending on the protocol:
For username/password authentication, IAM acts as the OAuth Server, storing user data in the
userstable, while the Gateway acts as the OAuth Client.
For LDAP, IAM acts as an LDAP client, forwarding credentials to the LDAP server before proceeding with the OAuth2 flow.
For CAS, IAM acts as a CAS client; it redirects unauthenticated users to the CAS server, exchanges the ticket for user information, then continues with the standard OAuth2 flow.
For OAuth2, IAM also acts as an OAuth client, redirecting users to the OAuth server for authentication and handling the token exchange.
Login Flow
Combined login flows for “Username/Password & LDAP” and “CAS & OAuth2” are illustrated below.
Key Steps Pseudocode
Gateway
The Gateway functions as a reverse proxy and part of the OAuth client, handling token requests and refreshes.
<code># Gateway as OAuth client, front‑end login first calls /authorize
# Gateway assembles OAuth client parameters
location /authorize {
local authorize_uri = '/oauth/authorize?client_id=gateway&response_type=code&redirect_uri=xxx/oauth/callback'
redirect to authorize_uri
}
# Standard OAuth2 authorization code endpoint, third‑party system calls with its parameters
location /oauth/authorize {
proxy IAM
}
# Endpoint to receive authentication code (token/code) when integrating CAS or OAuth
location /sso/callback {
proxy IAM
}
# Gateway processes the authorization code callback, calls IAM to obtain token
location /oauth/callback {
local redirect_uri = 'xxx/oauth/callback'
local auth_info = {
grant_type = 'authorization_code',
code = args.code,
client_id = oauth2.client_id,
client_secret = oauth2.client_secret,
redirect_uri = redirect_uri
}
# Retrieve token using client_id and code
local token = getTokrnByCode(auth_info)
# Return token to front‑end
}
# Endpoint for third‑party systems to exchange authorization code for token
location /oauth/token {
proxy IAM
}</code>IAM
IAM uses
spring-security-oauth2to build an OAuth server and depends on
spring-security-ldapand
spring-security-casfor LDAP and CAS integration.
Framework Dependencies
<code>implementation("org.springframework.security.oauth:spring-security-oauth2:2.3.6.RELEASE")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
implementation("org.springframework.security:spring-security-ldap")
implementation("org.springframework.security:spring-security-cas")</code>SSO Integration Configuration
<code>grant:
type: LDAP or ORIGIN or CAS or OAUTH
cas:
serverUrl: https://xxx:8443/cas
ldap:
type: ad or openLdap
domain: xxx.com
url: ldap://xxx:389
rootDn: dc=xxx,dc=com</code>Authentication Method Configuration
<code>@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Value("${grant.type}")
private GrantType grantType;
@Bean
public AuthenticationProvider defaultDaoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
// Create different authentication providers based on configuration
public AuthenticationProvider grantTypeAuthenticationProvider() {
AuthenticationProvider provider = null;
switch (configs.getGrantType()) {
case LDAP:
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(configs);
ldapProvider.setUserDetailsContextMapper(userDetailsService);
provider = ldapProvider;
break;
case OAUTH:
DefaultAuthorizationCodeTokenResponseClient tokenClient = new DefaultAuthorizationCodeTokenResponseClient();
DefaultOAuth2UserService oAuth2UserService = new DefaultOAuth2UserService();
provider = new OAuth2LoginAuthenticationProvider(tokenClient, oAuth2UserService);
break;
default:
break;
}
return provider;
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() {
List<AuthenticationProvider> providers = new ArrayList<>();
providers.add(defaultDaoAuthenticationProvider());
AuthenticationProvider grantProvider = grantTypeAuthenticationProvider();
if (Objects.nonNull(grantProvider)) {
providers.add(grantProvider);
}
return new ProviderManager(providers);
}
// CAS ticket validator
@Bean
@ConditionalOnProperty(prefix = "grant", value = "type", havingValue = "CAS")
public TicketValidator validator() {
return new Cas30ServiceTicketValidator(casServer);
}
}</code>Callback Interface Logic
<code>@GetMapping(value = "/sso/callback")
public void casCallBack(@RequestParam Map<String, String> parameters, HttpServletResponse response) throws IOException {
String username;
switch (configs.getGrantType()) {
case CAS:
String ticket = parameters.get("ticket");
// Retrieve username from CAS ticket
username = xxx;
break;
case OAUTH:
String code = parameters.get("code");
// Retrieve token and username from OAuth code
username = xxx;
break;
default:
username = null;
}
// Authenticate in IAM
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, "");
Authentication authentication = authenticationManager.authenticate(authRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Generate OAuth code and redirect back to gateway
AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(parameters);
OAuth2Request storedOAuth2Request = oAuth2RequestFactory.createOAuth2Request(authorizationRequest);
OAuth2Authentication combinedAuth = new OAuth2Authentication(storedOAuth2Request, authentication);
String code = authorizationCodeServices.createAuthorizationCode(combinedAuth);
String redirectUrl = String.format("xxx/oauth/callback?code=%s", code);
redirectUrl = response.encodeRedirectURL(redirectUrl);
response.sendRedirect(redirectUrl);
}</code>References
https://datatracker.ietf.org/doc/html/rfc6749
https://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
https://www.apereo.org/projects/cas
GrowingIO Tech Team
The official technical account of GrowingIO, showcasing our tech innovations, experience summaries, and cutting‑edge black‑tech.
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.