我已将实现的代码发布到这里。当我单击特定的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
答案 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的身份验证/授权提供程序的说明。
您可以使用上面的GitHub存储库来模拟SAML IdP和SAML SP之间的SAML身份验证流程。
此外,您可以构建并运行Shibboleth SAML IdP来测试SAML SP的SAML身份验证流程。