I've set up a CAS 3.5.2 server on GlassFish 3.1.2.2 and now I am trying to protect a Jersey REST web service with CAS using Spring Security 3.2.0 by following the official documentation. My configuration:
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>springtest</display-name>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/log4j.properties</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<!-- - Location of the XML file that defines the root application context
- Applied by ContextLoaderListener. -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext-security.xml
</param-value>
</context-param>
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>cas.root</param-value>
</context-param>
<!-- Include the character encoding Filter as per JASIG recommenation when
doing Single Sign Out https://wiki.jasig.org/display/CASC/Configuring+Single+Sign+Out -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Included to support Single Logout. Note that the SingleSignOutFilter
is included in the springSecurityFilterChain. However, it could also be placed
as the first filter-mapping in the web.xml -->
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!-- - Loads the root application context of this web app at startup. -
The application context is then available via - WebApplicationContextUtils.getWebApplicationContext(servletContext). -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Jersey Servlet config -->
<servlet>
<description>JAX-RS Tools Generated - Do not modify</description>
<servlet-name>JAX-RS Servlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>JAX-RS Servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
applicationContext-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns="http://www.springframework.org/schema/security" xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- This section is used to configure CAS. The service is the actual redirect
that will be triggered after the CAS login sequence. -->
<b:bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">
<b:property name="service" value="https://localhost:8181/springtest/" />
<b:property name="sendRenew" value="false" />
</b:bean>
<!-- this is what hooks up the CAS entry point -->
<b:bean id="exceptionTranslationFilter"
class="org.springframework.security.web.access.ExceptionTranslationFilter">
<b:property name="authenticationEntryPoint">
<b:ref local="casEntryPoint"client />
</b:property>
</b:bean>
<!-- Enable security, let the casAuthenticationEntryPoint handle all intercepted
urls. The CAS_FILTER needs to be in the right position within the filter
chain. -->
<http entry-point-ref="casEntryPoint">
<intercept-url pattern="/**" access="ROLE_USER" />
<custom-filter position="CAS_FILTER" ref="casFilter" />
</http>
<!-- The CAS filter handles the redirect from the CAS server and starts
the ticket validation. -->
<b:bean id="casFilter"
class="org.springframework.security.cas.web.CasAuthenticationFilter">
<b:property name="authenticationManager" ref="authenticationManager" />
</b:bean>
<!-- The entryPoint intercepts all the CAS authentication requests. It redirects
to the CAS loginUrl for the CAS login page. -->
<b:bean id="casEntryPoint"
class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
<b:property name="loginUrl" value="https://192.168.10.144/cas/login" />
<b:property name="serviceProperties" ref="serviceProperties" />
</b:bean>
<!-- Required for the casProcessingFilter, so define it explicitly set and
specify an Id Even though the authenticationManager is created by default
when namespace based config is used. -->
<authentication-manager alias="authenticationManager">
<authentication-provider ref="casAuthenticationProvider" />
</authentication-manager>
<!-- Handles the CAS ticket processing. -->
<b:bean id="casAuthenticationProvider"
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
<b:property name="authenticationUserDetailsService">
<b:bean
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<b:constructor-arg ref="userService" />
</b:bean>
</b:property>
<b:property name="serviceProperties" ref="serviceProperties" />
<b:property name="ticketValidator">
<b:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
<b:constructor-arg index="0"
value="https://192.168.10.144/cas" />
</b:bean>
</b:property>
<b:property name="key" value="myCAS" />
</b:bean>
<!-- The users available for this application. -->
<user-service id="userService">
<user name="joe" password="joe" authorities="ROLE_USER" />
</user-service>
</b:beans>
I've made sure that the service trusts the CAS server's certificate, don't know if the opposite direction is necessary though. The following messages are looped over and over again, until the browser 'gets bored of it':
log4j DEBUG messages
DEBUG [http-thread-pool-8181(4)] (ExceptionTranslationFilter.java:165) - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83)
...
DEBUG [http-thread-pool-8181(4)] (HttpSessionRequestCache.java:41) - DefaultSavedRequest added to Session: DefaultSavedRequest[https://localhost:8181/springtest/?ticket=ST-44-L0mrrGmf3vNFeGXCRkAj]
DEBUG [http-thread-pool-8181(4)] (ExceptionTranslationFilter.java:185) - Calling Authentication entry point.
DEBUG [http-thread-pool-8181(4)] (HttpSessionSecurityContextRepository.java:300) - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
DEBUG [http-thread-pool-8181(4)] (SecurityContextPersistenceFilter.java:97) - SecurityContextHolder now cleared, as request processing completed
DEBUG [http-thread-pool-8181(1)] (FilterChainProxy.java:337) - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 1 of 9 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
DEBUG [http-thread-pool-8181(1)] (HttpSessionSecurityContextRepository.java:148) - HttpSession returned null object for SPRING_SECURITY_CONTEXT
DEBUG [http-thread-pool-8181(1)] (HttpSessionSecurityContextRepository.java:90) - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@50e4c821. A new one will be created.
DEBUG [http-thread-pool-8181(1)] (FilterChainProxy.java:337) - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 2 of 9 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
DEBUG [http-thread-pool-8181(1)] (FilterChainProxy.java:337) - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 3 of 9 in additional filter chain; firing Filter: 'CasAuthenticationFilter'
DEBUG [http-thread-pool-8181(1)] (CasAuthenticationFilter.java:311) - serviceTicketRequest = false
DEBUG [http-thread-pool-8181(1)] (CasAuthenticationFilter.java:362) - proxyReceptorConfigured = false
DEBUG [http-thread-pool-8181(1)] (CasAuthenticationFilter.java:349) - proxyReceptorRequest = false
DEBUG [http-thread-pool-8181(1)] (CasAuthenticationFilter.java:327) - proxyTicketRequest = false
DEBUG [http-thread-pool-8181(1)] (CasAuthenticationFilter.java:262) - requiresAuthentication = false
DEBUG [http-thread-pool-8181(1)] (FilterChainProxy.java:337) - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 4 of 9 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
DEBUG [http-thread-pool-8181(1)] (DefaultSavedRequest.java:325) - pathInfo: arg1=/; arg2=/ (property equals)
DEBUG [http-thread-pool-8181(1)] (DefaultSavedRequest.java:331) - queryString: arg1=ticket=ST-44-L0mrrGmf3vNFeGXCRkAj; arg2=ticket=ST-45-3m2F3CVknJk6Af2u7d26 (property not equals)
DEBUG [http-thread-pool-8181(1)] (HttpSessionRequestCache.java:75) - saved request doesn't match
DEBUG [http-thread-pool-8181(1)] (FilterChainProxy.java:337) - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 5 of 9 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
DEBUG [http-thread-pool-8181(1)] (FilterChainProxy.java:337) - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 6 of 9 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
DEBUG [http-thread-pool-8181(1)] (AnonymousAuthenticationFilter.java:102) - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@6faa1b5a: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffff6a82: RemoteIpAddress: 127.0.0.1; SessionId: 3e9339134a98fa96a8dd34676e8f; Granted Authorities: ROLE_ANONYMOUS'
DEBUG [http-thread-pool-8181(1)] (FilterChainProxy.java:337) - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 7 of 9 in additional filter chain; firing Filter: 'SessionManagementFilter'
DEBUG [http-thread-pool-8181(1)] (FilterChainProxy.java:337) - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 8 of 9 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
DEBUG [http-thread-pool-8181(1)] (FilterChainProxy.java:337) - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 9 of 9 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
DEBUG [http-thread-pool-8181(1)] (AbstractSecurityInterceptor.java:194) - Secure object: FilterInvocation: URL: /?ticket=ST-45-3m2F3CVknJk6Af2u7d26; Attributes: [ROLE_USER]
DEBUG [http-thread-pool-8181(1)] (AbstractSecurityInterceptor.java:310) - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@6faa1b5a: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffff6a82: RemoteIpAddress: 127.0.0.1; SessionId: 3e9339134a98fa96a8dd34676e8f; Granted Authorities: ROLE_ANONYMOUS
DEBUG [http-thread-pool-8181(1)] (AffirmativeBased.java:65) - Voter: org.springframework.security.access.vote.RoleVoter@65b46ab9, returned: -1
DEBUG [http-thread-pool-8181(1)] (AffirmativeBased.java:65) - Voter: org.springframework.security.access.vote.AuthenticatedVoter@27cacbd9, returned: 0
Seems as the CASFilter just does not realize that a valid service ticket is provided. Did I misconfigure something?