Single sign-on in CAS client setup with spring security

Single sign-on

In previous blog, We learned how to use CAS server with spring boot. Now will see how to configure multiple application(Spring boot client app) with CAS server.

Let us create spring project using by Spring Initializr. Then add below maven cas module to your spring project in pom.xml.

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-cas</artifactId>
</dependency>

This step is very important because without server and client configuration it won’t communicate. Let’s register client information in CAS server, We can configure multiple ways(JSON,YAML,JPA).In this blog will see JSON service configuration method.

Create JSON file in cas-server/src/main/resources folder.Add below code in json file.

{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^http://localhost:9000/login/cas",
"name" : "CAS Spring Secured App",
"description": "This is a Spring App that usses the CAS Server for it's authentication",
"id" : 19990,
"evaluationOrder" : 1
}

You can see all description about each attribute information here.Then add two more line for where JSON file is located in server and initialise registry from JSON file.Both these configuration items are placed in another file, named cas.properties. We create this file in the cas-server/src/main/resources directory:

cas.serviceRegistry.initFromJson=true
cas.serviceRegistry.config.location=classpath:/services

Now the time to change spring security configuration for CAS authentication in CAS client app. Spring bean are important role to change configuration so create configuration class called cassecuredapp.java. Which will act as entry point of CAS client application.

@Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService(“http://localhost:9000/login/cas");
serviceProperties.setSendRenew(false);
return serviceProperties;
}

@Bean
@Primary
public AuthenticationEntryPoint authenticationEntryPoint(
ServiceProperties sP) {

CasAuthenticationEntryPoint entryPoint
= new CasAuthenticationEntryPoint();
entryPoint.setLoginUrl(“https://localhost:6443/cas/login");
entryPoint.setServiceProperties(sP);
return entryPoint;
}

@Bean
public TicketValidator ticketValidator() {
return new Cas30ServiceTicketValidator(
https://localhost:6443/cas");
}

@Bean
public CasAuthenticationProvider casAuthenticationProvider() {

CasAuthenticationProvider provider = new CasAuthenticationProvider();
provider.setServiceProperties(serviceProperties());
provider.setTicketValidator(ticketValidator());
provider.setUserDetailsService(
s -> new User(“username”, “password”, true, true, true, true,
AuthorityUtils.createAuthorityList(“ROLE_ADMIN”)));
provider.setKey(“CAS_PROVIDER_LOCALHOST_9000”);
return provider;
}

Flow of the application

  1. A user attempts to access a secured page
  2. The AuthenticationEntryPoint is triggered and takes the user to the server. The login address of the server has been specified in the AuthenticationEntryPoint
  3. On a successful authentication with the server, it redirects the request back to the service URL that has been specified, with the service ticket appended as a query parameter
  4. CasAuthenticationFilter is mapped to a URL that matches the pattern and in turn, triggers the ticket validation internally.
  5. If the ticket is valid, a user will be redirected to the originally requested URL.

Let’s create SecurityConfig.java that extends WebSecurityConfigurerAdapter and override the config()

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.regexMatchers("/cassecured.*", "/login")
.authenticated()
.and()
.authorizeRequests()
.regexMatchers("/")
.permitAll()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint);
}

we override the following methods and create the CasAuthenticationFilter bean at the same time

@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(authenticationProvider);
}
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return new ProviderManager(
Arrays.asList(authenticationProvider));
}
@Bean
public CasAuthenticationFilter casAuthenticationFilter(ServiceProperties sP)
throws Exception {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setServiceProperties(sP);
filter.setAuthenticationManager(authenticationManager());
return filter;
}

Step 3: Configuring Single Logout

Let’s start by defining some bean configurations in CasSecuredAppApplicaiton class:

@Bean
public SecurityContextLogoutHandler securityContextLogoutHandler() {
return new SecurityContextLogoutHandler();
}
@Bean
public LogoutFilter logoutFilter() {
LogoutFilter logoutFilter = new LogoutFilter(
"https://localhost:6443/cas/logout",
securityContextLogoutHandler());
logoutFilter.setFilterProcessesUrl("/logout/cas");
return logoutFilter;
}
@Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setCasServerUrlPrefix("https://localhost:6443/cas");
singleSignOutFilter.setIgnoreInitConfiguration(true);
return singleSignOutFilter;
}
@EventListener
public SingleSignOutHttpSessionListener
singleSignOutHttpSessionListener(HttpSessionEvent event) {
return new SingleSignOutHttpSessionListener();
}

Let’s modify the HttpSecurity configuration in the config() of SecurityConfig class. The CasAuthenticationFilter and LogoutFilter that were configured earlier are now added to the chain as well

http
.authorizeRequests()
.regexMatchers("/secured.*", "/login")
.authenticated()
.and()
.authorizeRequests()
.regexMatchers("/")
.permitAll()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.logout().logoutSuccessUrl("/logout")
.and()
.addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class)
.addFilterBefore(logoutFilter, LogoutFilter.class);

The link is the same as the one set as the filter process URL of the LogoutFilter we configured above:

@GetMapping("/logout")
public String logout(
HttpServletRequest request,
HttpServletResponse response,
SecurityContextLogoutHandler logoutHandler) {
Authentication auth = SecurityContextHolder
.getContext().getAuthentication();
logoutHandler.logout(request, response, auth );
new CookieClearingLogoutHandler(
AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY)
.logout(request, response, auth);
return "auth/logout";
}

If you want more detail about logout check it here.

Start the CAS Server once again with build run, go to https://localhost:6443/cas you can see CAS server login page.

If you enjoyed this article, feel free to hit that clap button 👏 to help others find it.

I have a passion for understanding technology at a fundamental level and Sharing ideas and code. * Aspire to Inspire before I expire* https://balavenkatesh.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store