Due to the popularity of my prior posts on processing SAML using OpenSAML I put together this code that shows a more real-world-example of how you would setup an HttpServlet to sit and listen for SAML Responses that are posted to it. Then it just shows what you might do with the Response to get what you need out of it. Again, I'm just verifying the Signature on the Response, Decrypting the Assertion and then looping through the Attribute nodes to look at the name/value pairs. There are other things that you might want to do such as receiving the signing-key from the Response itself (if it's being sent), or verifying a Signature on the Assertion if that's what's being signed. The trouble I've found with SAML implementations is that the spec is consistent, but how people use it isn't, so you'll undoubtedly need to tweak something depending on what your requirements are, but this code should get you started.
1 import java.io.PrintWriter; 2 import java.io.IOException; 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.InputStream; 6 7 import java.util.List; 8 9 import java.security.KeyFactory; 10 import java.security.KeyStore; 11 import java.security.PublicKey; 12 import java.security.cert.CertificateFactory; 13 import java.security.cert.X509Certificate; 14 import java.security.interfaces.RSAPrivateKey; 15 import java.security.spec.X509EncodedKeySpec; 16 17 import javax.servlet.ServletException; 18 import javax.servlet.http.HttpServlet; 19 import javax.servlet.http.HttpServletRequest; 20 import javax.servlet.http.HttpServletResponse; 21 22 import org.opensaml.common.binding.BasicSAMLMessageContext; 23 import org.opensaml.saml2.binding.decoding.HTTPPostDecoder; 24 import org.opensaml.ws.message.MessageContext; 25 import org.opensaml.ws.transport.http.HttpServletRequestAdapter; 26 import org.opensaml.saml2.core.Assertion; 27 import org.opensaml.saml2.core.Attribute; 28 import org.opensaml.saml2.core.AttributeStatement; 29 import org.opensaml.saml2.core.Response; 30 import org.opensaml.saml2.encryption.Decrypter; 31 import org.opensaml.xml.XMLObject; 32 import org.opensaml.xml.encryption.DecryptionException; 33 import org.opensaml.xml.encryption.InlineEncryptedKeyResolver; 34 import org.opensaml.xml.security.keyinfo.StaticKeyInfoCredentialResolver; 35 import org.opensaml.xml.security.x509.BasicX509Credential; 36 import org.opensaml.xml.signature.Signature; 37 import org.opensaml.xml.signature.SignatureValidator; 38 import org.opensaml.xml.validation.ValidationException; 39 40 /** 41 * @author kevnls 42 * If you use this code as a base for your implementation please leave this comment intact. 43 * You should add your own name in addition. 44 */ 45 46 public class ProcessSAML extends HttpServlet { 47 48 /** 49 * Processes requests for both HTTP GET and POST methods. 50 * @param request servlet request 51 * @param response servlet response 52 * @throws ServletException if a servlet-specific error occurs 53 * @throws IOException if an I/O error occurs 54 */ 55 56 protected void processRequest(HttpServletRequest request, HttpServletResponse response) 57 throws ServletException, IOException { 58 59 response.setContentType("text/html;charset=UTF-8"); 60 PrintWriter out = response.getWriter(); 61 62 File signatureVerificationPublicKeyFile = new File("C:\\Documents and Settings\\kevnls\\My Documents\\NetBeansProjects\\SAMLReceiver\\files\\IdPSigningCert.cer"); 63 File decryptionPrivateKeyFile = new File("C:\\Documents and Settings\\kevnls\\My Documents\\NetBeansProjects\\SAMLReceiver\\files\\SPEncryptionCert.jks"); 64 String decryptionPrivateKeyName = "pvktmp:bd5ba0e0-9718-48ea-b6e6-32cd9c852d76"; 65 String decryptionPrivateKeyPassword = "!c3c0ld"; 66 67 try 68 { 69 //bootstrap the opensaml stuff 70 org.opensaml.DefaultBootstrap.bootstrap(); 71 72 // get the message context 73 MessageContext messageContext = new BasicSAMLMessageContext(); 74 messageContext.setInboundMessageTransport(new HttpServletRequestAdapter(request)); 75 HTTPPostDecoder samlMessageDecoder = new HTTPPostDecoder(); 76 samlMessageDecoder.decode(messageContext); 77 78 // get the SAML Response 79 Response samlResponse = (Response)messageContext.getInboundMessage(); 80 81 //get the certificate from the file 82 InputStream inputStream2 = new FileInputStream(signatureVerificationPublicKeyFile); 83 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); 84 X509Certificate certificate = (X509Certificate)certificateFactory.generateCertificate(inputStream2); 85 inputStream2.close(); 86 87 //pull out the public key part of the certificate into a KeySpec 88 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(certificate.getPublicKey().getEncoded()); 89 90 //get KeyFactory object that creates key objects, specifying RSA 91 KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 92 93 //generate public key to validate signatures 94 PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); 95 96 //create credentials 97 BasicX509Credential publicCredential = new BasicX509Credential(); 98 99 //add public key value 100 publicCredential.setPublicKey(publicKey); 101 102 //create SignatureValidator 103 SignatureValidator signatureValidator = new SignatureValidator(publicCredential); 104 105 //get the signature to validate from the response object 106 Signature signature = samlResponse.getSignature(); 107 108 //try to validate 109 try 110 { 111 signatureValidator.validate(signature); 112 } 113 catch (ValidationException ve) 114 { 115 out.println("Signature is not valid."); 116 out.println(ve.getMessage()); 117 return; 118 } 119 120 //no validation exception was thrown 121 out.println("Signature is valid."); 122 123 //start decryption of assertion 124 125 //load up a KeyStore 126 KeyStore keyStore = KeyStore.getInstance("JKS"); 127 keyStore.load(new FileInputStream(decryptionPrivateKeyFile), decryptionPrivateKeyPassword.toCharArray()); 128 129 RSAPrivateKey privateKey = (RSAPrivateKey) keyStore.getKey(decryptionPrivateKeyName, decryptionPrivateKeyPassword.toCharArray()); 130 131 //create the credential 132 BasicX509Credential decryptionCredential = new BasicX509Credential(); 133 decryptionCredential.setPrivateKey(privateKey); 134 135 StaticKeyInfoCredentialResolver skicr = new StaticKeyInfoCredentialResolver(decryptionCredential); 136 137 //create a decrypter 138 Decrypter decrypter = new Decrypter(null, skicr, new InlineEncryptedKeyResolver()); 139 140 //decrypt the first (and only) assertion 141 Assertion decryptedAssertion; 142 143 try 144 { 145 decryptedAssertion = decrypter.decrypt(samlResponse.getEncryptedAssertions().get(0)); 146 } 147 catch (DecryptionException de) 148 { 149 out.println("Assertion decryption failed."); 150 out.println(de.getMessage()); 151 return; 152 } 153 154 out.println("Assertion decryption succeeded."); 155 156 //loop through the nodes to get the Attributes 157 //this is where you would do something with these elements 158 //to tie this user with your environment 159 List attributeStatements = decryptedAssertion.getAttributeStatements(); 160 for (int i = 0; i < attributeStatements.size(); i++) 161 { 162 List attributes = attributeStatements.get(i).getAttributes(); 163 for (int x = 0; x < attributes.size(); x++) 164 { 165 String strAttributeName = attributes.get(x).getDOM().getAttribute("Name"); 166 167 List attributeValues = attributes.get(x).getAttributeValues(); 168 for (int y = 0; y < attributeValues.size(); y++) 169 { 170 String strAttributeValue = attributeValues.get(y).getDOM().getTextContent(); 171 out.println(strAttributeName + ": " + strAttributeValue + " "); 172 } 173 } 174 } 175 } 176 catch (Exception ex) 177 { 178 ex.printStackTrace(); 179 } 180 181 } 182 183 // 184 /** 185 * Handles the HTTP GET method. 186 * @param request servlet request 187 * @param response servlet response 188 * @throws ServletException if a servlet-specific error occurs 189 * @throws IOException if an I/O error occurs 190 */ 191 @Override 192 protected void doGet(HttpServletRequest request, HttpServletResponse response) 193 throws ServletException, IOException { 194 processRequest(request, response); 195 } 196 197 /** 198 * Handles the HTTP POST method. 199 * @param request servlet request 200 * @param response servlet response 201 * @throws ServletException if a servlet-specific error occurs 202 * @throws IOException if an I/O error occurs 203 */ 204 @Override 205 protected void doPost(HttpServletRequest request, HttpServletResponse response) 206 throws ServletException, IOException { 207 processRequest(request, response); 208 } 209 210 /** 211 * Returns a short description of the servlet. 212 * @return a String containing servlet description 213 */ 214 @Override 215 public String getServletInfo() { 216 return "This servlet processes a SAML 2.0 Response. It verifies the signature, " + 217 "decrypts an assertion, and parses out the data in the attribute statements. " + 218 "If you use this code as a base for your implementation please leave the @author comment intact. " + 219 "You should add your own name in addition."; 220 }// 221 222 }
I've also got a full NetBeans project with this code posted here. I've found that NetBeans is the easiest way to get an HttpServlet running locally if you install the version with the web and app server bundled in.
Anyway, hope this is useful to someone.