Cas and Spring Security – Client

Continuing from my previous post, I’ll show the client integration to a CAS Authentication service using a little example. It will show how calling the authentication and how verify user credentials once authenticated.

Once configured and run the CAS Server, it’s now time to take a look at the client code. I want to protect a photo albums from the free access by using CAS supported authentication.

I used Spring Security Framework to integrate the CAS client; that’s because the configuration is quite easy due the good Spring support to the CAS authentication. The security layer is defined as the follow code


<http use-expressions="true" entry-point-ref="casEntryPoint">
    <intercept-url pattern="/"
            access="permitAll"/>

    <intercept-url pattern="/403"
            access="permitAll"/>

    <intercept-url pattern="/protected/parents"
            access="hasRole('ROLE_PARENTS')"/>

    <intercept-url pattern="/protected/friends"
            access="hasRole('ROLE_FRIENDS')"/>

    <intercept-url pattern="/images/**"
            access="hasAnyRole('ROLE_FRIENDS','ROLE_PARENTS')"/>

    <access-denied-handler error-page="/403"/>

    <custom-filter ref="singleLogoutFilter" before="LOGOUT_FILTER"/>
    <custom-filter ref="casFilter" position="CAS_FILTER"/>

    <logout logout-url="/logout"
            logout-success-url="https://${cas.server.host}/chapter09.06-cas-server/logout?
service=http%3A%2F%2F${cas.client.host}%2Fspringcasphotoalbum%2F"/>
</http>
<authentication-manager alias="authenticationManager">
    <authentication-provider ref="casAuthProvider" />
</authentication-manager>

I got two main url to protected; one allowed the access to the parent (role ROLE_PARENTS) and the other let the access to the friends (role ROLE_FRIENDS). The highlights code tell Spring Security to use the CAS authentication system. The beans are defined here (I omitted the namespace definition):


<bean id="serviceProperties"
        class="org.springframework.security.cas.ServiceProperties">
    <property name="service"
             value="https://${cas.client.sslhost}/springcasphotoalbum/login"/>
</bean>

<context:property-placeholder location="classpath:service.properties"/>

<!-- sends to the CAS Server, must be in entry-point-ref of security.xml -->
<bean id="casEntryPoint"
    class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
    <property name="serviceProperties" ref="serviceProperties"/>
    <property name="loginUrl" value="https://${cas.server.host}/chapter09.06-cas-server/login" />
</bean>

<!-- authenticates CAS tickets, must be in custom-filter of security.xml -->
<bean id="casFilter"
    class="org.springframework.security.cas.web.CasAuthenticationFilter">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="filterProcessesUrl" value="/login"/>
</bean>

<bean id="casAuthProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
    <property name="ticketValidator" ref="ticketValidator"/>
    <property name="serviceProperties" ref="serviceProperties"/>
    <property name="key" value="casPhotoalbum"/>
    <property name="authenticationUserDetailsService" ref="authenticationUserDetailsService"/>
    <property name="statelessTicketCache" ref="statelessTicketCache"/>
</bean>
<bean id="statelessTicketCache" class="org.springframework.security.cas.authentication.EhCacheBasedTicketCache">
    <property name="cache">
        <bean class="net.sf.ehcache.Cache"
                init-method="initialise" destroy-method="dispose">
            <constructor-arg value="casTickets"/>
            <constructor-arg value="50"/>
            <constructor-arg value="true"/>
            <constructor-arg value="false"/>
            <constructor-arg value="3600"/>
            <constructor-arg value="900"/>
        </bean>
    </property>
</bean>

<bean id="ticketValidator" class="org.jasig.cas.client.validation.Saml11TicketValidator">
    <constructor-arg value="https://${cas.server.host}/chapter09.06-cas-server" />
</bean>
<bean id="authenticationUserDetailsService"
class="org.springframework.security.cas.userdetails.GrantedAuthorityFromAssertionAttributesUserDetailsService">
    <constructor-arg>
        <array>
            <value>role</value>
        </array>
    </constructor-arg>
</bean>

<!-- Handles a Single Logout Request from the CAS Server must be in custom-filter of security.xml -->
<bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/>

The key points of this configuration are:

  • casEntryPoint: Defines the authentication type and the login Url.
  • casFilter: validates the CAS tickets.
  • casAuthProvider: Authentication provider of type CAS.

The service.properties file lets me introduce the dialog between the client and the server:


cas.server.host=localhost:8444
cas.client.sslhost=localhost:8443
cas.client.host=localhost:8080

The client will be available by http protocol at the port 8080 and ssl connection at the port 8443. On the other side, server will be available by ssl connection at the port 8444. CAS server will never be contacted on http port.

Obviously, the communication between the client and server will go through a secure connection; so, when will the client be contacted at no-secure connection? It’ll happen only at redirect of logout service, when the user will be redirect to the home page.

In the web.xml I defined the Spring Security section and Spring Mvc definitions.


<display-name>Spring Cas PhotoAlbum</display-name>

<!-- Spring MVC -->
<servlet>
	<servlet-name>mvc-dispatcher-controller</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>mvc-dispatcher-controller</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>

<listener>
	<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>
          /WEB-INF/security.xml
          /WEB-INF/security-cas.xml
      </param-value>
</context-param>

<!-- Spring Security -->
<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

So, Spring Mvc definition


<context:component-scan base-package="it.iol.springcasphotoalbum.*" />

<bean
	class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<property name="prefix">
		<value>/WEB-INF/pages/</value>
	</property>
	<property name="suffix">
		<value>.jsp</value>
	</property>
</bean>

<mvc:resources mapping="/images/**" location="/images/" />
<mvc:annotation-driven/> 

And the related codes


@Controller
public class MvcController {

@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView homePage() {

ModelAndView model = new ModelAndView();
model.addObject("user", getUserName());

model.setViewName("home");
return model;

}

@RequestMapping(value = "/protected/{album}", method = RequestMethod.GET)
public ModelAndView protectedPage(@PathVariable String album) {

ModelAndView model = new ModelAndView();
model.addObject("user", getUserName());

switch (album) {
case "parents":
model.addObject("photos", Album.ride);
break;
case "friends":
model.addObject("photos", Album.friends);
break;
}

model.setViewName("protected");
return model;

}

@RequestMapping(value = "/403", method = RequestMethod.GET)
public ModelAndView accesssDenied(Principal user) {

ModelAndView model = new ModelAndView();

if (user != null) {
model.addObject("user", getUserName());
} else {
model.addObject("user", "");
}

model.setViewName("403");
return model;

}

protected String getUserName() {
Object principal = SecurityContextHolder.getContext()
.getAuthentication().getPrincipal();
String username = "";

if (principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
} else {
username = principal.toString();
}

return username;
}

}

Albums List


public class Album {
	public static ArrayList<String> ride = new ArrayList<String>() {{
	    add("../images/parents/image1.jpg");
	    add("../images/parents/image2.jpg");
	    add("../images/parents/image3.jpg");
	}};

	public static ArrayList<String> friends = new ArrayList<String>() {{
	    add("../images/friends/image1.jpg");
	    add("../images/friends/image2.jpg");
	    add("../images/friends/image3.jpg");
	}};
}

Now the protected.jsp file (using JSF tags)

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page session="true"%>
<html>
<head>
<title>Spring Cas PhotoAlbum - Home</title>
</head>
<body>
	<h1>
		<span style="font-family: arial, helvetica, sans-serif"><span
			style="font-size: 10px">Username : ${user}&nbsp;|&nbsp;
<a href="../logout">Logout</a></span></span>
	</h1>
	<span style="font-size: 10px"><span
		style="font-family: arial, helvetica, sans-serif"> <a
			href="../">Home</a></span></span>
	<p>
		<c:forEach items="${photos}" var="photo">
			<img src="${photo}">
		</c:forEach>
	</p>
</body>

</html>

I think I’ve written every single codes called. Now, configure the client for accepting the SSL connection and deploy the Web App.

Browsing to http://localhost:8080/springcasphotoalbum/ I’ll see this screen.

cas-hp

 

Now, let’s try to click “My first bicycle ride” and I’ll see the CAS Login Page. Put inside the Mom credentials and I’ll redirect to the pictures page.

cas-photoalbum

 

Take a look at what happened at CAS server log page; once sent the credentials, four steps are executed at the authentication level.


=============================================================
WHO: [username: mom@album.com]
WHAT: supplied credentials: [username: mom@album.com]
ACTION: AUTHENTICATION_SUCCESS
APPLICATION: CAS
WHEN: Thu Jun 12 15:45:14 CEST 2014
CLIENT IP ADDRESS: 127.0.0.1
SERVER IP ADDRESS: 127.0.0.1
=============================================================

>
2014-06-12 15:45:14,904 INFO [com.github.inspektr.audit.support.Slf4jLoggingAudi
tTrailManager] - <Audit trail record BEGIN
=============================================================
WHO: [username: mom@album.com]
WHAT: TGT-1-AVioqXaQ7XqFwIXofEgTl6XG5E56bi6ZSResRoEsybYOcrsHCZ-chapter09.06-cas-
server
ACTION: TICKET_GRANTING_TICKET_CREATED
APPLICATION: CAS
WHEN: Thu Jun 12 15:45:14 CEST 2014
CLIENT IP ADDRESS: 127.0.0.1
SERVER IP ADDRESS: 127.0.0.1
=============================================================

>
2014-06-12 15:45:14,919 INFO [org.jasig.cas.CentralAuthenticationServiceImpl] -
<Granted service ticket [ST-1-drf6w2Bnbc5ZUCctEPr5-chapter09.06-cas-server] for
service [https://localhost:8443/springcasphotoalbum/login] for user [mom@album.c
om]>
2014-06-12 15:45:14,919 INFO [com.github.inspektr.audit.support.Slf4jLoggingAudi
tTrailManager] - <Audit trail record BEGIN
=============================================================
WHO: mom@album.com
WHAT: ST-1-drf6w2Bnbc5ZUCctEPr5-chapter09.06-cas-server for https://localhost:84
43/springcasphotoalbum/login
ACTION: SERVICE_TICKET_CREATED
APPLICATION: CAS
WHEN: Thu Jun 12 15:45:14 CEST 2014
CLIENT IP ADDRESS: 127.0.0.1
SERVER IP ADDRESS: 127.0.0.1
=============================================================

>
2014-06-12 15:45:15,154 INFO [com.github.inspektr.audit.support.Slf4jLoggingAudi
tTrailManager] - <Audit trail record BEGIN
=============================================================
WHO: audit:unknown
WHAT: ST-1-drf6w2Bnbc5ZUCctEPr5-chapter09.06-cas-server
ACTION: SERVICE_TICKET_VALIDATED
APPLICATION: CAS
WHEN: Thu Jun 12 15:45:15 CEST 2014
CLIENT IP ADDRESS: 127.0.0.1
SERVER IP ADDRESS: 127.0.0.1
=============================================================

My application goes through two authentication steps. The first created the “Ticket Granting” (look at the “What” equals at “TG-1”) and the second step is created the “Service Ticket” (look at the “What” equals at “ST-1”).

Now, I try to access at the “Ibiza Party” photo album. The result is the follows:

cas-accessdenied

 

That’s because I’m not allowed due at the role “ROLE_FRIENDS” that’s not the mom’s role.

Summary

I think that Cas integration is very powerful especially when you must  integrate a multiple system under an unique system authentication for building Single-Sign-On system.

Spring, as usual, provides a very good integration. In few configurations rows you’ve a “ready to go” authentication system. It’s not a bad thing.

 

Advertisements

4 thoughts on “Cas and Spring Security – Client

  1. Hi Marco, i have configured cas ssso in my application. Now i want to access a service which is under same sso and use the same configuration as above. My RestTemplate client fails with an HTTP status 302. Any clue?

    • Hi Salvan.
      I think you should check the role permission configured in the intercept-url section.
      Let’s try to give the “permitAll” permission and browse it. Sometimes, that helps me to get the issue.
      HTTP 302 is a redirect action, you could debug the class “CasAuthenticationProvider” to understand whether the problem is in authentication or in authorization process.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s