|
@@ -0,0 +1,463 @@
|
|
|
+[[servlet-cas]]
|
|
|
+= CAS Authentication
|
|
|
+
|
|
|
+[[cas-overview]]
|
|
|
+== Overview
|
|
|
+JA-SIG produces an enterprise-wide single sign on system known as CAS.
|
|
|
+Unlike other initiatives, JA-SIG's Central Authentication Service is open source, widely used, simple to understand, platform independent, and supports proxy capabilities.
|
|
|
+Spring Security fully supports CAS, and provides an easy migration path from single-application deployments of Spring Security through to multiple-application deployments secured by an enterprise-wide CAS server.
|
|
|
+
|
|
|
+You can learn more about CAS at https://www.apereo.org.
|
|
|
+You will also need to visit this site to download the CAS Server files.
|
|
|
+
|
|
|
+[[cas-how-it-works]]
|
|
|
+== How CAS Works
|
|
|
+Whilst the CAS web site contains documents that detail the architecture of CAS, we present the general overview again here within the context of Spring Security.
|
|
|
+Spring Security 3.x supports CAS 3.
|
|
|
+At the time of writing, the CAS server was at version 3.4.
|
|
|
+
|
|
|
+Somewhere in your enterprise you will need to setup a CAS server.
|
|
|
+The CAS server is simply a standard WAR file, so there isn't anything difficult about setting up your server.
|
|
|
+Inside the WAR file you will customise the login and other single sign on pages displayed to users.
|
|
|
+
|
|
|
+When deploying a CAS 3.4 server, you will also need to specify an `AuthenticationHandler` in the `deployerConfigContext.xml` included with CAS.
|
|
|
+The `AuthenticationHandler` has a simple method that returns a boolean as to whether a given set of Credentials is valid.
|
|
|
+Your `AuthenticationHandler` implementation will need to link into some type of backend authentication repository, such as an LDAP server or database.
|
|
|
+CAS itself includes numerous ``AuthenticationHandler``s out of the box to assist with this.
|
|
|
+When you download and deploy the server war file, it is set up to successfully authenticate users who enter a password matching their username, which is useful for testing.
|
|
|
+
|
|
|
+Apart from the CAS server itself, the other key players are of course the secure web applications deployed throughout your enterprise.
|
|
|
+These web applications are known as "services".
|
|
|
+There are three types of services.
|
|
|
+Those that authenticate service tickets, those that can obtain proxy tickets, and those that authenticate proxy tickets.
|
|
|
+Authenticating a proxy ticket differs because the list of proxies must be validated and often times a proxy ticket can be reused.
|
|
|
+
|
|
|
+
|
|
|
+[[cas-sequence]]
|
|
|
+=== Spring Security and CAS Interaction Sequence
|
|
|
+The basic interaction between a web browser, CAS server and a Spring Security-secured service is as follows:
|
|
|
+
|
|
|
+* The web user is browsing the service's public pages.
|
|
|
+CAS or Spring Security is not involved.
|
|
|
+* The user eventually requests a page that is either secure or one of the beans it uses is secure.
|
|
|
+Spring Security's `ExceptionTranslationFilter` will detect the `AccessDeniedException` or `AuthenticationException`.
|
|
|
+* Because the user's `Authentication` object (or lack thereof) caused an `AuthenticationException`, the `ExceptionTranslationFilter` will call the configured `AuthenticationEntryPoint`.
|
|
|
+If using CAS, this will be the `CasAuthenticationEntryPoint` class.
|
|
|
+* The `CasAuthenticationEntryPoint` will redirect the user's browser to the CAS server.
|
|
|
+It will also indicate a `service` parameter, which is the callback URL for the Spring Security service (your application).
|
|
|
+For example, the URL to which the browser is redirected might be https://my.company.com/cas/login?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/cas.
|
|
|
+* After the user's browser redirects to CAS, they will be prompted for their username and password.
|
|
|
+If the user presents a session cookie which indicates they've previously logged on, they will not be prompted to login again (there is an exception to this procedure, which we'll cover later).
|
|
|
+CAS will use the `PasswordHandler` (or `AuthenticationHandler` if using CAS 3.0) discussed above to decide whether the username and password is valid.
|
|
|
+* Upon successful login, CAS will redirect the user's browser back to the original service.
|
|
|
+It will also include a `ticket` parameter, which is an opaque string representing the "service ticket".
|
|
|
+Continuing our earlier example, the URL the browser is redirected to might be https://server3.company.com/webapp/login/cas?ticket=ST-0-ER94xMJmn6pha35CQRoZ.
|
|
|
+* Back in the service web application, the `CasAuthenticationFilter` is always listening for requests to `/login/cas` (this is configurable, but we'll use the defaults in this introduction).
|
|
|
+The processing filter will construct a `UsernamePasswordAuthenticationToken` representing the service ticket.
|
|
|
+The principal will be equal to `CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER`, whilst the credentials will be the service ticket opaque value.
|
|
|
+This authentication request will then be handed to the configured `AuthenticationManager`.
|
|
|
+* The `AuthenticationManager` implementation will be the `ProviderManager`, which is in turn configured with the `CasAuthenticationProvider`.
|
|
|
+The `CasAuthenticationProvider` only responds to ``UsernamePasswordAuthenticationToken``s containing the CAS-specific principal (such as `CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER`) and ``CasAuthenticationToken``s (discussed later).
|
|
|
+* `CasAuthenticationProvider` will validate the service ticket using a `TicketValidator` implementation.
|
|
|
+This will typically be a `Cas20ServiceTicketValidator` which is one of the classes included in the CAS client library.
|
|
|
+In the event the application needs to validate proxy tickets, the `Cas20ProxyTicketValidator` is used.
|
|
|
+The `TicketValidator` makes an HTTPS request to the CAS server in order to validate the service ticket.
|
|
|
+It may also include a proxy callback URL, which is included in this example: https://my.company.com/cas/proxyValidate?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/cas&ticket=ST-0-ER94xMJmn6pha35CQRoZ&pgtUrl=https://server3.company.com/webapp/login/cas/proxyreceptor.
|
|
|
+* Back on the CAS server, the validation request will be received.
|
|
|
+If the presented service ticket matches the service URL the ticket was issued to, CAS will provide an affirmative response in XML indicating the username.
|
|
|
+If any proxy was involved in the authentication (discussed below), the list of proxies is also included in the XML response.
|
|
|
+* [OPTIONAL] If the request to the CAS validation service included the proxy callback URL (in the `pgtUrl` parameter), CAS will include a `pgtIou` string in the XML response.
|
|
|
+This `pgtIou` represents a proxy-granting ticket IOU.
|
|
|
+The CAS server will then create its own HTTPS connection back to the `pgtUrl`.
|
|
|
+This is to mutually authenticate the CAS server and the claimed service URL.
|
|
|
+The HTTPS connection will be used to send a proxy granting ticket to the original web application.
|
|
|
+For example, https://server3.company.com/webapp/login/cas/proxyreceptor?pgtIou=PGTIOU-0-R0zlgrl4pdAQwBvJWO3vnNpevwqStbSGcq3vKB2SqSFFRnjPHt&pgtId=PGT-1-si9YkkHLrtACBo64rmsi3v2nf7cpCResXg5MpESZFArbaZiOKH.
|
|
|
+* The `Cas20TicketValidator` will parse the XML received from the CAS server.
|
|
|
+It will return to the `CasAuthenticationProvider` a `TicketResponse`, which includes the username (mandatory), proxy list (if any were involved), and proxy-granting ticket IOU (if the proxy callback was requested).
|
|
|
+* Next `CasAuthenticationProvider` will call a configured `CasProxyDecider`.
|
|
|
+The `CasProxyDecider` indicates whether the proxy list in the `TicketResponse` is acceptable to the service.
|
|
|
+Several implementations are provided with Spring Security: `RejectProxyTickets`, `AcceptAnyCasProxy` and `NamedCasProxyDecider`.
|
|
|
+These names are largely self-explanatory, except `NamedCasProxyDecider` which allows a `List` of trusted proxies to be provided.
|
|
|
+* `CasAuthenticationProvider` will next request a `AuthenticationUserDetailsService` to load the `GrantedAuthority` objects that apply to the user contained in the `Assertion`.
|
|
|
+* If there were no problems, `CasAuthenticationProvider` constructs a `CasAuthenticationToken` including the details contained in the `TicketResponse` and the ``GrantedAuthority``s.
|
|
|
+* Control then returns to `CasAuthenticationFilter`, which places the created `CasAuthenticationToken` in the security context.
|
|
|
+* The user's browser is redirected to the original page that caused the `AuthenticationException` (or a custom destination depending on the configuration).
|
|
|
+
|
|
|
+It's good that you're still here!
|
|
|
+Let's now look at how this is configured
|
|
|
+
|
|
|
+[[cas-client]]
|
|
|
+== Configuration of CAS Client
|
|
|
+The web application side of CAS is made easy due to Spring Security.
|
|
|
+It is assumed you already know the basics of using Spring Security, so these are not covered again below.
|
|
|
+We'll assume a namespace based configuration is being used and add in the CAS beans as required.
|
|
|
+Each section builds upon the previous section.
|
|
|
+A full CAS sample application can be found in the Spring Security xref:samples.adoc#samples[Samples].
|
|
|
+
|
|
|
+
|
|
|
+[[cas-st]]
|
|
|
+=== Service Ticket Authentication
|
|
|
+This section describes how to setup Spring Security to authenticate Service Tickets.
|
|
|
+Often times this is all a web application requires.
|
|
|
+You will need to add a `ServiceProperties` bean to your application context.
|
|
|
+This represents your CAS service:
|
|
|
+
|
|
|
+[source,xml]
|
|
|
+----
|
|
|
+<bean id="serviceProperties"
|
|
|
+ class="org.springframework.security.cas.ServiceProperties">
|
|
|
+<property name="service"
|
|
|
+ value="https://localhost:8443/cas-sample/login/cas"/>
|
|
|
+<property name="sendRenew" value="false"/>
|
|
|
+</bean>
|
|
|
+----
|
|
|
+
|
|
|
+The `service` must equal a URL that will be monitored by the `CasAuthenticationFilter`.
|
|
|
+The `sendRenew` defaults to false, but should be set to true if your application is particularly sensitive.
|
|
|
+What this parameter does is tell the CAS login service that a single sign on login is unacceptable.
|
|
|
+Instead, the user will need to re-enter their username and password in order to gain access to the service.
|
|
|
+
|
|
|
+The following beans should be configured to commence the CAS authentication process (assuming you're using a namespace configuration):
|
|
|
+
|
|
|
+[source,xml]
|
|
|
+----
|
|
|
+<security:http entry-point-ref="casEntryPoint">
|
|
|
+...
|
|
|
+<security:custom-filter position="CAS_FILTER" ref="casFilter" />
|
|
|
+</security:http>
|
|
|
+
|
|
|
+<bean id="casFilter"
|
|
|
+ class="org.springframework.security.cas.web.CasAuthenticationFilter">
|
|
|
+<property name="authenticationManager" ref="authenticationManager"/>
|
|
|
+</bean>
|
|
|
+
|
|
|
+<bean id="casEntryPoint"
|
|
|
+ class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
|
|
|
+<property name="loginUrl" value="https://localhost:9443/cas/login"/>
|
|
|
+<property name="serviceProperties" ref="serviceProperties"/>
|
|
|
+</bean>
|
|
|
+----
|
|
|
+
|
|
|
+For CAS to operate, the `ExceptionTranslationFilter` must have its `authenticationEntryPoint` property set to the `CasAuthenticationEntryPoint` bean.
|
|
|
+This can easily be done using xref:servlet/appendix/namespace.adoc#nsa-http-entry-point-ref[entry-point-ref] as is done in the example above.
|
|
|
+The `CasAuthenticationEntryPoint` must refer to the `ServiceProperties` bean (discussed above), which provides the URL to the enterprise's CAS login server.
|
|
|
+This is where the user's browser will be redirected.
|
|
|
+
|
|
|
+The `CasAuthenticationFilter` has very similar properties to the `UsernamePasswordAuthenticationFilter` (used for form-based logins).
|
|
|
+You can use these properties to customize things like behavior for authentication success and failure.
|
|
|
+
|
|
|
+Next you need to add a `CasAuthenticationProvider` and its collaborators:
|
|
|
+
|
|
|
+[source,xml,attrs="-attributes"]
|
|
|
+----
|
|
|
+<security:authentication-manager alias="authenticationManager">
|
|
|
+<security:authentication-provider ref="casAuthenticationProvider" />
|
|
|
+</security:authentication-manager>
|
|
|
+
|
|
|
+<bean id="casAuthenticationProvider"
|
|
|
+ class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
|
|
|
+<property name="authenticationUserDetailsService">
|
|
|
+ <bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
|
|
|
+ <constructor-arg ref="userService" />
|
|
|
+ </bean>
|
|
|
+</property>
|
|
|
+<property name="serviceProperties" ref="serviceProperties" />
|
|
|
+<property name="ticketValidator">
|
|
|
+ <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
|
|
|
+ <constructor-arg index="0" value="https://localhost:9443/cas" />
|
|
|
+ </bean>
|
|
|
+</property>
|
|
|
+<property name="key" value="an_id_for_this_auth_provider_only"/>
|
|
|
+</bean>
|
|
|
+
|
|
|
+<security:user-service id="userService">
|
|
|
+<!-- Password is prefixed with {noop} to indicate to DelegatingPasswordEncoder that
|
|
|
+NoOpPasswordEncoder should be used.
|
|
|
+This is not safe for production, but makes reading
|
|
|
+in samples easier.
|
|
|
+Normally passwords should be hashed using BCrypt -->
|
|
|
+<security:user name="joe" password="{noop}joe" authorities="ROLE_USER" />
|
|
|
+...
|
|
|
+</security:user-service>
|
|
|
+----
|
|
|
+
|
|
|
+The `CasAuthenticationProvider` uses a `UserDetailsService` instance to load the authorities for a user, once they have been authenticated by CAS.
|
|
|
+We've shown a simple in-memory setup here.
|
|
|
+Note that the `CasAuthenticationProvider` does not actually use the password for authentication, but it does use the authorities.
|
|
|
+
|
|
|
+The beans are all reasonably self-explanatory if you refer back to the <<cas-how-it-works,How CAS Works>> section.
|
|
|
+
|
|
|
+This completes the most basic configuration for CAS.
|
|
|
+If you haven't made any mistakes, your web application should happily work within the framework of CAS single sign on.
|
|
|
+No other parts of Spring Security need to be concerned about the fact CAS handled authentication.
|
|
|
+In the following sections we will discuss some (optional) more advanced configurations.
|
|
|
+
|
|
|
+
|
|
|
+[[cas-singlelogout]]
|
|
|
+=== Single Logout
|
|
|
+The CAS protocol supports Single Logout and can be easily added to your Spring Security configuration.
|
|
|
+Below are updates to the Spring Security configuration that handle Single Logout
|
|
|
+
|
|
|
+[source,xml]
|
|
|
+----
|
|
|
+<security:http entry-point-ref="casEntryPoint">
|
|
|
+...
|
|
|
+<security:logout logout-success-url="/cas-logout.jsp"/>
|
|
|
+<security:custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/>
|
|
|
+<security:custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/>
|
|
|
+</security:http>
|
|
|
+
|
|
|
+<!-- This filter handles a Single Logout Request from the CAS Server -->
|
|
|
+<bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/>
|
|
|
+
|
|
|
+<!-- This filter redirects to the CAS Server to signal Single Logout should be performed -->
|
|
|
+<bean id="requestSingleLogoutFilter"
|
|
|
+ class="org.springframework.security.web.authentication.logout.LogoutFilter">
|
|
|
+<constructor-arg value="https://localhost:9443/cas/logout"/>
|
|
|
+<constructor-arg>
|
|
|
+ <bean class=
|
|
|
+ "org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
|
|
|
+</constructor-arg>
|
|
|
+<property name="filterProcessesUrl" value="/logout/cas"/>
|
|
|
+</bean>
|
|
|
+----
|
|
|
+
|
|
|
+The `logout` element logs the user out of the local application, but does not end the session with the CAS server or any other applications that have been logged into.
|
|
|
+The `requestSingleLogoutFilter` filter will allow the URL of `/spring_security_cas_logout` to be requested to redirect the application to the configured CAS Server logout URL.
|
|
|
+Then the CAS Server will send a Single Logout request to all the services that were signed into.
|
|
|
+The `singleLogoutFilter` handles the Single Logout request by looking up the `HttpSession` in a static `Map` and then invalidating it.
|
|
|
+
|
|
|
+It might be confusing why both the `logout` element and the `singleLogoutFilter` are needed.
|
|
|
+It is considered best practice to logout locally first since the `SingleSignOutFilter` just stores the `HttpSession` in a static `Map` in order to call invalidate on it.
|
|
|
+With the configuration above, the flow of logout would be:
|
|
|
+
|
|
|
+* The user requests `/logout` which would log the user out of the local application and send the user to the logout success page.
|
|
|
+* The logout success page, `/cas-logout.jsp`, should instruct the user to click a link pointing to `/logout/cas` in order to logout out of all applications.
|
|
|
+* When the user clicks the link, the user is redirected to the CAS single logout URL (https://localhost:9443/cas/logout).
|
|
|
+* On the CAS Server side, the CAS single logout URL then submits single logout requests to all the CAS Services.
|
|
|
+On the CAS Service side, JASIG's `SingleSignOutFilter` processes the logout request by invalidating the original session.
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+The next step is to add the following to your web.xml
|
|
|
+
|
|
|
+[source,xml]
|
|
|
+----
|
|
|
+<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-mapping>
|
|
|
+<filter-name>characterEncodingFilter</filter-name>
|
|
|
+<url-pattern>/*</url-pattern>
|
|
|
+</filter-mapping>
|
|
|
+<listener>
|
|
|
+<listener-class>
|
|
|
+ org.jasig.cas.client.session.SingleSignOutHttpSessionListener
|
|
|
+</listener-class>
|
|
|
+</listener>
|
|
|
+----
|
|
|
+
|
|
|
+When using the SingleSignOutFilter you might encounter some encoding issues.
|
|
|
+Therefore it is recommended to add the `CharacterEncodingFilter` to ensure that the character encoding is correct when using the `SingleSignOutFilter`.
|
|
|
+Again, refer to JASIG's documentation for details.
|
|
|
+The `SingleSignOutHttpSessionListener` ensures that when an `HttpSession` expires, the mapping used for single logout is removed.
|
|
|
+
|
|
|
+
|
|
|
+[[cas-pt-client]]
|
|
|
+=== Authenticating to a Stateless Service with CAS
|
|
|
+This section describes how to authenticate to a service using CAS.
|
|
|
+In other words, this section discusses how to setup a client that uses a service that authenticates with CAS.
|
|
|
+The next section describes how to setup a stateless service to Authenticate using CAS.
|
|
|
+
|
|
|
+
|
|
|
+[[cas-pt-client-config]]
|
|
|
+==== Configuring CAS to Obtain Proxy Granting Tickets
|
|
|
+In order to authenticate to a stateless service, the application needs to obtain a proxy granting ticket (PGT).
|
|
|
+This section describes how to configure Spring Security to obtain a PGT building upon thencas-st[Service Ticket Authentication] configuration.
|
|
|
+
|
|
|
+The first step is to include a `ProxyGrantingTicketStorage` in your Spring Security configuration.
|
|
|
+This is used to store PGT's that are obtained by the `CasAuthenticationFilter` so that they can be used to obtain proxy tickets.
|
|
|
+An example configuration is shown below
|
|
|
+
|
|
|
+[source,xml]
|
|
|
+----
|
|
|
+<!--
|
|
|
+NOTE: In a real application you should not use an in memory implementation.
|
|
|
+You will also want to ensure to clean up expired tickets by calling
|
|
|
+ProxyGrantingTicketStorage.cleanup()
|
|
|
+-->
|
|
|
+<bean id="pgtStorage" class="org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl"/>
|
|
|
+----
|
|
|
+
|
|
|
+The next step is to update the `CasAuthenticationProvider` to be able to obtain proxy tickets.
|
|
|
+To do this replace the `Cas20ServiceTicketValidator` with a `Cas20ProxyTicketValidator`.
|
|
|
+The `proxyCallbackUrl` should be set to a URL that the application will receive PGT's at.
|
|
|
+Last, the configuration should also reference the `ProxyGrantingTicketStorage` so it can use a PGT to obtain proxy tickets.
|
|
|
+You can find an example of the configuration changes that should be made below.
|
|
|
+
|
|
|
+[source,xml]
|
|
|
+----
|
|
|
+<bean id="casAuthenticationProvider"
|
|
|
+ class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
|
|
|
+...
|
|
|
+<property name="ticketValidator">
|
|
|
+ <bean class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator">
|
|
|
+ <constructor-arg value="https://localhost:9443/cas"/>
|
|
|
+ <property name="proxyCallbackUrl"
|
|
|
+ value="https://localhost:8443/cas-sample/login/cas/proxyreceptor"/>
|
|
|
+ <property name="proxyGrantingTicketStorage" ref="pgtStorage"/>
|
|
|
+ </bean>
|
|
|
+</property>
|
|
|
+</bean>
|
|
|
+----
|
|
|
+
|
|
|
+The last step is to update the `CasAuthenticationFilter` to accept PGT and to store them in the `ProxyGrantingTicketStorage`.
|
|
|
+It is important the `proxyReceptorUrl` matches the `proxyCallbackUrl` of the `Cas20ProxyTicketValidator`.
|
|
|
+An example configuration is shown below.
|
|
|
+
|
|
|
+[source,xml]
|
|
|
+----
|
|
|
+
|
|
|
+<bean id="casFilter"
|
|
|
+ class="org.springframework.security.cas.web.CasAuthenticationFilter">
|
|
|
+ ...
|
|
|
+ <property name="proxyGrantingTicketStorage" ref="pgtStorage"/>
|
|
|
+ <property name="proxyReceptorUrl" value="/login/cas/proxyreceptor"/>
|
|
|
+</bean>
|
|
|
+
|
|
|
+----
|
|
|
+
|
|
|
+[[cas-pt-client-sample]]
|
|
|
+==== Calling a Stateless Service Using a Proxy Ticket
|
|
|
+Now that Spring Security obtains PGTs, you can use them to create proxy tickets which can be used to authenticate to a stateless service.
|
|
|
+The CAS xref:samples.adoc#samples[sample application] contains a working example in the `ProxyTicketSampleServlet`.
|
|
|
+Example code can be found below:
|
|
|
+
|
|
|
+====
|
|
|
+.Java
|
|
|
+[source,java,role="primary"]
|
|
|
+----
|
|
|
+protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
|
|
+ throws ServletException, IOException {
|
|
|
+// NOTE: The CasAuthenticationToken can also be obtained using
|
|
|
+// SecurityContextHolder.getContext().getAuthentication()
|
|
|
+final CasAuthenticationToken token = (CasAuthenticationToken) request.getUserPrincipal();
|
|
|
+// proxyTicket could be reused to make calls to the CAS service even if the
|
|
|
+// target url differs
|
|
|
+final String proxyTicket = token.getAssertion().getPrincipal().getProxyTicketFor(targetUrl);
|
|
|
+
|
|
|
+// Make a remote call using the proxy ticket
|
|
|
+final String serviceUrl = targetUrl+"?ticket="+URLEncoder.encode(proxyTicket, "UTF-8");
|
|
|
+String proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8");
|
|
|
+...
|
|
|
+}
|
|
|
+----
|
|
|
+
|
|
|
+.Kotlin
|
|
|
+[source,kotlin,role="secondary"]
|
|
|
+----
|
|
|
+protected fun doGet(request: HttpServletRequest, response: HttpServletResponse?) {
|
|
|
+ // NOTE: The CasAuthenticationToken can also be obtained using
|
|
|
+ // SecurityContextHolder.getContext().getAuthentication()
|
|
|
+ val token = request.userPrincipal as CasAuthenticationToken
|
|
|
+ // proxyTicket could be reused to make calls to the CAS service even if the
|
|
|
+ // target url differs
|
|
|
+ val proxyTicket = token.assertion.principal.getProxyTicketFor(targetUrl)
|
|
|
+
|
|
|
+ // Make a remote call using the proxy ticket
|
|
|
+ val serviceUrl: String = targetUrl + "?ticket=" + URLEncoder.encode(proxyTicket, "UTF-8")
|
|
|
+ val proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8")
|
|
|
+}
|
|
|
+----
|
|
|
+====
|
|
|
+
|
|
|
+[[cas-pt]]
|
|
|
+=== Proxy Ticket Authentication
|
|
|
+The `CasAuthenticationProvider` distinguishes between stateful and stateless clients.
|
|
|
+A stateful client is considered any that submits to the `filterProcessUrl` of the `CasAuthenticationFilter`.
|
|
|
+A stateless client is any that presents an authentication request to `CasAuthenticationFilter` on a URL other than the `filterProcessUrl`.
|
|
|
+
|
|
|
+Because remoting protocols have no way of presenting themselves within the context of an `HttpSession`, it isn't possible to rely on the default practice of storing the security context in the session between requests.
|
|
|
+Furthermore, because the CAS server invalidates a ticket after it has been validated by the `TicketValidator`, presenting the same proxy ticket on subsequent requests will not work.
|
|
|
+
|
|
|
+One obvious option is to not use CAS at all for remoting protocol clients.
|
|
|
+However, this would eliminate many of the desirable features of CAS.
|
|
|
+As a middle-ground, the `CasAuthenticationProvider` uses a `StatelessTicketCache`.
|
|
|
+This is used solely for stateless clients which use a principal equal to `CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER`.
|
|
|
+What happens is the `CasAuthenticationProvider` will store the resulting `CasAuthenticationToken` in the `StatelessTicketCache`, keyed on the proxy ticket.
|
|
|
+Accordingly, remoting protocol clients can present the same proxy ticket and the `CasAuthenticationProvider` will not need to contact the CAS server for validation (aside from the first request).
|
|
|
+Once authenticated, the proxy ticket could be used for URLs other than the original target service.
|
|
|
+
|
|
|
+This section builds upon the previous sections to accommodate proxy ticket authentication.
|
|
|
+The first step is to specify to authenticate all artifacts as shown below.
|
|
|
+
|
|
|
+[source,xml]
|
|
|
+----
|
|
|
+<bean id="serviceProperties"
|
|
|
+ class="org.springframework.security.cas.ServiceProperties">
|
|
|
+...
|
|
|
+<property name="authenticateAllArtifacts" value="true"/>
|
|
|
+</bean>
|
|
|
+----
|
|
|
+
|
|
|
+The next step is to specify `serviceProperties` and the `authenticationDetailsSource` for the `CasAuthenticationFilter`.
|
|
|
+The `serviceProperties` property instructs the `CasAuthenticationFilter` to attempt to authenticate all artifacts instead of only ones present on the `filterProcessUrl`.
|
|
|
+The `ServiceAuthenticationDetailsSource` creates a `ServiceAuthenticationDetails` that ensures the current URL, based upon the `HttpServletRequest`, is used as the service URL when validating the ticket.
|
|
|
+The method for generating the service URL can be customized by injecting a custom `AuthenticationDetailsSource` that returns a custom `ServiceAuthenticationDetails`.
|
|
|
+
|
|
|
+[source,xml]
|
|
|
+----
|
|
|
+<bean id="casFilter"
|
|
|
+ class="org.springframework.security.cas.web.CasAuthenticationFilter">
|
|
|
+...
|
|
|
+<property name="serviceProperties" ref="serviceProperties"/>
|
|
|
+<property name="authenticationDetailsSource">
|
|
|
+ <bean class=
|
|
|
+ "org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource">
|
|
|
+ <constructor-arg ref="serviceProperties"/>
|
|
|
+ </bean>
|
|
|
+</property>
|
|
|
+</bean>
|
|
|
+----
|
|
|
+
|
|
|
+You will also need to update the `CasAuthenticationProvider` to handle proxy tickets.
|
|
|
+To do this replace the `Cas20ServiceTicketValidator` with a `Cas20ProxyTicketValidator`.
|
|
|
+You will need to configure the `statelessTicketCache` and which proxies you want to accept.
|
|
|
+You can find an example of the updates required to accept all proxies below.
|
|
|
+
|
|
|
+[source,xml]
|
|
|
+----
|
|
|
+
|
|
|
+<bean id="casAuthenticationProvider"
|
|
|
+ class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
|
|
|
+...
|
|
|
+<property name="ticketValidator">
|
|
|
+ <bean class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator">
|
|
|
+ <constructor-arg value="https://localhost:9443/cas"/>
|
|
|
+ <property name="acceptAnyProxy" value="true"/>
|
|
|
+ </bean>
|
|
|
+</property>
|
|
|
+<property name="statelessTicketCache">
|
|
|
+ <bean 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>
|
|
|
+</property>
|
|
|
+</bean>
|
|
|
+----
|