最近很多朋友都在问我怎样从java客户端调用带安全验证的WCF服务。 当初我解决这个问题的时候查了很多文献和资料,虽说web service是号称跨平台的吧,可是真跨起来,有时也挺麻烦。现在我把它总结下来,希望能对大家有所帮助。
因为当时给公司(Paragallo Mobile AS)写解决方案的时候就是用的英文,所以还是英文的。同时我配了demo代码,相信大家可以看明白。如果有什么不明白的地方,可以给我留言。
We use Message-Level Security authenticating with wsHttpBinding. And the user also needs a username/password. X.509 Certificate is used here.
This article talks about how to call WCF service from the java client with authentication. The server side use X.509 certificate to encrypt the Username and Password. The java client tool is Netbeans and Eclipse with SoapUI plug-in installed.
Contents
Invoke WCF service from Java Client with Authentication (X.509 Certificate)
Summary:
1.Prepare the Certificate:
1.1. Create a Certificate to Act as Your Root Certificate Authority
1.2. Install Your Root Certificate Authority Certificate on the Server and Client Machines
1.3. Create and Install Your Temporary Service Certificate
2.Create a Demo WCF Service
2.1. New a demo wcf service
2.2. Create Customer Validator
2.3. the Server config file
3. Call the Service From Java Client
3.1 Create the Java client, the container is Glassfish
3.2 Create the Java Application client, the container is not Glassfish
3.3 Development the Java client code in Eclipse.
4. Download Demos
Reference
We need to create a self-signed root certificate authority (CA) that will be placed in the Trusted Root Certification Authority store (受信任的根证书颁发机构). The certificate used by WCF is then created from the root self-signed certificate and installed in the LocalMachine store.
Command: makecert -n "CN=RootCATest" -r -sv RootCATest.pvk RootCATest.cer
In this command:
-n – Specifies the subject name for the root CA. The convention is to prefix the subject name with "CN = " for "Common Name".
-r – Specifies that the certificate will be self-signed.
-sv – Specifies the file that contains the private key of the certificate.
RootCATest.cer – Specifies the name of the file containing the public key of the certificate.
In the Create Private Key Password dialog box, enter a password, confirm the password, and then click OK. Optionally, you can click None without entering the password, but this is not recommended for security reasons.
In the Enter Private Key Password dialog box, enter the password again and then click OK.
This is the password needed to access the private key file RootCATest.pvk in order to generate the file RootCATest.cer containing the public key.
In this step, you will install the certificate in the Trusted Root Certification Authorities (受信任的根证书颁发机构) location on both the server and client machines. All certificates that are signed with this certificate will be trusted by the client machine.
You can do this by run mmc command, then do it in Microsoft Management Console. Import the Certificate to Trusted Root Certification Authorities (受信任的根证书颁发机构).
Command: makecert -sk MyKeyName -iv RootCATest.pvk -n "CN=tempCert" -ic RootCATest.cer -sr localmachine -ss my -sky exchange -pe tempCert.cer
In the Enter Private Key Password dialog box, enter the password for the root CA privatekeyfile specified in STEP 1, and then click OK.
Class MyCustomValidator by inheriting UserNamePasswordValidator, but this should be created in a new class library project.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IdentityModel.Selectors; using System.IdentityModel.Tokens; namespace ServerWcfService.CustomValidators { public class MyCustomValidator : UserNamePasswordValidator { public override void Validate(string userName, string password) { if (string.IsNullOrEmpty(userName)) throw new ArgumentNullException("userName"); if (string.IsNullOrEmpty(password)) throw new ArgumentNullException("password"); if (userName != "xiaozhuang" || password != "123456") throw new SecurityTokenException("Error username or password"); } } }
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="mySecureBinding">
<security mode="Message">
<message clientCredentialType="UserName"/>
security>
binding>
wsHttpBinding>
bindings>
<services>
<service behaviorConfiguration="ServerWcfService.Services.MySimpleServiceBehavior" name="ServerWcfService.Services.MySimpleService">
<endpoint address="" binding="wsHttpBinding" contract="ServerWcfService.ServiceContracts.IMySimpleService" bindingConfiguration="mySecureBinding">
<identity>
<dns value="MyServerCert"/>
identity>
endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
service>
services>
<behaviors>
<serviceBehaviors>
<behavior name="ServerWcfService.Services.MySimpleServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<serviceCertificate findValue="MyServerCert" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My"/>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="ServerWcfService.CustomValidators.MyCustomValidator,ServerWcfService"/>
serviceCredentials>
behavior>
serviceBehaviors>
behaviors>
system.serviceModel>
The config file set the clientCredentialType in the
You can use Web Services Interoperability Technologies (WSIT) do access the WCF Service from java client. And this was integrated in NetBeans V6.1.
Here the IDE is Netbeans, my version is 6.1.
1 New a Web project
2 add a new web service client.
3 import the server cert to the store “TrustStore”
This the command, you must install JDK if you want use it.
keytool -import -file MyServer.cer -keystore TrustStore -alias serverkey
the import file MyServer.cer is the server's X.509 certificate, serverkey is the alias of the certificate. And you will need to input a keystore password after you execute this command. Please remember your password. The keystore file will in the current forder where you do this command.
keytool -list -v -keystore TrustStore
using this command to see if the importing is successful.
Note: if you don't do this, it will throw an exception “WSS1511: An Error occurred while locating PEER Entity certificate in TrustStore.”.
4 Edit Web Service attributes
You can Edit it by right click the webservice
then you can edit the security attribute, please click the TrustStore button:
You can choose your truststore which you import the server Cert at step 3 by browse.please change the File of Type to all files.afte this input the password of the keystore. And the Cert's alias.
After this you will get two config file in folder “Source Packages” of Netbeans. The two files are MySimpleService.svc.xml and wsit-client.xml. Please edit the MySimpleService.svc.xml.
Example:
Note: the type is “JKS”.
5. New a Test Servlet.
Add the following code to the servlet.
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); org.tempuri.MySimpleService client; org.tempuri.IMySimpleService port; try { client = new MySimpleService(); port = client.getWSHttpBindingIMySimpleService(); ((BindingProvider)port).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "xiaozhuang"); ((BindingProvider)port).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "123456"); String test = port.printMessage("Hello World!"); //TODO output your page here out.println(""); out.println("
"); out.println("
Note: the bold code is to put the username and password. Please write this before you call a method.
I test successfully.
It's very same like above configuration. But if you are running on some other container then the only way for you is to supply your own CallbackHandler :
The config file:
The class-name is the full name of your own callbackhandler class.
Example:
package javaapplication1; import com.sun.xml.wss.impl.callback.KeyStoreCallback; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.util.Iterator; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; public class TrustStoreCallbackHandler implements CallbackHandler { KeyStore keyStore = null; String password = "1302821986"; public TrustStoreCallbackHandler() { System.out.println("Truststore CBH.CTOR Called.........."); InputStream is = null; try { keyStore = KeyStore.getInstance("JKS"); String keystoreURL = "C://Documents and Settings//Marvion//TrustStore"; is = new FileInputStream(keystoreURL); keyStore.load(is, "1302821986".toCharArray()); } catch (IOException ex) { // Logger.getLogger(KeyStoreCallbackHandler.class.getName()).log(Level.SEVERE, null, ex); throw new RuntimeException(ex); } catch (NoSuchAlgorithmException ex) { // Logger.getLogger(KeyStoreCallbackHandler.class.getName()).log(Level.SEVERE, null, ex); throw new RuntimeException(ex); } catch (CertificateException ex) { // Logger.getLogger(KeyStoreCallbackHandler.class.getName()).log(Level.SEVERE, null, ex); throw new RuntimeException(ex); } catch (KeyStoreException ex) { // Logger.getLogger(KeyStoreCallbackHandler.class.getName()).log(Level.SEVERE, null, ex); throw new RuntimeException(ex); } finally { try { is.close(); } catch (IOException ex) { // Logger.getLogger(KeyStoreCallbackHandler.class.getName()).log(Level.SEVERE, null, ex); } } } public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { System.out.println("Truststore CBH.handle() Called.........."); for (int i = 0; i < callbacks.length; i++) { if (callbacks[i] instanceof KeyStoreCallback) { KeyStoreCallback cb = (KeyStoreCallback) callbacks[i]; print(cb.getRuntimeProperties()); cb.setKeystore(keyStore); } else { throw new UnsupportedCallbackException(callbacks[i]); } } } private void print(Map context) { Iterator it = context.keySet().iterator(); while (it.hasNext()) { System.out.println("Prop " + it.next()); } } }
Note: you need download metro-1_3.jar from https://metro.dev.java.net/1.3/ . After install that add the webservices-rt.jar to the project library reference.
Well, there is no eclipse plug-in for can do it directly. So what I do is develop is in Netbeans first, then I move the code and configuration files to Eclipse. It needs to change some source folder directory here. see my sample code.
4.1 The Service side code. http://download.csdn.net/source/1135380
4.2 Web application code http://download.csdn.net/source/1135375
4.3 Java application code http://download.csdn.net/source/1135382
4.4 Eclipse client code http://download.csdn.net/source/1135383
4.5 The needed jar file (webservices-rt.jar) it will generate by netbeans when you add new webservice client.
01 http://msdn2.microsoft.com/en-us/library/ms733813.aspx
02 http://forums.java.net/jive/thread.jspa?messageID=293406
03 http://forums.java.net/jive/message.jspa?messageID=277883
04 http://forums.java.net/jive/thread.jspa?messageID=262600
05 https://xwss.dev.java.net/articles/security_config.html
06 https://wsit.dev.java.net/issues/show_bug.cgi?id=844