如何读取SAML编码的响应并提取名称ID

时间:2019-11-27 14:35:34

标签: saml

我已将实现的代码发布到这里。当我单击特定的URL Ex:(https://wwwdev-lom.app.ford.com/launchomatic/launch/view.jsp?chronicleId=0900cad780aaac86&docbase=edmsdev)时,我将初始化SAML servlet,但是当我单击对SAML进行身份验证时,该页面将重定向回ADFS页面。 ADFS页面已成功通过身份验证。我希望将页面重定向回我单击时使用的相同URL。该链接应该可以帮助我在ADFS进行身份验证后从浏览器下载内容。我是否应该通过JSP重新构建此URL,然后再转发如果是的话,如何从当前的servlet(下一个)调用JSP,在下面的哪里调用呢?

import com.documentum.com.DfClientX;
import com.ford.launchomatic.downloader.LaunchFile;
import com.documentum.fc.client.DfClient;
import com.documentum.fc.client.IDfClient;
import com.documentum.fc.client.IDfSession;
import com.documentum.fc.client.IDfSessionManager;
import com.documentum.fc.common.IDfLoginInfo;
import com.documentum.wc.env.jsp.DwJSPPageContext;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URLEncoder;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.X509EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.joda.time.DateTime;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.SAMLVersion;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.EncryptedAssertion;
import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.StatusMessage;
import org.opensaml.saml2.core.Subject;
import org.opensaml.saml2.core.SubjectConfirmation;
import org.opensaml.saml2.core.SubjectConfirmationData;
import org.opensaml.saml2.core.impl.AuthnRequestBuilder;
import org.opensaml.saml2.core.impl.IssuerBuilder;
import org.opensaml.xml.Configuration;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.io.Marshaller;
import org.opensaml.xml.io.Unmarshaller;
import org.opensaml.xml.io.UnmarshallerFactory;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.x509.BasicX509Credential;
import org.opensaml.xml.signature.Signature;
import org.opensaml.xml.signature.SignatureValidator;
import org.opensaml.xml.util.Base64;
import org.opensaml.xml.util.XMLHelper;
import org.opensaml.xml.validation.ValidationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class AuthenticateSAML extends HttpServlet
{
  private static final long serialVersionUID = 1L;
  String path = null;
  static PrintWriter log;
  Properties prop;

  public void init(ServletConfig servletConfig) throws ServletException {
    super.init(servletConfig);
    this.path = servletConfig.getServletContext().getRealPath(
        "/WEB-INF/classes/saml.properties");
  }



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



  public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
    InputStream inputStream = null;
    try {
      PrintWriter pw = response.getWriter();
      this.prop = new Properties();
      inputStream = new FileInputStream(this.path);
      this.prop.load(inputStream);
      inputStream.close();
      String logFile = this.prop.getProperty("logFile");
      createLog(logFile);
        Trace("==========START SAML SSO==========");
        Trace("Creating Authentication Request");
        String url = buildAuthenticationRequest(request,response);
        response.sendRedirect(url);
        String value = request.getParameter("SAMLResponse");
        if(validateResponse("xxxx",value))
            {
            Trace("SAML response validated successful");
            }
        String docbase = this.prop.getProperty("docbase");
        Boolean docbasesuccess = DFCAuthentication("xxxx", value, docbase);
        if(docbasesuccess)
        {
            Trace("docbase authentication successful");
        } 
        if (log != null) {
          log.flush();
          log.close();
        } 

    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      if (inputStream != null) {
        inputStream.close();
      }
    } 
  }

  public String buildAuthenticationRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
    DefaultBootstrap.bootstrap();
    String IDPurl = this.prop.getProperty("IDPurl");
    String ACSurl = request.getRequestURL().toString();
    String issuerURL = this.prop.getProperty("Issuer");

    IssuerBuilder issuerBuilder = new IssuerBuilder();
    Issuer issuer = issuerBuilder.buildObject("urn:oasis:names:tc:SAML:2.0:assertion", "Issuer", "saml");
    issuer.setValue(issuerURL);
    DateTime issueInstant = new DateTime();
    AuthnRequestBuilder authnRequestBuilder = new AuthnRequestBuilder();
    AuthnRequest authnRequest = authnRequestBuilder.buildObject();
    authnRequest.setForceAuthn(new Boolean(false));
    authnRequest.setIsPassive(new Boolean(false));
    authnRequest.setIssueInstant(issueInstant);
    authnRequest.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");
    authnRequest.setAssertionConsumerServiceURL(ACSurl);
    authnRequest.setIssuer(issuer);
    authnRequest.setID("AQ1");
    authnRequest.setVersion(SAMLVersion.VERSION_20);
    Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller((XMLObject)authnRequest);
    Element authDOM = marshaller.marshall((XMLObject)authnRequest);
    authDOM.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:space", "preserve");
    StringWriter rspWrt = new StringWriter();
    XMLHelper.writeNode(authDOM, rspWrt);
    String requestMessage = rspWrt.toString();
    Deflater deflater = new Deflater(8, true);
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater);
    deflaterOutputStream.write(requestMessage.getBytes());
    deflaterOutputStream.close();
    String encodedRequestMessage = Base64.encodeBytes(byteArrayOutputStream.toByteArray(), 8);
    String encodedAuthnRequest = URLEncoder.encode(encodedRequestMessage, "UTF-8");
    String url = String.valueOf(IDPurl) + "/?SAMLRequest=" + encodedAuthnRequest;
    return url;
  }

  static void Trace(String info) {
    if (log != null) {
      Date date = new Date();
      SimpleDateFormat dateFormat = new SimpleDateFormat(
          "yyyy-MM-dd HH:mm:ss.SSS");
      log.write(String.valueOf(dateFormat.format(date)) + " " + info);
      log.write(System.lineSeparator());
    } 
  }

  static void createLog(String fileName) {
    try {
      File logFile = new File(fileName);
      File logDir = new File(logFile.getParent());
      if (!logDir.exists())
        logDir.mkdir(); 
      FileWriter fw = new FileWriter(fileName, true);
      log = new PrintWriter(fw);
    } catch (IOException e) {
      e.printStackTrace();
    } 
  }

  public static ArrayList<String> listFilesForFolder(File folder) {
    ArrayList<String> certList = new ArrayList<>();
    int flag = 0;
    if (folder == null || !folder.exists()) {
      Trace("Cannot load certificates. Folder doesn't exist: " + 
          folder.getAbsolutePath());
      return null;
    } 
    Trace("Retrieving Certificates in folder: " + folder.getAbsolutePath()); byte b; int i; File[] arrayOfFile;
    for (i = (arrayOfFile = folder.listFiles()).length, b = 0; b < i; ) { File fileEntry = arrayOfFile[b];
      if (!fileEntry.isDirectory()) {


        String path = fileEntry.getAbsolutePath();
        String name = fileEntry.getName();
        int extStart = name.lastIndexOf(".");
        if (extStart != -1) {
          String ext = name.substring(extStart + 1);
          if (ext.equalsIgnoreCase("crt") || 
            ext.equalsIgnoreCase("cer") || 
            ext.equalsIgnoreCase("der")) {
            flag = 1;
            certList.add(path);
          } 
        } 
      }  b++; }

    if (flag == 0) {
      Trace("No certificates found...");
      return null;
    } 
    return certList;
  }


  public boolean isNullorEmpty(String value) { return !(value != null && value.trim() != ""); }



  public boolean validateResponse(String username, String responseMessage) throws Exception {
    InputStream inputStream = null;
    try {
      if (this.prop == null)
        this.prop = new Properties(); 
      inputStream = new FileInputStream(this.path);
      this.prop.load(inputStream);
      inputStream.close();
      String certPath = this.prop.getProperty("certPath");
      Trace("User: " + username);
      if (isNullorEmpty(username) || isNullorEmpty(responseMessage)) {
        Trace("Username or SAMLToken cannot be null");
        Trace("Authentication failure");
        return false;
      } 
      DefaultBootstrap.bootstrap();
      byte[] base64DecodedResponse = Base64.decode(responseMessage);
      ByteArrayInputStream is = new ByteArrayInputStream(
          base64DecodedResponse);
      DocumentBuilderFactory documentBuilderFactory = 
        DocumentBuilderFactory.newInstance();
      documentBuilderFactory.setNamespaceAware(true);
      DocumentBuilder docBuilder = documentBuilderFactory
        .newDocumentBuilder();
      Document document = docBuilder.parse(is);
      Element element = document.getDocumentElement();
      UnmarshallerFactory unmarshallerFactory = 
        Configuration.getUnmarshallerFactory();
      Unmarshaller unmarshaller = unmarshallerFactory
        .getUnmarshaller(element);
      XMLObject responseXmlObj = unmarshaller.unmarshall(element);
      Response samlresponse = (Response)responseXmlObj;


      String statusCode = samlresponse.getStatus().getStatusCode()
        .getValue();
      Trace("Response status: " + statusCode);
      if (!statusCode.equals("urn:oasis:names:tc:SAML:2.0:status:Success")) {
        StatusMessage statusMessage = samlresponse.getStatus().getStatusMessage();
        Trace("Status code is not success");
        if (statusMessage != null) {
          String statusMessageText = null;
          statusMessageText = statusMessage.getMessage();
          Trace("Reason: " + statusMessageText);
        } 
        Trace("Authentication failure");
        return false;
      } 


      Assertion assertion = null;
      Signature signature = null;
      List<Assertion> assertionList = samlresponse.getAssertions();
      if (assertionList.isEmpty()) {
        List<EncryptedAssertion> encassertionList = samlresponse
          .getEncryptedAssertions();
        if (encassertionList.isEmpty()) {
          Trace("Problem with retrieveing assertion/encrypted assertion from the provided SAML response");
          Trace("Authentication failure");
          return false;
        } 
        Trace("Encrypted Assertion is not supported right now. Please turn off encrypted assertion at the IdP");
        return false;
      } 

      assertion = assertionList.get(0);
      if (assertion == null) {
        Trace("The Response must contain at least one Assertion");
        Trace("Authentication failure");
        return false;
      } 

      if (!assertion.isSigned() && !samlresponse.isSigned()) {
        Trace("Either assertion or response has to be signed ");
        Trace("Authentication failure");
        return false;
      } 
      signature = assertion.getSignature();
      if (signature == null) {
        signature = samlresponse.getSignature();
        if (signature == null) {
          Trace("Problem  retrieving signature from the provided SAML response.");
          Trace("Authentication failure");
          return false;
        } 
      } 

      ArrayList<String> certList = listFilesForFolder(new File(certPath));
      if (certList == null) {
        Trace("Authentication failure");
        return false;
      } 
      int success = 0;
      for (String name : certList) {
        Trace("Validating signature with certificate: " + name);
        File certificateFile = new File(name);

        SignatureValidator signatureValidator = null;
        X509EncodedKeySpec publicKeySpec = null;
        FileInputStream certInputStream = null;
        try {
          certInputStream = new FileInputStream(certificateFile);
          CertificateFactory certificateFactory = 
            CertificateFactory.getInstance("X.509");
          X509Certificate certificate = (X509Certificate)certificateFactory
            .generateCertificate(certInputStream);
          publicKeySpec = new X509EncodedKeySpec(certificate
              .getPublicKey().getEncoded());
        } catch (CertificateException ce) {
          Trace("Signature verificaton: Failure");
          Trace("Reason: " + ce.getMessage());

          continue;
        } catch (FileNotFoundException e) {
          Trace("Signature verificaton: Failure");
          Trace("Reason: " + e.getMessage());


          continue;
        } 


        try {
          KeyFactory keyFactory = KeyFactory.getInstance("RSA");
          PublicKey publicKey = keyFactory
            .generatePublic(publicKeySpec);
          BasicX509Credential publicCredential = new BasicX509Credential();
          publicCredential.setPublicKey(publicKey);
          signatureValidator = new SignatureValidator(
              (Credential)publicCredential);
          signatureValidator.validate(signature);
          Trace("Signature verification: Success");
          success = 1;
          break;
        } catch (ValidationException e) {
          Trace("Invalid Signature");
          Trace("Reason: " + e.getMessage());
        } 
      } 

      if (success == 0) {
        Trace("Signature doesn't match with any of the available certificates");
        Trace("Authentication failure");
        return false;
      } 


      if (assertion.getConditions() != null) {
        DateTime validFrom = assertion.getConditions().getNotBefore();
        DateTime validTill = assertion.getConditions()
          .getNotOnOrAfter();
        if (validFrom != null && validFrom.isAfterNow()) {
          Trace("Assertion is not yet valid, invalidated by condition notBefore");
          Trace("Authentication failure");
          return false;
        } 
        if (validTill != null && (
          validTill.isBeforeNow() || validTill.isEqualNow())) {
          Trace("Assertion is no longer valid, invalidated by condition notOnOrAfter");
          Trace("Authentication failure");
          return false;
        } 
      } 

      success = 0;
      Subject subject = assertion.getSubject();
      if (subject == null) {
        Trace("Assertion subject cannot be null");
        Trace("Authentication failure");
        return false;
      } 


      try {
        String nameID = subject.getNameID().getValue();
        if (!nameID.equalsIgnoreCase(username)) {
          Trace("UserName doesn't match with the NameID in Assertion");
          Trace("Authentication failure");
          return false;
        } 
      } catch (Exception e) {
        Trace("Problem retrieving NameID from Assertion's subject");
        Trace("Authentication failure");
        return false;
      } 


      Iterator<SubjectConfirmation> iterator = subject.getSubjectConfirmations().iterator(); while (iterator.hasNext()) { SubjectConfirmation confirmation = iterator.next();
        if ("urn:oasis:names:tc:SAML:2.0:cm:bearer".equals(confirmation
            .getMethod())) {
          SubjectConfirmationData data = confirmation
            .getSubjectConfirmationData();

          if (data == null) {
            Trace("Bearer SubjectConfirmation invalidated by missing confirmation data");
            continue;
          } 
          if (data.getNotBefore() != null) {
            Trace("Bearer SubjectConfirmation invalidated by not before which is forbidden");
            continue;
          } 
          DateTime expiry = data.getNotOnOrAfter();
          if (expiry == null) {
            Trace("Bearer SubjectConfirmation invalidated by missing notOnOrAfter");
            continue;
          } 
          if (expiry.isBeforeNow() || expiry.isEqualNow()) {
            Trace("Bearer SubjectConfirmation invalidated by notOnOrAfter");
            continue;
          } 
          success = 1;
          break;
        }  }

      if (success == 0) {
        Trace("Not able to validate subject confirmation");
        Trace("Authentication failure");
        return false;
      } 
      Trace("Authentication success");
      return true;
    } catch (Exception e) {
      Trace("Authentication failure");
      Trace("Reason: " + e.getMessage());

      return false;
    } finally {
      if (inputStream != null) {
        inputStream.close();
      }
    } 
  }

  public boolean DFCAuthentication(String user, String password, String docbase) {
    IDfSessionManager sessionManager = null;
    IDfSession session = null;
    System.out.println("Calling docbase authentication+++++++");


    try {
      DfClientX dfClientX = new DfClientX();
      IDfClient client = DfClient.getLocalClient();
      sessionManager = client.newSessionManager();
      IDfLoginInfo loginInfo = dfClientX.getLoginInfo();
      loginInfo.setUser(user);
      String pass = "dm_saml=" + password;
       Trace(pass);
      loginInfo.setPassword(pass);
      loginInfo.setDomain(null);
      sessionManager.setIdentity(docbase, loginInfo);
      session = sessionManager.getSession(docbase);
      return true;
    }
    catch (Exception e) {

      return false;
    } 
  }
}

    enter code here

1 个答案:

答案 0 :(得分:0)

问题1

我有一个SAML响应。我想读取编码的SAML响应,使用Java从响应中解码并提取名称ID值。

答案

GitHub存储库上的

OneLogin Java SAML SP提供了以下Java源代码,以读取编码的SAML响应,解码并从SAML响应中提取名称ID值。

java-saml / core / src / main / java / com / onelogin / saml2 / authn / SamlResponse.java

/**
     * Gets the NameID provided from the SAML Response Document.
     *
     * @return the Name ID Data (Value, Format, NameQualifier, SPNameQualifier)
     *
     * @throws Exception
     *
     */
    public Map<String,String> getNameIdData() throws Exception {
        if (this.nameIdData != null) {
            return this.nameIdData;
        }
        Map<String,String> nameIdData = new HashMap<>();

        NodeList encryptedIDNodes = this.queryAssertion("/saml:Subject/saml:EncryptedID");
        NodeList nameIdNodes;
        Element nameIdElem;
        if (encryptedIDNodes.getLength() == 1) {
            NodeList encryptedDataNodes = this.queryAssertion("/saml:Subject/saml:EncryptedID/xenc:EncryptedData");
            if (encryptedDataNodes.getLength() == 1) {
                Element encryptedData = (Element) encryptedDataNodes.item(0);
                PrivateKey key = settings.getSPkey();
                if (key == null) {
                    throw new SettingsException("Key is required in order to decrypt the NameID", SettingsException.PRIVATE_KEY_NOT_FOUND);
                }

                Util.decryptElement(encryptedData, key);
            }
            nameIdNodes = this.queryAssertion("/saml:Subject/saml:EncryptedID/saml:NameID|/saml:Subject/saml:NameID");

            if (nameIdNodes == null || nameIdNodes.getLength() == 0) {
                throw new Exception("Not able to decrypt the EncryptedID and get a NameID");
            }
        } else {
            nameIdNodes = this.queryAssertion("/saml:Subject/saml:NameID");
        }

        if (nameIdNodes != null && nameIdNodes.getLength() == 1) {
            nameIdElem = (Element) nameIdNodes.item(0);

            if (nameIdElem != null) {
                String value = nameIdElem.getTextContent();
                if (settings.isStrict() && value.isEmpty()) {
                    throw new ValidationError("An empty NameID value found", ValidationError.EMPTY_NAMEID);
                }

                nameIdData.put("Value", value);

                if (nameIdElem.hasAttribute("Format")) {
                    nameIdData.put("Format", nameIdElem.getAttribute("Format"));
                }
                if (nameIdElem.hasAttribute("SPNameQualifier")) {
                    String spNameQualifier = nameIdElem.getAttribute("SPNameQualifier");
                    validateSpNameQualifier(spNameQualifier);
                    nameIdData.put("SPNameQualifier", spNameQualifier);
                }
                if (nameIdElem.hasAttribute("NameQualifier")) {
                    nameIdData.put("NameQualifier", nameIdElem.getAttribute("NameQualifier"));
                }
            }
        } else {
            if (settings.getWantNameId()) {
                throw new ValidationError("No name id found in Document.", ValidationError.NO_NAMEID);
            }
        }
        this.nameIdData = nameIdData;
        return nameIdData;
    }

问题2

这是我使用过的代码,但是没有运行

答案

引用您的源代码,这是由于身份验证失败而导致无效SAML响应的根本原因。

    String url = String.valueOf(IDPurl) + "/?SAMLRequest=" + encodedAuthnRequest;
    String samlresponse = request.getParameter("SAMLResponse");

您不能使用传统的HTTP请求/响应对来完成SAML身份验证,因为SAML依赖于Web浏览器将SAML请求从SP重定向到IdP,然后将SAML响应从IdP重定向到SP。

(1)用户访问SAML SP应用程序。

(2)使用SAML请求将用户重定向到SAML IdP。

(3)SAML IdP收到SAML请求,然后提示用户键入其用户名/密码凭据以进行身份​​验证。

(4)如果用户名/密码凭据正确,则SAML IdP会生成SAML响应,然后将用户重定向回SAML SP应用程序,同时将POST SAML响应发送到SAML SP。

解决方案

将源代码分为两部分,为您的SAML SP(服务提供商)应用程序创建两(2)个不同的API,以将SAML请求发送到SAML IdP(身份提供商)并分别从SAML IdP接收SAML响应。

例如,

(1)SAML SP API,用于构建SAML请求并将SAML请求重定向到SAML IdP

public String buildAuthenticationRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { 

DefaultBootstrap.bootstrap();

/*Copy your source code to build SAML authentication request here */

String url = String.valueOf(IDPurl) + "/?SAMLRequest=" + encodedAuthnRequest;

response.sendRedirect(url);

}

(2)SAML SP API,用于通过SAML IdP接收SAML响应POST


/* See the source code provided by the above Answer for your Question 1 */

or 

/* Your source code for processing SAML response */

public void loadXmlFromBase64(String samlresponse) throws Exception { 

byte[] base64DecodedResponse = Base64.decode(samlresponse);
ByteArrayInputStream is = new ByteArrayInputStream(base64DecodedResponse);


}

参考

GitHub存储库上的

How to build and run Shibboleth SAML IdP and SP using Docker container提供了有关使用Shibboleth SAML IdP和OpenLDAP以及SAML SP Web应用程序构建基于SAML的身份验证/授权提供程序的说明。

  • Shibboleth SAML IdP负责身份联合。
  • OpenLDAP负责身份验证。

您可以使用上面的GitHub存储库来模拟SAML IdP和SAML SP之间的SAML身份验证流程。

此外,您可以构建并运行Shibboleth SAML IdP来测试SAML SP的SAML身份验证流程。