如何使用Java正确准备“HTTP重定向绑定”SAML请求?

时间:2014-07-07 17:02:19

标签: java single-sign-on saml-2.0 shibboleth

有一些测试资源,例如http://sometestresource.com我在http://sometestresource.com/Login上发出了授权请求,我重定向到了idP提供商:http://someidpprovoder.com/idp/authn/CommonLogin

Sniffer检测到重定向到https://someidpprovoder.com/idp/profile/SAML2/Redirect/SSO

SAMLRequest=fZJda...D&RelayState=https%3A%2F%2Fwww.sometestresource.com%2Fcms%2Fextentions%2Fsimplesaml%2Fwww%2Fmodule.php%2Fcore%2Fauthenticate.php%3Fas%3D100000za-sp
&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1
&Signature=dAx0M...D

好的,我写了一个简单的servlet应用程序,我希望得到相同的功能 - Web Browser SSO Profile

我的ServiceProvider servlet:

@WebServlet(urlPatterns={"/ServiceProvider"},
            initParams={ @WebInitParam(name="Issuer", value="some.issuer"),
                         @WebInitParam(name="IdpUrl", value="https://someidpprovoder.com/idp/profile/SAML2/Redirect/SSO"),
                         @WebInitParam(name="ConsumerUrl", value="http://localhost:8080/com.secure.isusia-1.0-SNAPSHOT/ServiceProvider")
            } )
public class ServiceProvider  extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private SamlConsumerManager consumer;

    public void init(ServletConfig config) throws ServletException {
        try {
            consumer = new SamlConsumerManager(config);
        } catch (ConfigurationException e) {
            throw new ServletException("Errow while configuring SAMLConsumerManager", e);
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException,
            IOException {
        doPost(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String responseMessage = request.getParameter("SAMLResponse");
        if (responseMessage != null) { 
            // response from the identity provider
            if (result == null) {
                // lets logout the user
            } else if (result.size() == 1) {
                 //
                 // No user attributes are returned, so just goto the default
                 //home page.
                 //
            } else if (result.size() > 1) {
                 // We have received attributes, so lets show them in the
                 // attribute home page.
                 //

            } else {
                // something wrong, re-login
            }
        } else { 
            /* time to create the authentication request or logout request */
        }
    }
}

我的SamlConsumerManager:

public class SamlConsumerManager {
    private String consumerUrl;
    private String authReqRandomId;
    private String relayState;
    private String issuerId;
    private String idpUrl;

    private char[] password = "...".toCharArray();
    private String alias = "...";

    private String sigAlg = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";

    private Signature signature;
    private BasicX509Credential credential;
    private Element authDOM;

    public SamlConsumerManager(ServletConfig servletConfig) throws ConfigurationException {
        authReqRandomId = Integer.toHexString(new Double(Math.random()).intValue());        
        consumerUrl = servletConfig.getInitParameter("ConsumerUrl");
        idpUrl = servletConfig.getInitParameter("IdpUrl");
        issuerId = servletConfig.getInitParameter("Issuer");

        DefaultBootstrap.bootstrap();
    }

    public String buildRequestMessage(HttpServletRequest request) {

        RequestAbstractType requestMessage = null;

        if (request.getParameter("logout") == null) {
            // time to build the authentication request message
        } else { 
            // ok, user needs to be single logged out
        }

        String encodedRequestMessage = encodeRequestMessage(requestMessage);

        /* SAML2 Authentication Request is appended to IP's URL */
        return idpUrl + "?SAMLRequest=" + encodedRequestMessage + "&SigAlg=" + sigAlg;
    }

    private LogoutRequest buildLogoutRequest(String user) {
        LogoutRequest logoutReq = new LogoutRequestBuilder().buildObject();
        logoutReq.setID(Util.createID());

        DateTime issueInstant = new DateTime();
        logoutReq.setIssueInstant(issueInstant);
        logoutReq.setNotOnOrAfter(new DateTime(issueInstant.getMillis() + 5 * 60 * 1000));

        IssuerBuilder issuerBuilder = new IssuerBuilder();
        Issuer issuer = issuerBuilder.buildObject();
        issuer.setValue(issuerId);
        logoutReq.setIssuer(issuer);

        NameID nameId = new NameIDBuilder().buildObject();
        nameId.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:entity");
        nameId.setValue(user);
        logoutReq.setNameID(nameId);

        SessionIndex sessionIndex = new SessionIndexBuilder().buildObject();
        sessionIndex.setSessionIndex(UIDGenerator.generateUID());
        logoutReq.getSessionIndexes().add(sessionIndex);

        logoutReq.setReason("Single Logout");

        return logoutReq;
    }

    private AuthnRequest buildAuthnRequestObject() {
        /* Building Issuer object */
        IssuerBuilder issuerBuilder = new IssuerBuilder();
        Issuer issuer = issuerBuilder.buildObject("urn:oasis:names:tc:SAML:2.0:assertion", "Issuer", "samlp");
        issuer.setValue(issuerId);

        /* NameIDPolicy */
        NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder();
        NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject();
        nameIdPolicy.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent");
        nameIdPolicy.setSPNameQualifier("Isser");
        nameIdPolicy.setAllowCreate(new Boolean(true));

        /* AuthnContextClass */
        AuthnContextClassRefBuilder authnContextClassRefBuilder = new AuthnContextClassRefBuilder();
        AuthnContextClassRef authnContextClassRef = authnContextClassRefBuilder.buildObject(
                        "urn:oasis:names:tc:SAML:2.0:assertion",
                        "AuthnContextClassRef",
                        "saml");

        authnContextClassRef.setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");

        /* AuthnContex */
        RequestedAuthnContextBuilder requestedAuthnContextBuilder =
                new RequestedAuthnContextBuilder();
        RequestedAuthnContext requestedAuthnContext = requestedAuthnContextBuilder.buildObject();
        requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
        requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);

        DateTime issueInstant = new DateTime();

        /* Creation of AuthRequestObject */
        AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();
        AuthnRequest authRequest =
                authRequestBuilder.buildObject("urn:oasis:names:tc:SAML:2.0:protocol",
                        "AuthnRequest", "samlp");
        authRequest.setForceAuthn(new Boolean(false));
        authRequest.setIsPassive(new Boolean(false));
        authRequest.setIssueInstant(issueInstant);
        authRequest.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");
        authRequest.setAssertionConsumerServiceURL(consumerUrl);
        authRequest.setIssuer(issuer);
        authRequest.setNameIDPolicy(nameIdPolicy);
        authRequest.setRequestedAuthnContext(requestedAuthnContext);
        authRequest.setID(authReqRandomId);
        authRequest.setVersion(SAMLVersion.VERSION_20);

        KeyStore  keyStore = KeyStore.getInstance(KeyStore.getDefaultType());

        FileInputStream fileInputStream = new FileInputStream(new File("....jks"));        

        keyStore.load(fileInputStream, password);
        fileInputStream.close();

        KeyStore.PrivateKeyEntry privateKeyEntry = null;
        privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, new KeyStore.PasswordProtection(password));

        PrivateKey privateKey = privateKeyEntry.getPrivateKey();

        X509Certificate certificate = (X509Certificate) privateKeyEntry.getCertificate();

        credential = new BasicX509Credential();
        credential.setEntityCertificate(certificate);
        credential.setPrivateKey(privateKey);

        try {
            DefaultBootstrap.bootstrap();
        } catch (ConfigurationException e) {
            e.printStackTrace();
        }

        signature = (Signature) org.opensaml.xml.Configuration.getBuilderFactory().getBuilder(org.opensaml.xml.signature.Signature.DEFAULT_ELEMENT_NAME)
                .buildObject(org.opensaml.xml.signature.Signature.DEFAULT_ELEMENT_NAME);

        signature.setSigningCredential(credential);
        signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1);
        signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);

        authRequest.setSignature(signature);

        return authRequest;
    }

    private String encodeRequestMessage(RequestAbstractType requestMessage)
            throws MarshallingException,
            IOException {

        Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(requestMessage);
        Element authDOM = marshaller.marshall(requestMessage);

        Deflater deflater = new Deflater(Deflater.DEFLATED, true);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        DeflaterOutputStream deflaterOutputStream =
                new DeflaterOutputStream(byteArrayOutputStream,
                        deflater);

        StringWriter rspWrt = new StringWriter();
        XMLHelper.writeNode(authDOM, rspWrt);
        deflaterOutputStream.write(rspWrt.toString().getBytes());
        deflaterOutputStream.close();

        /* Encoding the compressed message */
        String encodedRequestMessage =
                Base64.encodeBytes(byteArrayOutputStream.toByteArray(),
                        Base64.DONT_BREAK_LINES);
        return URLEncoder.encode(encodedRequestMessage, "UTF-8").trim();
    }

}

然而,我的idP不是登录页面,而是一个错误页面。

我做错了什么?我想知道如何正确准备授权请求。例如,我不明白如何在URL中添加签名:

&Signature=dAx0M...D

我非常感谢你的帮助。谢谢大家。


更新

  • 我正在使用SP发起的Web SSO。
  • 基于Shibboleth的远程idP(在客户方面,关于它我什么都不知道。只是它是基于Shibboleth编写的。)

1 个答案:

答案 0 :(得分:1)

您需要签名,

buildAuthnRequestObject

下的authRequest.setSignature(signature);

添加

Configuration.getMarshallerFactory().getMarshaller(authRequest).marshall(authRequest);`
    if(signature!=null) {
        Signer.signObject(signature);
    }

Signerorg.opensaml.xml.signature.Signer

**更新**

使用SAMLMessageContextHTTPRedirectDeflateEncoder

发送重定向
public void doAuthenticationRedirect (HttpServletResponse response, final HttpSession currentSessiond) throws Exception {  

    /** Generate your authnrequest **/
    AuthnRequest authnRequest = generateAuthnRequest();  

    /** Create an adapter from tomcat response servlet **/
    HttpServletResponseAdapter responseAdapter = new HttpServletResponseAdapter(response, true); 

    /** 302 status code **/
    responseAdapter.setStatusCode(HttpServletResponse.SC_MOVED_TEMPORARILY);

    /** Build blank context **/
    SAMLMessageContext<?, AuthnRequest, ?> context =  SAMLUtility.makeSamlMessageContext();

    /** Set entity end point**/
    context.setPeerEntityEndpoint(endpointObject);  

    context.setOutboundSAMLMessage(authnRequest);  

   /** Sign this request **/
    context.setOutboundSAMLMessageSigningCredential(getSigningCredential());  
    context.setOutboundMessageTransport(responseAdapter);
     /** Run encoder **/
    try {  
        runEncoder(new HTTPRedirectDeflateEncoder(), context);
    } catch (Throwable t) {  
        Log.error("Error endcoding AuthnRequest: ",t);
    }  
}  


 /** 
  * Encode our Context and send it
  * 
  * @param encoder
  * @param context
  * @throws IOException
  * @throws MessageEncodingException
  */
  private void runEncoder(MessageEncoder encoder, MessageContext context)      throws IOException, MessageEncodingException {
      encoder.encode(context);
  }

如果你需要,可以makeSamlMessageContext()

 public static <TI extends SAMLObject, TO extends SAMLObject, TN extends SAMLObject>
    SAMLMessageContext<TI, TO, TN> makeSamlMessageContext() {
      return new BasicSAMLMessageContext<TI, TO, TN>();
 }