I'd like to structure the blog into the following sections:
- Extend the STS to support Issue requests OnBehalfOf without authenticating the requesting party
- Protect the Web Services provider using SAML 2.0
- Integrate Web SSO and the Web Services stack in the Web Application
I've prepared this demo to run the different components in three tomcat instances. The reason is to get closer to a real scenario.
- tomcat-idp
This tomcat instance hosts the security infrastructure components which are the IDP and the STS.
HTTPS: 9443, HTTP: 9080
- tomcat-rp
This tomcat instance hosts the Web Application.
HTTPS: 8443, HTTP: 8080
- tomcat-svc
This tomcat instance hosts the Web Services provider which is usually not hosted on the same instance as the frontend.
HTTPS: 10443, HTTP: 10080
tomcatkeystore.jks
for HTTPS which can be downloaded here.The Tomcat HTTPS connector is configured in
conf/server.xml
. Deploy the tomcatkeystore.jks
of the example project to the Tomcat root directory if the Connector is configured as illustrated:<Connector port="10443" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" keystoreFile="tomcatKeystore.jks" keystorePass="tompass" sslProtocol="TLS" />
Extend the STS...
We use the same STS instance prepared for the Web SSO. The initial set up of this STS is described in my first blog. You can find the updated sources here.
The previous STS supported only one policy which says the communication with the STS must be secured on the transport level using HTTPS (TransportBinding) and UsernameToken is used as a "SupportingToken".
We have to configure a new policy which supports the TransportBinding but doesn't enforce a UsernameToken (SupportingToken assertion is missing). The new policy is added to the wsdl file ws-trust-1.4-service.wsdl
.
Right now, everybody who gets into the possession of a signed SAML token could request a new security token from the STS. This risk is mitigated using HTTPS (You could authenticate the requestor also who requests a token on behalf of someone else).
The new endpoint is configured in WEB-INF/cxf-transport.xml
as illustrated here:
<jaxws:endpoint id="transportSTS2" implementor="#transportSTSProviderBean" address="/STSServiceTransport" wsdlLocation="/WEB-INF/wsdl/ws-trust-1.4-service.wsdl" xmlns:ns1="http://docs.oasis-open.org/ws-sx/ws-trust/200512/" serviceName="ns1:SecurityTokenService" endpointName="ns1:Transport_Port"> </jaxws:endpoint>You can see all updates here.
Protect the Web Services provider...
The Web Service provider is a new component. You can find the sources of this example here. It supports a basic Web Services operation called "greetMe" with no input parameters but one output parameter. The response contains the authenticated user id which is retrieved from the JAX-WS WebServiceContext. In this use case, the authenticated user id must be the same as the one used by the browser user when login to the Web Application.
The following code snippet shows the implementation of the operation greetMe:
@Resource WebServiceContext context = null; public String greetMe() { LOG.info("Executing operation greetMe"); System.out.println("Executing operation greetMe"); if (context == null) { return "Unknown user"; } else { Principal p = context.getUserPrincipal(); if (p == null) { return "Principal null"; } return p.getName(); } }
The WSDL of the service provider contains a WS-SecurityPolicy which defines that:
- HTTPS must be used
- a SAML token must be sent issued by an STS
- the SAML token is a SupportingToken
<sp:TransportBinding xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"> <wsp:Policy> <sp:TransportToken> <wsp:Policy> <sp:HttpsToken RequireClientCertificate="false" /> </wsp:Policy> </sp:TransportToken> ... </wsp:Policy> </sp:TransportBinding> <sp:SupportingTokens xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"> <wsp:Policy> <sp:IssuedToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient"> <sp:RequestSecurityTokenTemplate> <t:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0</t:TokenType> <t:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</t:KeyType> </sp:RequestSecurityTokenTemplate> ... </sp:IssuedToken> </wsp:Policy> </sp:SupportingTokens>
The service provider trusts an STS which means that it accepts every SAML token which has been signed/issued by its trusted STS. This trust is established based on the certificate (stsstore.jks
) which must be configured in the service provider in the JAX-WS property ws-security.signature.properties
.
<jaxws:endpoint id="GreeterService" implementor="org.apache.cxf.fediz.examples.service.GreeterImpl" wsdlLocation="WEB-INF/wsdl/hello_world.wsdl" serviceName="svc:GreeterService" xmlns:svc="http://apache.org/hello_world_soap_http" address="/GreeterService"> <jaxws:properties> <entry key="ws-security.signature.properties" value="stsKeystore.properties" /> </jaxws:properties> </jaxws:endpoint>
You must ensure to add the Maven dependencies for security and policy because the policy engine is not initialized otherwise.
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-ws-policy</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-ws-security</artifactId> <version>${cxf.version}</version> </dependency>
The demo is prepared to deploy the Web Service to Tomcat instance tomcat-svc using Maven.
You don't have to change the tomcat URL if you have set up a Tomcat instance with HTTP port 10080 and HTTPS port 10443. Otherwise you must update the tomcat plugin in the POM:
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>tomcat-maven-plugin</artifactId> <version>1.1</version> <configuration> <server>myTomcat</server> <url>http://localhost:10080/manager</url> <path>/${project.build.finalName}</path> </configuration> </plugin>
Add the server with username and password to your Maven settings.xml
Ensure that this user has the role "manager-script" in Tomcat as described here
Run...
mvn clean install tomcat:redeployI recommend to use redeploy as deploy works the first time only
You can download the published WSDL with the following url:
https://localhost:10443/fedizservice/GreeterService?wsdl
Integrate the Web SSO and Web Services stack...
The Web Application fediz-tomcat-example
has been extended and slightly changed. You can find the new version here. A key change is the URL used to access the FederationServlet.
old: https://localhost:8443/fedizhelloworld/secureservlet/fed
new: https://localhost:8443/fedizhelloworld/secure/fedservlet
A simple JSP page has been added which shows the authenticated user and provides a button to call the Web Service. The Web Services is protected by the WS-SecurityPolicy which enforces a SAML token issued by its trusted STS. If the call was successfull a similar page is shown as for the federation servlet but it shows the response of the Web Services provider. The response contains the authenticated username which must be the same as the one for the authenticated user in the Web Application.
The following code snippet shows the code used to call the service provider securely. The JAX-WS proxy is instantiated by Spring and thus just read from the ApplicationContext.
Greeter service = (Greeter)ApplicationContextProvider.getContext().getBean("HelloServiceClient"); String reply = service.greetMe(); out.println("<br><b>Greeter Service Response: " + reply + "</b><p>");There is no security related code required.
The spring configuration beans.xml
contains the bean configuration for the service consumer:
<jaxws:client id="HelloServiceClient" serviceName="svc:GreeterService" xmlns:svc="http://apache.org/hello_world_soap_http" serviceClass="org.apache.hello_world_soap_http.Greeter" address="https://localhost:10443/fedizservice/GreeterService" wsdlLocation="WEB-INF/wsdl/hello_world.wsdl"> <jaxws:properties> <entry key="ws-security.sts.client"> <bean class="org.apache.cxf.ws.security.trust.STSClient"> <constructor-arg ref="cxf" /> <property name="wsdlLocation" value="https://localhost:9443/fedizidpsts/STSServiceTransport?wsdl" /> <property name="serviceName" value="{http://docs.oasis-open.org/ws-sx/ws-trust/200512/}SecurityTokenService" /> <property name="endpointName" value="{http://docs.oasis-open.org/ws-sx/ws-trust/200512/}Transport_Port" /> <property name="onBehalfOf" ref="delegationCallbackHandler" /> <property name="enableAppliesTo" value="true" /> </bean> </entry> <entry key="ws-security.cache.issued.token.in.endpoint" value="false" /> </jaxws:properties> </jaxws:client> <bean id="delegationCallbackHandler" class="org.apache.cxf.fediz.example.ThreadLocalCallbackHandler" />
The Web Application must have access to the WSDL which includes the WS-SecurityPolicy assertion. Either you have deployed this WSDL within your Web Application (see configuration wsdlLocation
attribute) or you download it from the service provider at runtime (see configuration for wsdlLocation
of the ws-security.sts.client
). The SecurityPolicy contains an IssuedToken assertion which indicates CXF to call the STS to request a token. The STS configuration is shown above where the STSClient bean is configured at entry ws-security.sts.client
.
The information in the SecurityPolicy element RequestSecurityTokenTemplate
are added to the SecondaryParameters
element in the request to the STS. The service consumer doesn't care about its content. Our example looks like this:
<sp:RequestSecurityTokenTemplate> <t:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0</t:TokenType> <t:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</t:KeyType> </sp:RequestSecurityTokenTemplate>
You can find more information about the correlation of STS parameters and SAML tokens here.
If you have deployed the service provider to a Tomcat instance with another port than 10443 you must update the attribute address
of the bean jaxws:client
.
The key thing to bridge Web SSO and Web Services stack CXF is the property onbehalfof
in the STSClient bean configuration. This property references a Java CallbackHandler implementation which provides the on-behalf-of token as a DOM element. More information about the CallbackHandler for onbehalfof can be found here.
CXF ships some out-of-the-box CallbackHandler implementations. This demo provides a custom CallbackHandler implementation called ThreadLocalCallbackHandler
which provides the SAML token which has been issued by the IDP/STS during the Web login. The following additional classes are required:
- SecurityTokenThreadLocal
This is the thread local which contains the SAML token as a DOM element. The
ThreadLocalCallbackHandler
gets the SAML token from this class. - FederationFilter
This servlet filter reads the SAML token from the session and adds it to SecurityTokenThreadLocal and cleans it after request processing finished
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-ws-policy</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-ws-security</artifactId> <version>${cxf.version}</version> </dependency>
You can deploy this demo with Maven to Tomcat instance tomcat-rp in the same way as for the service provider described above.
You got a bunch of information in the last two blogs and I'd say a great example to illustrate how to SSO enable your Web Applications and Web Services with only a few open source components.
Update: This example is part of the Fediz distribution.