Java tutorial
/* * Copyright 2010 Vladimir Schaefer * * 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.saml.websso; import org.apache.commons.httpclient.HostConfiguration; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.protocol.Protocol; import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; import org.opensaml.common.SAMLException; import org.opensaml.common.xml.SAMLConstants; import org.opensaml.saml2.metadata.IDPSSODescriptor; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.security.MetadataCriteria; import org.opensaml.ws.message.decoder.MessageDecodingException; import org.opensaml.ws.message.encoder.MessageEncodingException; import org.opensaml.ws.soap.client.http.TLSProtocolSocketFactory; import org.opensaml.ws.transport.http.HttpClientInTransport; import org.opensaml.ws.transport.http.HttpClientOutTransport; import org.opensaml.xml.security.CriteriaSet; import org.opensaml.xml.security.credential.UsageType; import org.opensaml.xml.security.criteria.EntityIDCriteria; import org.opensaml.xml.security.criteria.UsageCriteria; import org.springframework.security.saml.context.SAMLMessageContext; import org.springframework.security.saml.trust.X509KeyManager; import org.springframework.security.saml.trust.X509TrustManager; import javax.net.ssl.HostnameVerifier; import java.io.IOException; /** * Implementation of the artifact resolution protocol which uses Apache HTTPClient for SOAP binding transport. */ public class ArtifactResolutionProfileImpl extends ArtifactResolutionProfileBase { /** * Client used to perform HTTP calls for artifact resolution. */ private HttpClient httpClient; /** * @param httpClient client used to send SOAP messages */ public ArtifactResolutionProfileImpl(HttpClient httpClient) { this.httpClient = httpClient; } /** * Uses HTTPClient to send and retrieve ArtifactMessages. * * @param endpointURI URI incoming artifactMessage is addressed to * @param context context with filled communicationProfileId, outboundMessage, outboundSAMLMessage, peerEntityEndpoint, peerEntityId, peerEntityMetadata, peerEntityRole, peerEntityRoleMetadata * @throws SAMLException error processing artifact messages * @throws MessageEncodingException error sending artifactRequest * @throws MessageDecodingException error retrieving artifactResponse * @throws MetadataProviderException error resolving metadata * @throws org.opensaml.xml.security.SecurityException * invalid message signature */ protected void getArtifactResponse(String endpointURI, SAMLMessageContext context) throws SAMLException, MessageEncodingException, MessageDecodingException, MetadataProviderException, org.opensaml.xml.security.SecurityException { PostMethod postMethod = null; try { URI uri = new URI(context.getPeerEntityEndpoint().getLocation(), true, "UTF-8"); postMethod = new PostMethod(); postMethod.setPath(uri.getPath()); HostConfiguration hc = getHostConfiguration(uri, context); HttpClientOutTransport clientOutTransport = new HttpClientOutTransport(postMethod); HttpClientInTransport clientInTransport = new HttpClientInTransport(postMethod, endpointURI); context.setInboundMessageTransport(clientInTransport); context.setOutboundMessageTransport(clientOutTransport); // Send artifact retrieve message boolean signMessage = context.getPeerExtendedMetadata().isRequireArtifactResolveSigned(); processor.sendMessage(context, signMessage, SAMLConstants.SAML2_SOAP11_BINDING_URI); log.debug("Sending ArtifactResolution message to {}", uri); int responseCode = httpClient.executeMethod(hc, postMethod); if (responseCode != 200) { String responseBody = postMethod.getResponseBodyAsString(); throw new MessageDecodingException( "Problem communicating with Artifact Resolution service, received response " + responseCode + ", body " + responseBody); } // Decode artifact response message. processor.retrieveMessage(context, SAMLConstants.SAML2_SOAP11_BINDING_URI); } catch (IOException e) { throw new MessageDecodingException("Error when sending request to artifact resolution service.", e); } finally { if (postMethod != null) { postMethod.releaseConnection(); } } } /** * Method is expected to determine hostConfiguration used to send request to the server by back-channel. Configuration * should contain URI of the host and used protocol including all security settings. * <p> * Default implementation uses either default http protocol for non-SSL requests or constructs a separate * TrustManager using trust engine specified in the SAMLMessageContext - based either on MetaIOP (certificates * obtained from Metadata and ExtendedMetadata are trusted) or PKIX (certificates from metadata and ExtendedMetadata * including specified trust anchors are trusted and verified using PKIX). * <p> * Used trust engine can be customized as part of the SAMLContextProvider used to process this request. * <p> * Default values for the HostConfiguration are cloned from the HTTPClient set in this instance, when there are * no defaults available a new object is created. * * @param uri uri the request should be sent to * @param context context including the peer address * @return host configuration * @throws MessageEncodingException in case peer URI can't be parsed */ protected HostConfiguration getHostConfiguration(URI uri, SAMLMessageContext context) throws MessageEncodingException { try { HostConfiguration hc = httpClient.getHostConfiguration(); if (hc != null) { // Clone configuration from the HTTP Client object hc = new HostConfiguration(hc); } else { // Create brand new configuration when there are no defaults hc = new HostConfiguration(); } if (uri.getScheme().equalsIgnoreCase("http")) { log.debug("Using HTTP configuration"); hc.setHost(uri); } else { log.debug("Using HTTPS configuration"); CriteriaSet criteriaSet = new CriteriaSet(); criteriaSet.add(new EntityIDCriteria(context.getPeerEntityId())); criteriaSet .add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS)); criteriaSet.add(new UsageCriteria(UsageType.UNSPECIFIED)); X509TrustManager trustManager = new X509TrustManager(criteriaSet, context.getLocalSSLTrustEngine()); X509KeyManager manager = new X509KeyManager(context.getLocalSSLCredential()); HostnameVerifier hostnameVerifier = context.getLocalSSLHostnameVerifier(); ProtocolSocketFactory socketFactory = getSSLSocketFactory(context, manager, trustManager, hostnameVerifier); Protocol protocol = new Protocol("https", socketFactory, 443); hc.setHost(uri.getHost(), uri.getPort(), protocol); } return hc; } catch (URIException e) { throw new MessageEncodingException("Error parsing remote location URI", e); } } /** * Method returns SecureProtocolSocketFactory used to connect to create SSL connections for artifact resolution. * By default we create instance of org.opensaml.ws.soap.client.http.TLSProtocolSocketFactory. * * @param context current SAML context * @param manager keys used for client authentication * @param trustManager trust manager for server verification * @param hostnameVerifier verifier for server hostname, or null * @return socket factory */ protected SecureProtocolSocketFactory getSSLSocketFactory(SAMLMessageContext context, X509KeyManager manager, X509TrustManager trustManager, HostnameVerifier hostnameVerifier) { if (isHostnameVerificationSupported()) { return new TLSProtocolSocketFactory(manager, trustManager, hostnameVerifier); } else { return new TLSProtocolSocketFactory(manager, trustManager); } } /** * Check for the latest OpenSAML library. Support for HostnameVerification was added in openws-1.5.1 and * customers might use previous versions of OpenSAML. * * @return true when OpenSAML library support hostname verification */ protected boolean isHostnameVerificationSupported() { try { TLSProtocolSocketFactory.class.getConstructor(javax.net.ssl.X509KeyManager.class, javax.net.ssl.X509TrustManager.class, javax.net.ssl.HostnameVerifier.class); return true; } catch (NoSuchMethodException e) { log.warn("HostnameVerification is not supported, update your OpenSAML libraries"); return false; } } }