Question

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 &#91;http-thread-pool-8181(4)&#93; &#40;ExceptionTranslationFilter.java&#58;165&#41; - 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 &#91;http-thread-pool-8181(4)&#93; &#40;HttpSessionRequestCache.java&#58;41&#41; - DefaultSavedRequest added to Session: DefaultSavedRequest[https://localhost:8181/springtest/?ticket=ST-44-L0mrrGmf3vNFeGXCRkAj]

DEBUG &#91;http-thread-pool-8181(4)&#93; &#40;ExceptionTranslationFilter.java&#58;185&#41; - Calling Authentication entry point.

DEBUG &#91;http-thread-pool-8181(4)&#93; &#40;HttpSessionSecurityContextRepository.java&#58;300&#41; - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.

DEBUG &#91;http-thread-pool-8181(4)&#93; &#40;SecurityContextPersistenceFilter.java&#58;97&#41; - SecurityContextHolder now cleared, as request processing completed

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;FilterChainProxy.java&#58;337&#41; - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 1 of 9 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;HttpSessionSecurityContextRepository.java&#58;148&#41; - HttpSession returned null object for SPRING_SECURITY_CONTEXT

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;HttpSessionSecurityContextRepository.java&#58;90&#41; - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@50e4c821. A new one will be created.

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;FilterChainProxy.java&#58;337&#41; - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 2 of 9 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;FilterChainProxy.java&#58;337&#41; - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 3 of 9 in additional filter chain; firing Filter: 'CasAuthenticationFilter'

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;CasAuthenticationFilter.java&#58;311&#41; - serviceTicketRequest = false

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;CasAuthenticationFilter.java&#58;362&#41; - proxyReceptorConfigured = false

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;CasAuthenticationFilter.java&#58;349&#41; - proxyReceptorRequest = false

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;CasAuthenticationFilter.java&#58;327&#41; - proxyTicketRequest = false

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;CasAuthenticationFilter.java&#58;262&#41; - requiresAuthentication = false

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;FilterChainProxy.java&#58;337&#41; - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 4 of 9 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;DefaultSavedRequest.java&#58;325&#41; - pathInfo: arg1=/; arg2=/ (property equals)

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;DefaultSavedRequest.java&#58;331&#41; - queryString: arg1=ticket=ST-44-L0mrrGmf3vNFeGXCRkAj; arg2=ticket=ST-45-3m2F3CVknJk6Af2u7d26 (property not equals)

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;HttpSessionRequestCache.java&#58;75&#41; - saved request doesn't match

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;FilterChainProxy.java&#58;337&#41; - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 5 of 9 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;FilterChainProxy.java&#58;337&#41; - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 6 of 9 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;AnonymousAuthenticationFilter.java&#58;102&#41; - 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 &#91;http-thread-pool-8181(1)&#93; &#40;FilterChainProxy.java&#58;337&#41; - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 7 of 9 in additional filter chain; firing Filter: 'SessionManagementFilter'

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;FilterChainProxy.java&#58;337&#41; - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 8 of 9 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;FilterChainProxy.java&#58;337&#41; - /?ticket=ST-45-3m2F3CVknJk6Af2u7d26 at position 9 of 9 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;AbstractSecurityInterceptor.java&#58;194&#41; - Secure object: FilterInvocation: URL: /?ticket=ST-45-3m2F3CVknJk6Af2u7d26; Attributes: [ROLE_USER]

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;AbstractSecurityInterceptor.java&#58;310&#41; - 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 &#91;http-thread-pool-8181(1)&#93; &#40;AffirmativeBased.java&#58;65&#41; - Voter: org.springframework.security.access.vote.RoleVoter@65b46ab9, returned: -1

DEBUG &#91;http-thread-pool-8181(1)&#93; &#40;AffirmativeBased.java&#58;65&#41; - 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?

Was it helpful?

Solution 2

It looks like your serviceProperties are not defined correctly. Specifically the service must be a URL that is monitored by the CasAuthenticationFilter. Otherwise, CasAuthenticationFilter ignores the request and then Spring Security requires authentication for the URL and re-requests the ST.

By default CasAuthenticationFilter processes requests to /j_spring_cas_security_check. So you probably want something like this:

<b:bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">
    <b:property name="service" value="https://localhost:8181/springtest/j_spring_cas_security_check" />
    <b:property name="sendRenew" value="false" />
</b:bean>

OTHER TIPS

Just a heads-up that in more recent versions the default URL is now /login/cas instead of /j_spring_cas_security_check (https://jira.spring.io/browse/SEC-3053)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top