关于java调用.Net WCF服务的讲解.
最近做毕业设计,一个团体项目。
我做的是关于系统构架方面的。简单点说就是.Net写的一个WCF服务,而访问服务的是java客户端或者说是android客户端。
而我的WCF服务配置成wsHttpBinding+certificate证书加密+自定义的用户名密码检验功能。在服务的访问上用了https协议,个人也没做过java平台和.Net Web Service的整合。而且https无疑增加了这个难度。
所以刚开始的想法是通过asp.net网页来访问服务,再利用网页来给java,android返回xml。这样利用http避免了平台的不兼容性。
缺点是各种业务的增加删除不紧要在Web Service中增加,还要在网页中增加。
而后来由于另外几个同学迟迟没有开始做应用端,导致业务无法预料,时间越脱越长。
个人觉得时间可能有点紧,就想把框架简单化,直接让java端和andriod客户端调用WCF服务。
于是清明放假这几天改造了一下程序框架,网络上参考了各种文章,终于调试成功了。
废话不说了,看代码。
首先,最简单的服务,没有certificate,没有自定义的用户名密码检验,只有wsHttpBinding。
WCF服务配置程序如下:
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <system.serviceModel>
- <behaviors>
- <serviceBehaviors>
- <behavior name="httpsBasicBindings">
- <serviceMetadata httpGetEnabled="false" httpGetUrl="http://127.0.0.1:8889/test" />
- <serviceDebug includeExceptionDetailInFaults="true" />
- </behavior>
- </serviceBehaviors>
- </behaviors>
- <services>
- <service behaviorConfiguration="httpsBasicBindings" name="TestService.TestService">
- <endpoint address="" binding="basicHttpBinding" bindingConfiguration="test1" contract="TestService.ITestService">
- </endpoint>
- <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
- </service>
- </services>
- <bindings>
- <basicHttpBinding>
- <binding name="test1">
- <security mode="Transport">
- <transport clientCredentialType="None"/>
- </security>
- </binding>
- </basicHttpBinding>
- </bindings>
- </system.serviceModel>
java客户端调用程序:(用axis2-1.4.1生成,生成方法cmd下 axis2的bin目录下 wsdl2java -uri http://localhost:8889/test?wsdl)
- TestServiceStub.GetSysTime paraGetSysTime=new TestServiceStub.GetSysTime();
- try {
- TestServiceStub.GetSysTimeResponse response = client.GetSysTime(paraGetSysTime);
- resultString=response.getGetSysTimeResult();
- } catch (RemoteException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- } catch (AxisFault e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- System.out.print(resultString);
结果调用失败,一直是什么read time out,超时。网查查阅资料,说这么设置就可以了。
- client._getServiceClient().getOptions().setTimeOutInMilliSeconds(5*60*1000);
于是设置,结果还是超时。继续找资料。发现都是说超时。当时那个崩溃啊~~好在翻了10页google,终于找到一篇说是java不支持wshttpbinding协议。因为wshttpbinding是微软在SOAP1.0基础上改进过的,不是通用的绑定协议。这里又补充了下理论知识,呵呵~~于是改为basicHttpBinding。
WCF服务配置(basicHttpBinding)
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <system.serviceModel>
- <behaviors>
- <serviceBehaviors>
- <behavior name="httpsBasicBindings">
- <serviceMetadata httpGetEnabled="true" httpGetUrl="http://127.0.0.1:8889/test" />
- <serviceDebug includeExceptionDetailInFaults="true" />
- </behavior>
- </serviceBehaviors>
- </behaviors>
- <services>
- <service behaviorConfiguration="httpsBasicBindings" name="TestService.TestService">
- <endpoint address="" binding="basicHttpBinding" bindingConfiguration="test1" contract="TestService.ITestService">
- </endpoint>
- <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
- </service>
- </services>
- <bindings>
- <basicHttpBinding>
- <binding name="test1">
- <security mode="Transport">
- <transport clientCredentialType="None"/>
- </security>
- </binding>
- </basicHttpBinding>
- </bindings>
- </system.serviceModel>
- </configuration>
然后重新用axis2 生成客户端代理。调用代码,果然成功。调用代码不变。
调用结果图:
下面是axis2生成的客户端代理结构图。
证书制作,绑定端口什么的这边就不讲了,网上有关于这方面的讲解。用到了XP系统的话是httpcfg.exe,Win7目前我还没用过,反正是有一个差不多的工具。之前我也转载过一个制作证书的文章。
这边的话主要讲配置文件,以及调用中我遇到的一些问题。
WCF服务配置文件:
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <system.serviceModel>
- <behaviors>
- <serviceBehaviors>
- <behavior name="httpsBasicBindings">
- <serviceMetadata httpsGetEnabled="true" httpGetEnabled="true" httpGetUrl="http://127.0.0.1:8889/test" />
- <serviceDebug includeExceptionDetailInFaults="true" />
- <serviceCredentials>
- <serviceCertificate storeName="My" x509FindType="FindBySubjectName" findValue="HL7Server" storeLocation="LocalMachine"/>
- </serviceCredentials>
- </behavior>
- </serviceBehaviors>
- </behaviors>
- <services>
- <service behaviorConfiguration="httpsBasicBindings" name="TestService.TestService">
- <endpoint address="" binding="basicHttpBinding" bindingConfiguration="test1" contract="TestService.ITestService">
- </endpoint>
- <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
- </service>
- </services>
- <bindings>
- <basicHttpBinding>
- <binding name="test1">
- <security mode="Transport">
- <transport clientCredentialType="None"/>
- </security>
- </binding>
- </basicHttpBinding>
- </bindings>
- </system.serviceModel>
- </configuration>
在serviceBehaviors里面的serviceMetadata配置中,一定要设置httpGetEnabled=“true”,之前我没有设置,想通过wsdl2java -uri https://127.0.0.1:9001/test?wsdl 来生成axis2的服务代理,结果一直生成不了。错误代码:
网上查了很多资料,大部分都是说Java调用WCF服务的过程中出现的这个错误,是证书没有导入到java运行环境的security容器中。结果用网上提供的方法试了N次,还是失败。
后来我反思,为什么要用https://127.0.0.1:9001/test?wsdl这个地址来生成代理呢?这个请求会被浏览器阻截,所以我没有获取wsdl文件,当然会失败。然后尝试用http://127.0.0.1:8889/test?wsdl这个地址来生成代理。就是上面设置的
- httpGetEnabled="true" httpGetUrl="http://127.0.0.1:8889/test"
这一段。然后生成成功。很高兴,以为就要成功了。然后进行测试。结果失败-_-!
测试代码如下:
- String resultString="";
- try {
- TestServiceStub client=new TestServiceStub(); client._getServiceClient().getOptions().setProperty(org.apache.axis2.transport.http.HTTPConstants.CHUNKED, Boolean.FALSE);
- //client._getServiceClient().getOptions().setTimeOutInMilliSeconds(5*60*1000);
- TestServiceStub.GetSysTime paraGetSysTime=new TestServiceStub.GetSysTime();
- try {
- TestServiceStub.GetSysTimeResponse response = client.GetSysTime(paraGetSysTime);
- resultString=response.getGetSysTimeResult();
- } catch (RemoteException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- } catch (AxisFault e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- System.out.print(resultString);
java的错误信息:
- org.apache.axis2.AxisFault: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
- at org.apache.axis2.AxisFault.makeFault(AxisFault.java:430)
- at org.apache.axis2.transport.http.AxisRequestEntity.writeRequest(AxisRequestEntity.java:98)
- at org.apache.commons.httpclient.methods.EntityEnclosingMethod.writeRequestBody(EntityEnclosingMethod.java:499)
- at org.apache.commons.httpclient.HttpMethodBase.writeRequest(HttpMethodBase.java:2114)
- at org.apache.commons.httpclient.HttpMethodBase.execute(HttpMethodBase.java:1096)
- at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:398)
- at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:171)
- at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397)
- at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:346)
- at org.apache.axis2.transport.http.AbstractHTTPSender.executeMethod(AbstractHTTPSender.java:542)
- at org.apache.axis2.transport.http.HTTPSender.sendViaPost(HTTPSender.java:189)
- at org.apache.axis2.transport.http.HTTPSender.send(HTTPSender.java:75)
- at org.apache.axis2.transport.http.CommonsHTTPTransportSender.writeMessageWithCommons(CommonsHTTPTransportSender.java:371)
- at org.apache.axis2.transport.http.CommonsHTTPTransportSender.invoke(CommonsHTTPTransportSender.java:209)
- at org.apache.axis2.engine.AxisEngine.send(AxisEngine.java:448)
- at org.apache.axis2.description.OutInAxisOperationClient.send(OutInAxisOperation.java:401)
- at org.apache.axis2.description.OutInAxisOperationClient.executeImpl(OutInAxisOperation.java:228)
- at org.apache.axis2.client.OperationClient.execute(OperationClient.java:163)
- at org.tempuri.TestServiceStub.GetSysTime(TestServiceStub.java:205)
- at test.main(test.java:42)
- Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
- at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)
- at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1611)
- at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:187)
- at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:181)
- at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1035)
- at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:124)
- at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Handshaker.java:516)
- at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Handshaker.java:454)
- at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:884)
- at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1112)
- at com.sun.net.ssl.internal.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:623)
- at com.sun.net.ssl.internal.ssl.AppOutputStream.write(AppOutputStream.java:59)
- at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
- at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
- at java.io.FilterOutputStream.flush(FilterOutputStream.java:123)
- at org.apache.axis2.transport.http.AxisRequestEntity.writeRequest(AxisRequestEntity.java:94)
- ... 18 more
- Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
- at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:285)
- at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:191)
- at sun.security.validator.Validator.validate(Validator.java:218)
- at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:126)
- at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:209)
- at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:249)
- at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1014)
- ... 29 more
- Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
- at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:174)
- at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:238)
- at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:280)
- ... 35 more
然后再到网上查找,结果发现就是之前找到的说是证书没有导入到java运行环境的security容器中的错误,于是再导入证书,
导入证书的步骤:
1,在IE中访问WSDL的URL,弹出“安全警报”窗口,查看证书->详细信息标签页->复制到文件->下一步->下一步->指定文件名,将证书下载保存为.cer文件,例如:test_axis.cer
2,用下载到的证书文件生成信任库文件:
$ keytool -import -file test_axis.cer -storepass changeit -keystore client.truststore -alias serverkey -noprompt
3,在调用WebService代码前指定信任库文件的路径:
System.setProperty("javax.net.ssl.trustStore", "/tmp/client.truststore");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
然后在java调用代码中加入
System.setProperty("javax.net.ssl.trustStore", "/tmp/client.truststore");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
修改之后的java代码:
- public static void main(String[] args) {
- String resultString="";
- try {
- TestServiceStub client=new TestServiceStub();
- //-----------------------------------------------------------------------
- System.setProperty("javax.net.ssl.trustStore", "D://java//jre//lib//security//hl7");
- System.setProperty("javax.net.ssl.trustStorePassword", "*******");
- //设置信任证书
- //-----------------------------------------------------------------------
- client._getServiceClient().getOptions().setProperty(org.apache.axis2.transport.http.HTTPConstants.CHUNKED, Boolean.FALSE);
- //client._getServiceClient().getOptions().setProperty(org.
- //client._getServiceClient().getOptions().setTimeOutInMilliSeconds(5*60*1000);
- TestServiceStub.GetSysTime paraGetSysTime=new TestServiceStub.GetSysTime();
- try {
- TestServiceStub.GetSysTimeResponse response = client.GetSysTime(paraGetSysTime);
- resultString=response.getGetSysTimeResult();
- } catch (RemoteException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- } catch (AxisFault e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- System.out.print(resultString);
- }
然后运行,终于成功了。
调用结果:
上次将证书加入WCF服务中去之后,调用也调试成功。这里最后将自定义客户验证加上去之后就是最终版本了。我也不想改了。估计我的毕业设计中关于WCF服务的构架也就是这样了。
好了,先来看下加入自定义客户端验证的服务配置文件:
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <system.serviceModel>
- <behaviors>
- <serviceBehaviors>
- <behavior name="httpsBasicBindings">
- <serviceMetadata httpsGetEnabled="true" httpGetEnabled="false" httpGetUrl="http://127.0.0.1:8889/test" />
- <serviceDebug includeExceptionDetailInFaults="true" />
- <serviceCredentials>
- <serviceCertificate storeName="My" x509FindType="FindBySubjectName" findValue="HL7Server" storeLocation="LocalMachine"/>
- <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="HostApp.UserValidate,HostApp" />
- </serviceCredentials>
- </behavior>
- </serviceBehaviors>
- </behaviors>
- <services>
- <service behaviorConfiguration="httpsBasicBindings" name="TestService.TestService">
- <endpoint address="" binding="basicHttpBinding" bindingConfiguration="test1" contract="TestService.ITestService">
- </endpoint>
- <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
- </service>
- </services>
- <bindings>
- <basicHttpBinding>
- <binding name="test1">
- <security mode="Transport">
- <transport clientCredentialType="Basic"/>
- </security>
- </binding>
- </basicHttpBinding>
- </bindings>
- </system.serviceModel>
- </configuration>
配置中间中
- <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="HostApp.UserValidate,HostApp" />
- 和
- <bindings>
- <basicHttpBinding>
- <binding name="test1">
- <security mode="Transport">
- <transport clientCredentialType="Basic"/>
- </security>
- </binding>
- </basicHttpBinding>
- </bindings>
说明了userNamePasswordValidationMode用户名密码验证是自己来定义的,clientCredentialType=Basic说明是基础的验证。
这2个配置一起控制了要进行验证,验证的方式由我们自己定义。
下面是自定义验证的代码:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.IdentityModel;
- using System.ServiceModel;
- namespace HostApp
- {
- class UserValidate : System.IdentityModel.Selectors.UserNamePasswordValidator
- {
- public override void Validate(string userName, string password)
- {
- if (userName != "cch" || password != "cch")
- {
- throw new System.IdentityModel.Tokens.SecurityTokenException("用户名密码错误!");
- }
- else
- {
- }
- }
- }
- }
这边账号密码都是cch,如果调用的时候账号密码不正确,就会返回
org.apache.axis2.AxisFault: Transport error: 401 Error: Unauthorized
这个错误。
java调用代码如下:
- import java.rmi.RemoteException;
- import org.apache.axis2.AxisFault;
- import org.apache.axis2.transport.http.HttpTransportProperties;
- import org.tempuri.TestServiceStub;
- //import com.sun.mail.handlers.image_gif;
- public class test {
- /**
- * @param args
- */
- public static void main(String[] args) {
- String resultString="";
- try {
- TestServiceStub client=new TestServiceStub();
- //-----------------------------------------------------------------------
- System.setProperty("javax.net.ssl.trustStore", "D://java//jre//lib//security//hl7");
- System.setProperty("javax.net.ssl.trustStorePassword", "119215277");
- //设置信任证书
- //-----------------------------------------------------------------------
- //-----------------------------------------------------------------------
- HttpTransportProperties.Authenticator authenticator=new HttpTransportProperties.Authenticator();
- authenticator.setUsername("cch");
- authenticator.setPassword("cch");
- //设置Basic认证
- //-----------------------------------------------------------------------
- client._getServiceClient().getOptions().setProperty(org.apache.axis2.transport.http.HTTPConstants.CHUNKED, Boolean.FALSE);
- client._getServiceClient().getOptions().setProperty(org.apache.axis2.transport.http.HTTPConstants.AUTHENTICATE, authenticator);
- //client._getServiceClient().getOptions().setTimeOutInMilliSeconds(5*60*1000);
- TestServiceStub.GetSysTime paraGetSysTime=new TestServiceStub.GetSysTime();
- try {
- TestServiceStub.GetSysTimeResponse response = client.GetSysTime(paraGetSysTime);
- resultString=response.getGetSysTimeResult();
- } catch (RemoteException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- } catch (AxisFault e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- System.out.print(resultString);
- }
- }
调用结果如图: