首页 > 解决方案 > 如何读取 SAML 编码的响应并提取名称 ID

问题描述

我已经在此处发布了我实现的代码。当我单击特定 URL 时,我正在初始化 SAML servlet 例如:( https://wwwdev-lom.app.ford.com/launchomatic/launch/view.jsp?chronicleId=0900cad780aaac86&docbase =edmsdev)但是当我单击身份验证 SAML 时,页面将重定向回 ADFS 页面。ADFS 页面已成功通过身份验证。我希望将页面重定向回我单击的相同 URL。该链接应该有帮助我在验证 ADFS 后从浏览器下载内容。我是否应该通过 JSP 再次构建此 URL 并将此 servlet 转发到 JSP。如果是这样,我如何从当前的 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

标签: saml

解决方案


问题一

我有一个 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 应用程序,同时将 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 存储库中的 Docker 容器构建和运行 Shibboleth SAML IdP 和 SP 提供了有关使用 Shibboleth SAML IdP 和 OpenLDAP 以及 SAML SP Web 应用程序构建基于 SAML 的身份验证/授权提供程序的说明。

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

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

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


推荐阅读