SEC-1718: Update documentation and sample application to demonstrate how to use a PGT to authenticate to stateless services using a PT
This commit is contained in:
parent
abfa558c3c
commit
01fb4bdb6d
|
@ -125,8 +125,8 @@
|
|||
included in the CAS client library. In the event the application needs to validate proxy tickets, the
|
||||
<classname>Cas20ProxyTicketValidator</classname> is used. The
|
||||
<interfacename>TicketValidator</interfacename> 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:
|
||||
<literal>https://my.company.com/cas/proxyValidate?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Fj_spring_cas_security_check&ticket=ST-0-ER94xMJmn6pha35CQRoZ&pgtUrl=https://server3.company.com/webapp/casProxy/receptor</literal>.-->
|
||||
validate the service ticket. It may also include a proxy callback URL, which is included in this example:
|
||||
<literal>https://my.company.com/cas/proxyValidate?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Fj_spring_cas_security_check&ticket=ST-0-ER94xMJmn6pha35CQRoZ&pgtUrl=https://server3.company.com/webapp/j_spring_cas_security_proxyreceptor</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
|
@ -134,11 +134,11 @@
|
|||
<para>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
|
||||
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.--></para>
|
||||
included in the XML response.</para>
|
||||
</listitem>
|
||||
<!--
|
||||
|
||||
<listitem>
|
||||
<para>[OPTIONAL] If the request to the CAS validation service included the proxy callback
|
||||
URL (in the <literal>pgtUrl</literal> parameter), CAS will include a
|
||||
|
@ -147,17 +147,17 @@
|
|||
connection back to the <literal>pgtUrl</literal>. 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,
|
||||
<literal>https://server3.company.com/webapp/casProxy/receptor?pgtIou=PGTIOU-0-R0zlgrl4pdAQwBvJWO3vnNpevwqStbSGcq3vKB2SqSFFRnjPHt&pgtId=PGT-1-si9YkkHLrtACBo64rmsi3v2nf7cpCResXg5MpESZFArbaZiOKH</literal>.</para>
|
||||
<literal>https://server3.company.com/webapp/j_spring_cas_security_proxyreceptor?pgtIou=PGTIOU-0-R0zlgrl4pdAQwBvJWO3vnNpevwqStbSGcq3vKB2SqSFFRnjPHt&pgtId=PGT-1-si9YkkHLrtACBo64rmsi3v2nf7cpCResXg5MpESZFArbaZiOKH</literal>.</para>
|
||||
</listitem>
|
||||
-->
|
||||
|
||||
<listitem>
|
||||
<para>The <classname>Cas20TicketValidator</classname> will parse the XML received from the
|
||||
CAS server. It will return to the <classname>CasAuthenticationProvider</classname> a
|
||||
<literal>TicketResponse</literal>, which includes the username (mandatory). <!--, proxy list
|
||||
<literal>TicketResponse</literal>, which includes the username (mandatory), proxy list
|
||||
(if any were involved), and proxy-granting ticket IOU (if the proxy callback was
|
||||
requested). --></para>
|
||||
requested).</para>
|
||||
</listitem>
|
||||
<!--
|
||||
|
||||
<listitem>
|
||||
<para>Next <literal>CasAuthenticationProvider</literal> will call
|
||||
a configured <literal>CasProxyDecider</literal>. The
|
||||
|
@ -171,7 +171,7 @@
|
|||
which allows a <literal>List</literal> of trusted proxies to be
|
||||
provided.</para>
|
||||
</listitem>
|
||||
-->
|
||||
|
||||
<listitem>
|
||||
<para><classname>CasAuthenticationProvider</classname> will next
|
||||
request a <interfacename>AuthenticationUserDetailsService</interfacename> to load the
|
||||
|
@ -219,9 +219,9 @@
|
|||
<info>
|
||||
<title>Service Ticket Authentication</title>
|
||||
</info>
|
||||
<para>This section describes how to setup Spring Security to authenticate Service Tickets. You will need
|
||||
to add a <classname>ServiceProperties</classname> bean to your application context. This represents
|
||||
your CAS service:</para>
|
||||
<para>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 <classname>ServiceProperties</classname>
|
||||
bean to your application context. This represents your CAS service:</para>
|
||||
<para> <programlisting language="xml"><![CDATA[
|
||||
<bean id="serviceProperties"
|
||||
class="org.springframework.security.cas.ServiceProperties">
|
||||
|
@ -384,6 +384,89 @@
|
|||
when an <interfacename>HttpSession</interfacename> expires, the mapping used for single logout is
|
||||
removed.</para>
|
||||
</section>
|
||||
<section xml:id="cas-pt-client">
|
||||
<info>
|
||||
<title>Authenticating to a Stateless Service with CAS</title>
|
||||
</info>
|
||||
<para>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.</para>
|
||||
<section xml:id="cas-pt-client-config">
|
||||
<info>
|
||||
<title>Configuring CAS to Obtain Proxy Granting Tickets</title>
|
||||
</info>
|
||||
<para>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 then
|
||||
<link xlink:href="cas-st">Service Ticket Authentication</link> configuration.</para>
|
||||
<para>The first step is to include a <classname>ProxyGrantingTicketStorage</classname> in your Spring Security
|
||||
configuration. This is used to store PGT's that are obtained by the
|
||||
<classname>CasAuthenticationFilter</classname> so that they can be used to obtain proxy tickets. An example
|
||||
configuration is shown below <programlisting language="xml"><![CDATA[
|
||||
<!--
|
||||
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"/>
|
||||
]]></programlisting></para>
|
||||
<para>The next step is to update the <classname>CasAuthenticationProvider</classname> to be able to obtain proxy
|
||||
tickets. To do this replace the <classname>Cas20ServiceTicketValidator</classname> with a
|
||||
<classname>Cas20ProxyTicketValidator</classname>. The <literal>proxyCallbackUrl</literal> should be set to
|
||||
a URL that the application will receive PGT's at. Last, the configuration should also reference the
|
||||
<classname>ProxyGrantingTicketStorage</classname> so it can use a PGT to obtain proxy tickets.
|
||||
You can find an example of the configuration changes that should be made below.
|
||||
<programlisting language="xml"><![CDATA[
|
||||
<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/j_spring_cas_security_proxyreceptor"/>
|
||||
<property name="proxyGrantingTicketStorage" ref="pgtStorage"/>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
]]></programlisting></para>
|
||||
<para>The last step is to update the <classname>CasAuthenticationFilter</classname> to accept PGT and to store them
|
||||
in the <classname>ProxyGrantingTicketStorage</classname>. It is important the the <literal>proxyReceptorUrl</literal>
|
||||
matches the <literal>proxyCallbackUrl</literal> of the <classname>Cas20ProxyTicketValidator</classname>. An example
|
||||
configuration is shown below.
|
||||
<programlisting language="xml"><![CDATA[
|
||||
<bean id="casFilter"
|
||||
class="org.springframework.security.cas.web.CasAuthenticationFilter">
|
||||
...
|
||||
<property name="proxyGrantingTicketStorage" ref="pgtStorage"/>
|
||||
<property name="proxyReceptorUrl" value="/j_spring_cas_security_proxyreceptor"/>
|
||||
</bean>
|
||||
]]></programlisting></para>
|
||||
</section>
|
||||
<section xml:id="cas-pt-client-sample">
|
||||
<info>
|
||||
<title>Calling a Stateless Service Using a Proxy Ticket</title>
|
||||
</info>
|
||||
<para>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 <link xlink:href="#cas-sample">CAS sample application</link> contains a working example in
|
||||
the <classname>ProxyTicketSampleServlet</classname>. Example code can be found below:
|
||||
<programlisting language="xml"><![CDATA[
|
||||
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 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");
|
||||
...
|
||||
}
|
||||
]]></programlisting></para>
|
||||
</section>
|
||||
</section>
|
||||
<section xml:id="cas-pt">
|
||||
<info>
|
||||
<title>Proxy Ticket Authentication</title>
|
||||
|
|
|
@ -33,8 +33,13 @@ dependencies {
|
|||
|
||||
casServer "org.jasig.cas:cas-server-webapp:3.4.3.1@war"
|
||||
|
||||
runtime project(':spring-security-web'),
|
||||
providedCompile 'javax.servlet:servlet-api:2.5@jar'
|
||||
|
||||
compile project(':spring-security-core'),
|
||||
project(':spring-security-cas'),
|
||||
"org.jasig.cas.client:cas-client-core:3.1.12"
|
||||
|
||||
runtime project(':spring-security-web'),
|
||||
project(':spring-security-config'),
|
||||
"org.slf4j:jcl-over-slf4j:$slf4jVersion",
|
||||
"ch.qos.logback:logback-classic:$logbackVersion"
|
||||
|
|
|
@ -54,6 +54,13 @@ class CasSampleProxySpec extends BaseSpec {
|
|||
content.contains('<h1>Secure Page</h1>')
|
||||
}
|
||||
|
||||
def 'access proxy ticket sample succeeds with ROLE_USER'() {
|
||||
when: 'a proxy ticket is used to create another proxy ticket'
|
||||
def content = getSecured(getBaseUrl()+ProxyTicketSamplePage.url).responseBodyAsString
|
||||
then: 'The proxy ticket sample page is returned'
|
||||
content.contains('<h1>Secure Page using a Proxy Ticket</h1>')
|
||||
}
|
||||
|
||||
def 'access extremely secure page with ROLE_USER is denied'() {
|
||||
when: 'User with ROLE_USER accesses the extremely secure page'
|
||||
GetMethod method = getSecured(getBaseUrl()+ExtremelySecurePage.url)
|
||||
|
|
|
@ -69,6 +69,13 @@ class CasSampleSpec extends BaseSpec {
|
|||
at SecurePage
|
||||
}
|
||||
|
||||
def 'access proxy ticket sample with ROLE_USER is allowed'() {
|
||||
when: 'user with ROLE_USER requests the proxy ticket sample page'
|
||||
to ProxyTicketSamplePage
|
||||
then: 'the proxy ticket sample page is displayed'
|
||||
at ProxyTicketSamplePage
|
||||
}
|
||||
|
||||
def 'access extremely secure page with ROLE_USER is denied'() {
|
||||
when: 'User with ROLE_USER accesses extremely secure page'
|
||||
to ExtremelySecurePage
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.samples.cas.pages;
|
||||
|
||||
import geb.*
|
||||
import org.springframework.security.samples.cas.modules.*
|
||||
|
||||
|
||||
/**
|
||||
* Represents the proxy ticket sample page within the CAS Sample application.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
class ProxyTicketSamplePage extends Page {
|
||||
static url = "secure/ptSample"
|
||||
static at = { assert $('h1').text() == 'Secure Page using a Proxy Ticket'; true}
|
||||
static content = {
|
||||
navModule { module NavModule }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright 2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.samples.cas.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.jasig.cas.client.util.CommonUtils;
|
||||
import org.springframework.security.cas.authentication.CasAuthenticationToken;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* {@link ProxyTicketSampleServlet} demonstrates how to obtain a proxy ticket
|
||||
* and then use it to make a remote call. To learn how proxy tickets work, see
|
||||
* the <a href="https://wiki.jasig.org/display/CAS/Proxy+CAS+Walkthrough">Proxy
|
||||
* CAS Walkthrough</a>
|
||||
* </p>
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public final class ProxyTicketSampleServlet extends HttpServlet {
|
||||
/**
|
||||
* This is the URL that will be called and authenticate a proxy ticket.
|
||||
*/
|
||||
private String targetUrl;
|
||||
|
||||
@Override
|
||||
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 to the CAS service even if the target url differs
|
||||
final String proxyTicket = token.getAssertion().getPrincipal().getProxyTicketFor(targetUrl);
|
||||
|
||||
// Make a remote call to ourself. This is a bit silly, but it works well to demonstrate how to use proxy tickets.
|
||||
final String serviceUrl = targetUrl+"?ticket="+URLEncoder.encode(proxyTicket, "UTF-8");
|
||||
String proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8");
|
||||
|
||||
// modify the response and write it out to inform the user that it was obtained using a proxy ticket.
|
||||
proxyResponse = proxyResponse.replaceFirst("Secure Page", "Secure Page using a Proxy Ticket");
|
||||
proxyResponse = proxyResponse.replaceFirst("<p>",
|
||||
"<p>This page is rendered by "+getClass().getSimpleName()+" by making a remote call to the Secure Page using a proxy ticket ("+proxyTicket+") and inserts this message. ");
|
||||
final PrintWriter writer = response.getWriter();
|
||||
writer.write(proxyResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the target URL. It allows for the host to change based upon
|
||||
* the "cas.service.host" system property. If the property is not set, the
|
||||
* default is "localhost:8443".
|
||||
*/
|
||||
@Override
|
||||
public void init() throws ServletException {
|
||||
super.init();
|
||||
String casServiceHost = System.getProperty("cas.service.host", "localhost:8443");
|
||||
targetUrl = "https://"+casServiceHost+"/cas-sample/secure/";
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = -7720161771819727775L;
|
||||
}
|
|
@ -57,7 +57,9 @@
|
|||
<b:bean id="casFilter"
|
||||
class="org.springframework.security.cas.web.CasAuthenticationFilter"
|
||||
p:authenticationManager-ref="authManager"
|
||||
p:serviceProperties-ref="serviceProperties">
|
||||
p:serviceProperties-ref="serviceProperties"
|
||||
p:proxyGrantingTicketStorage-ref="pgtStorage"
|
||||
p:proxyReceptorUrl="/j_spring_cas_security_proxyreceptor">
|
||||
<b:property name="authenticationDetailsSource">
|
||||
<b:bean class="org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource"/>
|
||||
</b:property>
|
||||
|
@ -66,6 +68,11 @@
|
|||
p:defaultFailureUrl="/casfailed.jsp"/>
|
||||
</b:property>
|
||||
</b:bean>
|
||||
<!--
|
||||
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()
|
||||
-->
|
||||
<b:bean id="pgtStorage" class="org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl"/>
|
||||
<b:bean id="casAuthProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"
|
||||
p:serviceProperties-ref="serviceProperties"
|
||||
p:key="casAuthProviderKey">
|
||||
|
@ -78,7 +85,9 @@
|
|||
<b:property name="ticketValidator">
|
||||
<b:bean
|
||||
class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator"
|
||||
p:acceptAnyProxy="true">
|
||||
p:acceptAnyProxy="true"
|
||||
p:proxyCallbackUrl="https://${cas.service.host}/cas-sample/j_spring_cas_security_proxyreceptor"
|
||||
p:proxyGrantingTicketStorage-ref="pgtStorage">
|
||||
<b:constructor-arg value="https://${cas.server.host}/cas" />
|
||||
</b:bean>
|
||||
</b:property>
|
||||
|
|
|
@ -69,6 +69,15 @@
|
|||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>ptSampleServlet</servlet-name>
|
||||
<servlet-class>org.springframework.security.samples.cas.web.ProxyTicketSampleServlet</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>ptSampleServlet</servlet-name>
|
||||
<url-pattern>/secure/ptSample</url-pattern>
|
||||
</servlet-mapping>
|
||||
<error-page>
|
||||
<error-code>403</error-code>
|
||||
<location>/403.jsp</location>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<p>Your principal object is....: <%= request.getUserPrincipal() %></p>
|
||||
|
||||
<p><a href="secure/index.jsp">Secure page</a></p>
|
||||
<p><a href="secure/ptSample">Proxy Ticket Sample page</a></p>
|
||||
<p><a href="secure/extreme/index.jsp">Extremely secure page</a></p>
|
||||
</body>
|
||||
</html>
|
|
@ -4,6 +4,8 @@
|
|||
This is a protected page. You can only see me if you are a supervisor.
|
||||
|
||||
<p><a href="../../">Home</a>
|
||||
<p><a href="../../secure/index.jsp">Secure page</a></p>
|
||||
<p><a href="../../secure/ptSample">Proxy Ticket Sample page</a></p>
|
||||
<p><a href="../../j_spring_security_logout">Logout</a>
|
||||
</body>
|
||||
</html>
|
|
@ -1,15 +1,15 @@
|
|||
<html>
|
||||
<body>
|
||||
<h1>Secure Page</h1>
|
||||
This is a protected page. You can get to me if you've been remembered,
|
||||
or if you've authenticated this session.<br><br>
|
||||
<p>This is a protected page. You can get to me if you've been remembered,
|
||||
or if you've authenticated this session.</p>
|
||||
|
||||
<%if (request.isUserInRole("ROLE_SUPERVISOR")) { %>
|
||||
You are a supervisor! You can therefore see the <a href="extreme/index.jsp">extremely secure page</a>.<br><br>
|
||||
<p>You are a supervisor! You can therefore see the <a href="extreme/index.jsp">extremely secure page</a>.</p>
|
||||
<% } %>
|
||||
|
||||
|
||||
<p><a href="../">Home</a>
|
||||
<p><a href="ptSample">Proxy Ticket Sample page</a></p>
|
||||
<p><a href="../j_spring_security_logout">Logout</a>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue