package no.steras.opensamlbook.sp; import com.google.common.collect.Lists; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.httpclient.HttpClientBuilder; import net.shibboleth.utilities.java.support.xml.BasicParserPool; import no.steras.opensamlbook.OpenSAMLUtils; import no.steras.opensamlbook.idp.IDPConstants; import no.steras.opensamlbook.idp.IDPCredentials; import org.apache.xml.security.algorithms.MessageDigestAlgorithm; import org.joda.time.DateTime; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; import org.opensaml.core.xml.io.MarshallingException; import org.opensaml.core.xml.schema.XSString; import org.opensaml.messaging.context.InOutOperationContext; import org.opensaml.messaging.context.MessageContext; import org.opensaml.messaging.encoder.MessageEncodingException; import org.opensaml.messaging.handler.MessageHandler; import org.opensaml.messaging.handler.MessageHandlerException; import org.opensaml.messaging.handler.impl.BasicMessageHandlerChain; import org.opensaml.messaging.pipeline.httpclient.BasicHttpClientMessagePipeline; import org.opensaml.messaging.pipeline.httpclient.HttpClientMessagePipeline; import org.opensaml.messaging.pipeline.httpclient.HttpClientMessagePipelineFactory; import org.opensaml.profile.context.ProfileRequestContext; import org.opensaml.saml.common.SAMLObject; import org.opensaml.saml.common.binding.impl.SAMLSOAPDecoderBodyHandler; import org.opensaml.saml.common.binding.security.impl.MessageLifetimeSecurityHandler; import org.opensaml.saml.common.binding.security.impl.ReceivedEndpointSecurityHandler; import org.opensaml.saml.common.binding.security.impl.SAMLOutboundProtocolMessageSigningHandler; import org.opensaml.saml.common.binding.security.impl.SAMLProtocolMessageXMLSignatureSecurityHandler; import org.opensaml.saml.common.messaging.context.SAMLEndpointContext; import org.opensaml.saml.common.messaging.context.SAMLMessageInfoContext; import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext; import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.saml2.binding.decoding.impl.HttpClientResponseSOAP11Decoder; import org.opensaml.saml.saml2.binding.encoding.impl.HTTPSOAP11Encoder; import org.opensaml.saml.saml2.binding.encoding.impl.HttpClientRequestSOAP11Encoder; import org.opensaml.saml.saml2.core.*; import org.opensaml.saml.saml2.encryption.Decrypter; import org.opensaml.saml.saml2.metadata.ArtifactResolutionService; import org.opensaml.saml.saml2.metadata.Endpoint; import org.opensaml.saml.saml2.metadata.SingleSignOnService; import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; import org.opensaml.soap.client.http.AbstractPipelineHttpSOAPClient; import org.opensaml.soap.client.http.HttpSOAPClient; import org.opensaml.soap.client.http.PipelineFactoryHttpSOAPClient; import org.opensaml.soap.common.SOAPException; import org.opensaml.soap.messaging.context.SOAP11Context; import org.opensaml.soap.soap11.Envelope; import org.opensaml.soap.soap11.decoder.http.impl.EnvelopeBodyHandler; import org.opensaml.xmlsec.SignatureSigningParameters; import org.opensaml.xmlsec.context.SecurityParametersContext; import org.opensaml.xmlsec.encryption.support.DecryptionException; import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver; import org.opensaml.xmlsec.keyinfo.impl.StaticKeyInfoCredentialResolver; import org.opensaml.xmlsec.signature.Signature; import org.opensaml.xmlsec.signature.support.SignatureConstants; import org.opensaml.xmlsec.signature.support.SignatureException; import org.opensaml.xmlsec.signature.support.SignatureValidator; import org.opensaml.xmlsec.signature.support.Signer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.bind.ValidationException; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Created by Privat on 4/6/14. */ public class ConsumerServlet extends HttpServlet { private static Logger logger = LoggerFactory.getLogger(ConsumerServlet.class); @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { logger.info("Artifact received"); Artifact artifact = buildArtifactFromRequest(req); logger.info("Artifact: " + artifact.getArtifact()); //开始创建ArtifactResolve; ArtifactResolve artifactResolve = buildArtifactResolve(artifact); logger.info("Sending ArtifactResolve"); logger.info("ArtifactResolve: "); OpenSAMLUtils.logSAMLObject(artifactResolve); // 发送ArtifactResolve // SOAP消息发送之后,会同步等待Response返回或者超时。 // 当Response返回时,SAML消息便可或得到: ArtifactResponse artifactResponse = sendAndReceiveArtifactResolve(artifactResolve, resp); logger.info("ArtifactResponse received"); logger.info("ArtifactResponse: "); OpenSAMLUtils.logSAMLObject(artifactResponse); //验证目的地址和有效期; validateDestinationAndLifetime(artifactResponse, req); EncryptedAssertion encryptedAssertion = getEncryptedAssertion(artifactResponse); //获得解密后的断言; Assertion assertion = decryptAssertion(encryptedAssertion); verifyAssertionSignature(assertion); logger.info("Decrypted Assertion: "); OpenSAMLUtils.logSAMLObject(assertion); logAssertionAttributes(assertion); logAuthenticationInstant(assertion); logAuthenticationMethod(assertion); setAuthenticatedSession(req); redirectToGotoURL(req, resp); } private void validateDestinationAndLifetime(ArtifactResponse artifactResponse, HttpServletRequest request) { MessageContext context = new MessageContext<ArtifactResponse>(); context.setMessage(artifactResponse); SAMLMessageInfoContext messageInfoContext = context.getSubcontext(SAMLMessageInfoContext.class, true); messageInfoContext.setMessageIssueInstant(artifactResponse.getIssueInstant()); //生命周期验证,要求SAMLMessageInfoContext包含issue time; MessageLifetimeSecurityHandler lifetimeSecurityHandler = new MessageLifetimeSecurityHandler(); lifetimeSecurityHandler.setClockSkew(1000); lifetimeSecurityHandler.setMessageLifetime(2000); lifetimeSecurityHandler.setRequiredRule(true); //验证消息目的地址,要求base message context包含SAML消息,必需的信息可以从中提取出来 ReceivedEndpointSecurityHandler receivedEndpointSecurityHandler = new ReceivedEndpointSecurityHandler(); receivedEndpointSecurityHandler.setHttpServletRequest(request); List handlers = new ArrayList<MessageHandler>(); handlers.add(lifetimeSecurityHandler); handlers.add(receivedEndpointSecurityHandler); BasicMessageHandlerChain<ArtifactResponse> handlerChain = new BasicMessageHandlerChain<ArtifactResponse>(); handlerChain.setHandlers(handlers); try { handlerChain.initialize(); handlerChain.doInvoke(context); } catch (ComponentInitializationException e) { throw new RuntimeException(e); } catch (MessageHandlerException e) { throw new RuntimeException(e); } } /** * 解密断言 * @param encryptedAssertion 加密的断言 */ private Assertion decryptAssertion(EncryptedAssertion encryptedAssertion) { StaticKeyInfoCredentialResolver keyInfoCredentialResolver = new StaticKeyInfoCredentialResolver(SPCredentials.getCredential()); Decrypter decrypter = new Decrypter(null, keyInfoCredentialResolver, new InlineEncryptedKeyResolver()); decrypter.setRootInNewDocument(true); try { return decrypter.decrypt(encryptedAssertion); } catch (DecryptionException e) { throw new RuntimeException(e); } } private void verifyAssertionSignature(Assertion assertion) { if (!assertion.isSigned()) { throw new RuntimeException("The SAML Assertion was not signed"); } try { SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); profileValidator.validate(assertion.getSignature()); SignatureValidator.validate(assertion.getSignature(), IDPCredentials.getCredential()); logger.info("SAML Assertion signature verified"); } catch (SignatureException e) { e.printStackTrace(); } } private void setAuthenticatedSession(HttpServletRequest req) { req.getSession().setAttribute(SPConstants.AUTHENTICATED_SESSION_ATTRIBUTE, true); } private void redirectToGotoURL(HttpServletRequest req, HttpServletResponse resp) { String gotoURL = (String)req.getSession().getAttribute(SPConstants.GOTO_URL_SESSION_ATTRIBUTE); logger.info("Redirecting to requested URL: " + gotoURL); try { resp.sendRedirect(gotoURL); } catch (IOException e) { throw new RuntimeException(e); } } private void logAuthenticationMethod(Assertion assertion) { logger.info("Authentication method: " + assertion.getAuthnStatements().get(0) .getAuthnContext().getAuthnContextClassRef().getAuthnContextClassRef()); } private void logAuthenticationInstant(Assertion assertion) { logger.info("Authentication instant: " + assertion.getAuthnStatements().get(0).getAuthnInstant()); } private void logAssertionAttributes(Assertion assertion) { for (Attribute attribute : assertion.getAttributeStatements().get(0).getAttributes()) { logger.info("Attribute name: " + attribute.getName()); for (XMLObject attributeValue : attribute.getAttributeValues()) { logger.info("Attribute value: " + ((XSString) attributeValue).getValue()); } } } private EncryptedAssertion getEncryptedAssertion(ArtifactResponse artifactResponse) { Response response = (Response)artifactResponse.getMessage(); return response.getEncryptedAssertions().get(0); } /** * 使用SOAP协议发送 ArtifactResolve */ private ArtifactResponse sendAndReceiveArtifactResolve(final ArtifactResolve artifactResolve, HttpServletResponse servletResponse) { try { MessageContext<ArtifactResolve> contextout = new MessageContext<ArtifactResolve>(); contextout.setMessage(artifactResolve); //加入数据签名以增强安全性 SignatureSigningParameters signatureSigningParameters = new SignatureSigningParameters(); signatureSigningParameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); signatureSigningParameters.setSigningCredential(SPCredentials.getCredential()); signatureSigningParameters.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); SecurityParametersContext securityParametersContext = contextout.getSubcontext(SecurityParametersContext.class, true); if (securityParametersContext != null) { securityParametersContext.setSignatureSigningParameters(signatureSigningParameters); } //创建InOutOperationContext来处理输入输出的信息 InOutOperationContext<ArtifactResponse, ArtifactResolve> context = new ProfileRequestContext<ArtifactResponse, ArtifactResolve>(); context.setOutboundMessageContext(contextout); //为了能发送SOAP消息,还需要设置SOAP Client。 // 这个Client将会调用消息的处理器,编码器以及解码等来传送消息 AbstractPipelineHttpSOAPClient<SAMLObject, SAMLObject> soapClient = new AbstractPipelineHttpSOAPClient<SAMLObject, SAMLObject>() { @Nonnull protected HttpClientMessagePipeline newPipeline() throws SOAPException { //创建输入输出用的编码器和解码器 HttpClientRequestSOAP11Encoder encoder = new HttpClientRequestSOAP11Encoder(); HttpClientResponseSOAP11Decoder decoder = new HttpClientResponseSOAP11Decoder(); //创建管道 BasicHttpClientMessagePipeline pipeline = new BasicHttpClientMessagePipeline( encoder, decoder ); //为输出的内容签名 pipeline.setOutboundPayloadHandler(new SAMLOutboundProtocolMessageSigningHandler()); return pipeline; }}; // HTTP帮助SOAPClient编码和解码 HttpClientBuilder clientBuilder = new HttpClientBuilder(); soapClient.setHttpClient(clientBuilder.buildClient()); soapClient.send(IDPConstants.ARTIFACT_RESOLUTION_SERVICE, context); return context.getInboundMessageContext().getMessage(); } catch (SecurityException e) { throw new RuntimeException(e); } catch (ComponentInitializationException e) { throw new RuntimeException(e); } catch (MessageEncodingException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (Exception e) { throw new RuntimeException(e); } } /**SAML消息中有敏感信息 */ private Artifact buildArtifactFromRequest(final HttpServletRequest req) { Artifact artifact = OpenSAMLUtils.buildSAMLObject(Artifact.class); artifact.setArtifact(req.getParameter("SAMLart")); return artifact; } private ArtifactResolve buildArtifactResolve(final Artifact artifact) { ArtifactResolve artifactResolve = OpenSAMLUtils.buildSAMLObject(ArtifactResolve.class); //Issuer:发送方的身份表示,同AuthnRequest中的issuer; Issuer issuer = OpenSAMLUtils.buildSAMLObject(Issuer.class); issuer.setValue(SPConstants.SP_ENTITY_ID); artifactResolve.setIssuer(issuer); //Time of the Request artifactResolve.setIssueInstant(new DateTime()); //ID of the request: artifactResolve.setID(OpenSAMLUtils.generateSecureRandomId()); //destination URL artifactResolve.setDestination(IDPConstants.ARTIFACT_RESOLUTION_SERVICE); artifactResolve.setArtifact(artifact); return artifactResolve; } }