001 /** 002 * Copyright (C) 2009 "Darwin V. Felix" <darwinfelix@users.sourceforge.net> 003 * 004 * This library is free software; you can redistribute it and/or 005 * modify it under the terms of the GNU Lesser General Public 006 * License as published by the Free Software Foundation; either 007 * version 2.1 of the License, or (at your option) any later version. 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public 015 * License along with this library; if not, write to the Free Software 016 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 017 */ 018 019 package net.sourceforge.spnego; 020 021 import java.io.ByteArrayOutputStream; 022 import java.io.IOException; 023 import java.net.MalformedURLException; 024 import java.net.URL; 025 import java.security.PrivilegedActionException; 026 027 import javax.security.auth.login.LoginException; 028 import javax.xml.soap.MessageFactory; 029 import javax.xml.soap.MimeHeaders; 030 import javax.xml.soap.SOAPConnection; 031 import javax.xml.soap.SOAPConstants; 032 import javax.xml.soap.SOAPException; 033 import javax.xml.soap.SOAPMessage; 034 035 import org.ietf.jgss.GSSCredential; 036 import org.ietf.jgss.GSSException; 037 038 /** 039 * This class can be used to make SOAP calls to a protected SOAP Web Service. 040 * 041 * <p> 042 * The idea for this class is to replace code that looks like this... 043 * <pre> 044 * final SOAPConnectionFactory soapConnectionFactory = 045 * SOAPConnectionFactory.newInstance(); 046 * conn = soapConnectionFactory.createConnection(); 047 * </pre> 048 * </p> 049 * 050 * <p> 051 * with code that looks like this... 052 * <pre> 053 * conn = new SpnegoSOAPConnection("spnego-client", "dfelix", "myp@s5"); 054 * </pre> 055 * </p> 056 * 057 * <p><b>Example:</b></p> 058 * <pre> 059 * SOAPMessage response = null; 060 * 061 * <b>final SpnegoSOAPConnection conn = 062 * new SpnegoSOAPConnection(this.module, this.kuser, this.kpass);</b> 063 * 064 * try { 065 * final MessageFactory msgFactory = MessageFactory.newInstance(); 066 * final SOAPMessage message = msgFactory.createMessage(); 067 * 068 * final SOAPBody body = message.getSOAPBody(); 069 * 070 * final SOAPBodyElement bodyElement = body.addBodyElement( 071 * new QName(this.namespace, this.methodName, this.nsprefix)); 072 * 073 * for (int i=0; i<args.length; i++) { 074 * final SOAPElement element = bodyElement.addChildElement( 075 * new QName("arg" + i)); 076 * 077 * element.addTextNode(args[i]); 078 * } 079 * 080 * response = conn.call(message, this.serviceLocation); 081 * 082 * } finally { 083 * conn.close(); 084 * } 085 * </pre> 086 * 087 * <p> 088 * To see a full working example, take a look at the 089 * <a href="http://spnego.sourceforge.net/ExampleSpnegoSOAPClient.java" 090 * target="_blank">ExampleSpnegoSOAPClient.java</a> 091 * example. 092 * </p> 093 * 094 * <p> 095 * Also, take a look at the 096 * <a href="http://spnego.sourceforge.net/protected_soap_service.html" 097 * target="_blank">how to connect to a protected SOAP Web Service</a> 098 * example. 099 * </p> 100 * 101 * @see SpnegoHttpURLConnection 102 * 103 * @author Darwin V. Felix 104 * 105 */ 106 public class SpnegoSOAPConnection extends SOAPConnection { 107 108 private final transient SpnegoHttpURLConnection conn; 109 110 /** 111 * Creates an instance where the LoginContext relies on a keytab 112 * file being specified by "java.security.auth.login.config" or 113 * where LoginContext relies on tgtsessionkey. 114 * 115 * @param loginModuleName 116 * @throws LoginException 117 */ 118 public SpnegoSOAPConnection(final String loginModuleName) throws LoginException { 119 120 super(); 121 this.conn = new SpnegoHttpURLConnection(loginModuleName); 122 } 123 124 /** 125 * Create an instance where the GSSCredential is specified by the parameter 126 * and where the GSSCredential is automatically disposed after use. 127 * 128 * @param creds credentials to use 129 */ 130 public SpnegoSOAPConnection(final GSSCredential creds) { 131 this(creds, true); 132 } 133 134 /** 135 * Create an instance where the GSSCredential is specified by the parameter 136 * and whether the GSSCredential should be disposed after use. 137 * 138 * @param creds credentials to use 139 * @param dispose true if GSSCredential should be diposed after use 140 */ 141 public SpnegoSOAPConnection(final GSSCredential creds, final boolean dispose) { 142 super(); 143 this.conn = new SpnegoHttpURLConnection(creds, dispose); 144 } 145 146 /** 147 * Creates an instance where the LoginContext does not require a keytab 148 * file. However, the "java.security.auth.login.config" property must still 149 * be set prior to instantiating this object. 150 * 151 * @param loginModuleName 152 * @param username 153 * @param password 154 * @throws LoginException 155 */ 156 public SpnegoSOAPConnection(final String loginModuleName, 157 final String username, final String password) throws LoginException { 158 159 super(); 160 this.conn = new SpnegoHttpURLConnection(loginModuleName, username, password); 161 } 162 163 @Override 164 public final SOAPMessage call(final SOAPMessage request, final Object endpoint) 165 throws SOAPException { 166 167 SOAPMessage message = null; 168 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 169 170 try { 171 final MimeHeaders headers = request.getMimeHeaders(); 172 final String[] contentType = headers.getHeader("Content-Type"); 173 final String[] soapAction = headers.getHeader("SOAPAction"); 174 175 // build the Content-Type HTTP header parameter if not defined 176 if (null == contentType) { 177 final StringBuilder header = new StringBuilder(); 178 179 if (null == soapAction) { 180 header.append("application/soap+xml; charset=UTF-8;"); 181 } else { 182 header.append("text/xml; charset=UTF-8;"); 183 } 184 185 // not defined as a MIME header but we need it as an HTTP header parameter 186 this.conn.addRequestProperty("Content-Type", header.toString()); 187 } else { 188 if (contentType.length > 1) { 189 throw new IllegalArgumentException("Content-Type defined more than once."); 190 } 191 192 // user specified as a MIME header so add it as an HTTP header parameter 193 this.conn.addRequestProperty("Content-Type", contentType[0]); 194 } 195 196 // specify SOAPAction as an HTTP header parameter 197 if (null != soapAction) { 198 if (soapAction.length > 1) { 199 throw new IllegalArgumentException("SOAPAction defined more than once."); 200 } 201 this.conn.addRequestProperty("SOAPAction", soapAction[0]); 202 } 203 204 request.writeTo(bos); 205 206 this.conn.connect(new URL(endpoint.toString()), bos); 207 208 final MessageFactory factory = MessageFactory.newInstance( 209 SOAPConstants.SOAP_1_2_PROTOCOL); 210 211 try { 212 message = factory.createMessage(null, this.conn.getInputStream()); 213 } catch (IOException e) { 214 message = factory.createMessage(null, this.conn.getErrorStream()); 215 } 216 217 } catch (MalformedURLException e) { 218 throw new SOAPException(e); 219 } catch (IOException e) { 220 throw new SOAPException(e); 221 } catch (GSSException e) { 222 throw new SOAPException(e); 223 } catch (PrivilegedActionException e) { 224 throw new SOAPException(e); 225 } finally { 226 try { 227 bos.close(); 228 } catch (IOException ioe) { 229 assert true; 230 } 231 this.close(); 232 } 233 234 return message; 235 } 236 237 @Override 238 public final void close() { 239 if (null != this.conn) { 240 this.conn.disconnect(); 241 } 242 } 243 }