Early last year, Microsoft released a new version of their CRM product, Dynamics CRM 2011. My company has a Java product that integrates with MS CRM and we were successfully able to call the webservice that is included with CRM 2011 using SAML tokens. Everything was working great until we reconfigured CRM for on-premise authentication. In the previous version of MS CRM, the on-premise authentication used transport layer NTLM/Kerberos authentication which Java supports. With CRM 2011, however, Microsoft is using WCF 4.0 which uses message level NTLM/Kerberos based on SPNEGO WS-Trust. (Specification) We searched extensively, but could not find an implementation of this protocol for any open source Java Web Service framework. So I along with a coworker set out to implement this for CXF. After several weeks, we were successful and were able to invoke the CRM web service from Java. We submitted this functionality back to the CXF project. (JIRA) Recently, Colm O hEigeartaigh was kind enough to review the patch and integrate it into the trunk of the CXF codebase. (With some major refactoring to make it fit better) This works out of the box with the latest CXF code, but the configuration of this is far from trivial, so this blog post explains how to configure this new functionality.
CXF and MS CRM 2011
The first step in getting this all to work is to setup Java to authenticate using Kerberos with the Active Directory primary domain controller. This requires you to create a login.conf as follows:
spnego-client {
com.sun.security.auth.module.Krb5LoginModule required;
};and a krb5.conf as follows:
[libdefaults]
default_realm = <NTdomain>
default_tkt_enctypes = aes128-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
default_tgs_enctypes = aes128-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
permitted_enctypes = aes128-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
[realms]
<NTdomain> = {
kdc = <PrimaryDomainController>
default_domain = <NTdomain>
}
[domain_realm]
.<NTdomain> = <NTdomain>
Then, in my Java code, I manually configure the following the system properies so that JAAS will use these files:
System.setProperty("java.security.auth.login.config", "C:\\projects\\login.conf");
System.setProperty("java.security.krb5.conf", "C:\\projects\\krb5.conf");
CRM 2011 includes a custom policy element in the WSDL that needs to be asserted in CXF. If this is not done, CXF will error out because it doesn't have a policy provider to assert the custom policy. This custom policy can be asserted using the following classes, the first is the PolicyProvider itself:
public class XRMAuthPolicyProvider extends AbstractPolicyInterceptorProvider
{
public XRMAuthPolicyProvider()
{
super(Arrays
.asList(new QName[]{new QName("http://schemas.microsoft.com/xrm/2011/Contracts/Services",
"AuthenticationPolicy", "ms-xrm")}));
getInInterceptors().add(new XRMAuthPolicyInterceptor());
}
}
and the second is the associated CXF interceptor:
public class XRMAuthPolicyInterceptor extends AbstractSoapInterceptor
{
public XRMAuthPolicyInterceptor()
{
super(Phase.PRE_PROTOCOL);
addAfter(PolicyBasedWSS4JInInterceptor.class.getName());
addAfter(PolicyBasedWSS4JOutInterceptor.class.getName());
}
public void handleMessage(SoapMessage message) throws Fault
{
AssertionInfoMap aim = message.get(AssertionInfoMap.class);
if (null == aim)
{
return;
}
QName qname = new QName("http://schemas.microsoft.com/xrm/2011/Contracts/Services",
"AuthenticationPolicy", "ms-xrm");
Collectionais = aim.get(qname);
if (null == ais || ais.size() == 0)
{
return;
}
for (AssertionInfo ai : ais)
{
ai.setAsserted(true);
}
}
}
The policy provider can be added to web service client as follows:
Client client = ClientProxy.getClient(port);
Bus bus = ((EndpointImpl) client.getEndpoint()).getBus();
PolicyInterceptorProviderRegistry pipr = bus
.getExtension(PolicyInterceptorProviderRegistry.class);
pipr.register(new XRMAuthPolicyProvider());
Once the policy provider is added, CXF needs to be configured to use Kerberos authentication. For my setup, I was using an Active Directory username and password, so I had to create an implementation of SpnegoClientAction that uses GSSName.NT_USER_NAME rather than the default GSSName.NT_HOSTBASED_SERVICE:
public class XRMSpnegoClientAction implements SpnegoClientAction
{
private org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
.getLog(DefaultSpnegoClientAction.class);
protected String serviceName;
protected GSSContext secContext;
protected boolean mutualAuth;
/**
* Whether to enable mutual authentication or not.
*/
public void setMutualAuth(boolean mutualAuthentication)
{
mutualAuth = mutualAuthentication;
}
/**
* The Service Name
*/
public void setServiceName(String serviceName)
{
this.serviceName = serviceName;
}
/**
* Obtain a service ticket
*/
public byte[] run()
{
try
{
GSSManager gssManager = GSSManager.getInstance();
Oid oid = new Oid("1.3.6.1.5.5.2");
GSSName gssService = gssManager.createName(serviceName, GSSName.NT_USER_NAME);
secContext = gssManager.createContext(gssService, oid, null, GSSContext.DEFAULT_LIFETIME);
secContext.requestMutualAuth(mutualAuth);
secContext.requestCredDeleg(Boolean.FALSE);
byte[] token = new byte[0];
return secContext.initSecContext(token, 0, token.length);
}
catch (GSSException e)
{
if (log.isDebugEnabled())
{
log.debug("Error in obtaining a Kerberos token", e);
}
}
return null;
}
/**
* Get the GSSContext that was created after a service ticket was obtained
*/
public GSSContext getContext()
{
return secContext;
}
}
Then I added the following code to hook Kerberos into my web service client:
// Active Directory domain username and password
final String username = "<username>";
final String password = "<password>";
// Kerberos service provider name, e.g. RestrictedKrbHost/<computername>
String spn = "RestrictedKrbHost/<crm_server>";
// Kerberos JAAS client as configured in login.conf
String jaasClient = "spnego-client";
// Active Directory username and password
CallbackHandler callbackHandler = new NamePasswordCallbackHandler(username, password);
client.getRequestContext().put("ws-security.kerberos.jaas.context", jaasClient);
client.getRequestContext().put("ws-security.kerberos.spn", spn);
client.getRequestContext().put("ws-security.callback-handler", callbackHandler);
client.getRequestContext().put("ws-security.spnego.client.action", new XRMSpnegoClientAction());
Once the custom policy provider is added and the Kerberos authentication is setup, the client configuration is complete.
For reference, the complete client code example is below:
URL wsdlURL = OrganizationService.WSDL_LOCATION;
System.setProperty("java.security.auth.login.config", "C:\\projects\\login.conf");
System.setProperty("java.security.krb5.conf", "C:\\projects\\krb5.conf");
// Active Directory domain username and password
final String username = "<username>";
final String password = "<password>";
// Kerberos service provider name, e.g. RestrictedKrbHost/<computername>
String spn = "RestrictedKrbHost/<crm_server>";
// Kerberos JAAS client as configured in login.conf
String jaasClient = "spnego-client";
// Active Directory username and password
CallbackHandler callbackHandler = new NamePasswordCallbackHandler(username, password);
OrganizationService ss = new OrganizationService(wsdlURL, SERVICE_NAME);
IOrganizationService port = ss.getCustomBindingIOrganizationService();
Client client = ClientProxy.getClient(port);
Bus bus = ((EndpointImpl) client.getEndpoint()).getBus();
PolicyInterceptorProviderRegistry pipr = bus
.getExtension(PolicyInterceptorProviderRegistry.class);
pipr.register(new XRMAuthPolicyProvider());
client.getRequestContext().put("ws-security.kerberos.jaas.context", jaasClient);
client.getRequestContext().put("ws-security.kerberos.spn", spn);
client.getRequestContext().put("ws-security.callback-handler", callbackHandler);
client.getRequestContext().put("ws-security.spnego.client.action", new XRMSpnegoClientAction());
// call webservice
ColumnSet colSet = new ColumnSet();
colSet.setAllColumns(true);
colSet.setColumns(new ArrayOfstring());
QueryByAttribute qba = new QueryByAttribute();
qba.setEntityName("account");
qba.setColumnSet(colSet);
ArrayOfstring aos2 = new ArrayOfstring();
aos2.getString().add("accountid");
qba.setAttributes(aos2);
ArrayOfanyType aoat = new ArrayOfanyType();
aoat.getAnyType().add("2D08FEC1-9734-E111-9454-000C295D5DEA");
qba.setValues(aoat);
EntityCollection entityCol = port.retrieveMultiple(qba);
Listprops = entityCol.getEntities().getEntity().get(0)
.getAttributes().getKeyValuePairOfstringanyType();
for (KeyValuePairOfstringanyType prop : props)
{
if ("name".equals(prop.getKey()))
{
System.out.println(prop.getKey() + " = " + prop.getValue());
}
}
Since our team spent several weeks working on this example, I hope this walkthrough is helpful for other developers out there. This will not only work for CRM 2011, but any WCF webservice that uses message level NTLM/Kerberos authentication.
Tom, could you provide more information about your IIS configuration. I acquired TGT using AS, and -commit succeeded.
ReplyDeleteThen i have such exception:
org.apache.cxf.interceptor.Fault: General security error (An error occurred in trying to obtain a service ticket)
Thanks!
Hi Dimitry, have you solved this problem? We're facing the same.
DeleteSadly, I was not involved in setting up the MS CRM instance. That was handled by our netops team, so I don't have access to those details.
ReplyDeleteHi tom,
ReplyDeletein your class XRMSpnegoClientAction you implements SpnegoClientAction but in wss4j SpnegoClientaction is a claas
Can you help me to resolve the problem ?
Thanks
You need the very latest trunk code for wss4j. If you look at trunk, it is an interface in the latest version:
ReplyDeletehttp://svn.apache.org/viewvc/webservices/wss4j/trunk/src/main/java/org/apache/ws/security/spnego/SpnegoClientAction.java?revision=1227128&view=markup
This is all very bleeding edge, so you'll need the trunk versions of both CXF and wss4j.
This comment has been removed by the author.
ReplyDeleteThank you, Tom!
ReplyDeleteNice work, helpful message.
But unfortunately I've faced a problem:
my (ColumnSet) colSet variable has not a method setEntityName(String), but a setEntityName(JAXBElement&<String>) instead.
It concerns also several analogous setters.
Wat's wrong?
I've generated from wsdl using cxf library.
Thank you.
Ok, I've fixed previous type mismatch problem by creating corresponding JAXBElement<T> type variables instead of T type
ReplyDeleteusing values of @XmlElementRef notations in corresponding classes of package com.microsoft.schemas.xrm._2011.contracts.
But when running the sample, I've faced with error at the earliest stages of it.
Namely, at the line where IOrganizationService port is being obtained (it's just line 47 in my ServiceTest.java):
IOrganizationService port = ss.getCustomBindingIOrganizationService()
the exception appears:
-----------
java.lang.NoSuchFieldError: QUALIFIED
at org.apache.cxf.service.model.SchemaInfo.setSchema(SchemaInfo.java:146)
at org.apache.cxf.wsdl11.SchemaUtil.extractSchema(SchemaUtil.java:136)
at org.apache.cxf.wsdl11.SchemaUtil.getSchemas(SchemaUtil.java:81)
at org.apache.cxf.wsdl11.SchemaUtil.getSchemas(SchemaUtil.java:65)
............
at org.apache.cxf.jaxws.ServiceImpl.createPort(ServiceImpl.java:465)
at org.apache.cxf.jaxws.ServiceImpl.getPort(ServiceImpl.java:332)
at org.apache.cxf.jaxws.ServiceImpl.getPort(ServiceImpl.java:319)
at javax.xml.ws.Service.getPort(Service.java:92)
at com.microsoft.schemas.xrm._2011.contracts.OrganizationService.getCustomBindingIOrganizationService(OrganizationService.java:65)
at com.mypackage.msdynamics.onpremise.ServiceTest.run(ServiceTest.java:47)
-----------
Can anyone help me and explain what goes wrong?
Thank you.
Double check your JAXB version. The JAXB version used with the latest version of CXF is newer than what comes with the JDK 6. It takes some effort to get it all working because of this. (The JAXB jars have to be 'endorsed' to override what comes with the JDK) If that doesn't resolve the issue, it may help to look at the cxf source at the line in error--it might give you some ideas on what's wrong. This is all very experimental, so you definitely have to get your hands dirty to get it all working.
ReplyDeleteTom, thank you.
ReplyDeleteAt least now I know where to start.
Hi, Tom.
ReplyDeleteThanks to your previous advice, I've solved my problem with type inconsistency and simultaneously have overcome some another problems with inappropriate libraries and cast type errors.
But I'm stacking now because of null value of the pipr variable: my bus is occurred not to have an extension of type PolicyInterceptorProviderRegistry:
PolicyInterceptorProviderRegistry pipr = bus
.getExtension(PolicyInterceptorProviderRegistry.class); // -----> null
Can you suppose, please, what's wrong?
Any idea may be helpful.
Thank you.
Hmm, not sure. I was always able to get the PolicyInterceptorProviderRegistry without issue. It might be a new change with the latest code or something might not be getting initialized properly. You'll have to debug through the code to see where that gets initialized in CXF--I'm not familiar with that part of the code.
ReplyDeleteHello Tom,
ReplyDeleteI've tried to execute your code, but I've got 2 warnings:
19.09.2012 15:54:51 org.apache.cxf.ws.policy.AssertionBuilderRegistryImpl handleNoRegisteredBuilder
WARNING: No assertion builder for type {http://schemas.microsoft.com/xrm/2011/Contracts/Services}AuthenticationPolicy registered.
19.09.2012 15:54:54 org.apache.cxf.phase.PhaseInterceptorChain doDefaultLogging
WARNING: Interceptor for {http://schemas.xmlsoap.org/ws/2005/02/trust/wsdl}SecurityTokenService#{http://schemas.xmlsoap.org/ws/2005/02/trust/wsdl}RequestSecurityToken has thrown exception, unwinding now
org.apache.cxf.interceptor.Fault: Unexpected element {http://schemas.xmlsoap.org/ws/2005/02/trust}RequestSecurityTokenResponseCollection found. Expected {http://schemas.xmlsoap.org/ws/2005/02/trust}RequestSecurityTokenResponse.
The programm aborts at the line:
EntityCollection entityCol = port.retrieveMultiple(qba);
I've used eclipse juno, jdk 1.6.35 64bit and cxf 2.6.2
I have no idea, what's wrong with the code and I can't find the error by debugging. I have used the code from the homepage, only the credential and connection-entries are changed.
Have you any idea?
Thank you.
Looks like CXF didn't like the response from CRM. What version of CRM are you using? Also, you might want to compare the response to what CXF is expecting. (It wouldn't surprise me is there are valid responses that CXF isn't handling properly)
ReplyDeletehi Tom!
ReplyDeleteWe at our company are looking to integrate the CRM 2011 webservice from a java application. We do not have the on-premise authentication setup because this is not needed.
Can you give me some more information on how you managed to communicate with the webservice using SAML tokens?
thank you very much!!
Hi Tom,
ReplyDeleteI have the same problem at the same point as jja77 =/
This is where it goes wrong according to the logs :
FEIN: Invoking handleMessage on interceptor org.apache.cxf.binding.soap.interceptor.CheckFaultInterceptor@6ec44aaf
11.10.2012 20:40:56 org.apache.cxf.phase.PhaseInterceptorChain doIntercept
FEIN: Invoking handleMessage on interceptor org.apache.cxf.interceptor.URIMappingInterceptor@2c91e143
11.10.2012 20:40:56 org.apache.cxf.interceptor.URIMappingInterceptor handleMessage
FEIN: Invoking HTTP method null
11.10.2012 20:40:56 org.apache.cxf.interceptor.URIMappingInterceptor handleMessage
FEIN: URIMappingInterceptor can only handle HTTP GET, not HTTP null
11.10.2012 20:40:56 org.apache.cxf.phase.PhaseInterceptorChain doIntercept
FEIN: Invoking handleMessage on interceptor org.apache.cxf.interceptor.DocLiteralInInterceptor@2d83e895
11.10.2012 20:40:56 org.apache.cxf.phase.PhaseInterceptorChain unwind
FEIN: Invoking handleFault on interceptor org.apache.cxf.interceptor.DocLiteralInInterceptor@2d83e895
11.10.2012 20:40:56 org.apache.cxf.phase.PhaseInterceptorChain unwind
any thoughts on this?
I really have no idea. I don't think we ran into anything like that. I don't have access to the CRM environments anymore, did you look at the request/response in Fiddler? That might give you some idea about what's going on. (Or turning up the logging for CXF to see if there's any clues there)
ReplyDeleteHi Tom,
ReplyDeletejust FYI the problem seems to be this:
org.apache.cxf.interceptor.Fault: Unexpected element {http://schemas.xmlsoap.org/ws/2005/02/trust}RequestSecurityTokenResponseCollection found. Expected {http://schemas.xmlsoap.org/ws/2005/02/trust}RequestSecurityTokenResponse.
now I looked at the schema at http://schemas.xmlsoap.org/ws/2005/02/trust
for ws-trust.
There he seems to find this Collection of responses:
instead of just a singular response.
Any thoughts on this =/?
Somehow i cannot post the xml here..... =/
ReplyDeleteso I´ll put it in words.
At the http://schemas.xmlsoap.org/ws/2005/02/trust xml he finds a RequestSecurityTokenResponseCollection element instead of a singular RequestSecurityTokenResponse element.
I would take this issue up with the CXF user list. Seems like either CXF isn't doing something correctly or there might be more configuration needed. I haven't looked at the code recently enough to be of any help.
ReplyDeleteHi Tom,
ReplyDeletemaybe I can get your input on our login.conf file.
This is what we use:
spnego-client {
com.sun.security.auth.module.Krb5LoginModule
required
useSubjectCredsOnly=false
useTicketCache=true
doNotPrompt=true;
};
What´s your take on this?
Here's what I used--you'll have to substitute your values for your windows domain.
ReplyDeletelogin.conf:
spnego-client {
com.sun.security.auth.module.Krb5LoginModule required;
};
krb5.conf (if EXAMPLE.COM is the windows domain suffix):
[libdefaults]
default_realm = EXAMPLE.COM
default_tkt_enctypes = aes128-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
default_tgs_enctypes = aes128-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
permitted_enctypes = aes128-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
[realms]
EXAMPLE.COM = {
kdc =
default_domain = EXAMPLE.COM
}
[domain_realm]
.EXAMPLE.COM = EXAMPLE.COM
We forced these files to be used by explicitly setting the properties in our client code:
System.setProperty("java.security.auth.login.config", "C:\\projects\\login.conf");
System.setProperty("java.security.krb5.conf", "C:\\projects\\krb5.conf");
Sorry the realms section above was mangled by the filters:
ReplyDelete[realms]
EXAMPLE.COM = {
kdc = <WINDOWS_DOMAIN_CONTROLLER>
default_domain = EXAMPLE.COM
}
Hmm ... we have the same... =/
ReplyDeleteCrm 2011 is working with WS-Trust 1.3 right?
I just tried this with the latest CXF (2.7.0) and I get the same exception as you:
ReplyDeleteCaused by: org.apache.cxf.interceptor.Fault: Unexpected element {http://schemas.xmlsoap.org/ws/2005/02/trust}RequestSecurityTokenResponseCollection found. Expected {http://schemas.xmlsoap.org/ws/2005/02/trust}RequestSecurityTokenResponse.
Looks like some of the changes to the STSClient didn't make it in or aren't working correctly. It would be best to submit this as a bug to CXF itself. Here's a link to the original SPNEGO patch:
https://issues.apache.org/jira/browse/CXF-3635
Hi Tom,
ReplyDeletethanks for the patch info.
We have implemented it and seem to have gotten a little further, at least as far as my understanding is concerned^^.
But I cannot find the apache cxf version that you seemed to have been working with.
I have tried many and always received a different build error.
I could build it with version 2.3.8 and some own changes to the code.
What version did you use?
The code seems to work out of the box with cxf 3.5.0
ReplyDeleteBut I have received following error :
None of the policy alternatives can be satisfied.
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:146)
at $Proxy45.execute(Unknown Source)
at com.example.spnego.Main.main(Main.java:121)
I assume the SpnegoSTSClient who builds the XML to request the SecurityToken is not able to match crm´s policy?
Where did you get CXF 3.5.0? (typo maybe?) The latest version I see on the download page is 2.7.0: http://cxf.apache.org/download.html
ReplyDeleteOh sorry,
ReplyDeleteindeed a typo^^.
I meant cxf 2.3.5
Does this scenario also work for AD-authentication without sts?
Jair, this is AD-authentication without sts. The STSClient is used to negotiate SPNEGO directly with the endpoint. We're just reusing a lot of the code in the STSClient because the requests/responses are the same as talking to an STS.
ReplyDeleteOK,
ReplyDeletedo you have any idea why I receive the policy exception =/?
Are you sure you have the policty provider registered?
ReplyDeletepipr.register(new XRMAuthPolicyProvider());
If the policy provider isn't there, I get the same exception.
I was finally able to get this working using CXF 2.7.0. I had to add the following to disable the additional validation that's happening with the new version of CXF. (There might be an easier way to set this property, but this is the only way I found to get it to work)
ReplyDeleteSTSClient sts = new STSClient(bus);
sts.setFeatures(Arrays.asList(new Feature()
{
@Override
public void initialize(Server server, Bus bus)
{
}
@Override
public void initialize(Client client, Bus bus)
{
ServiceInfo si = client.getEndpoint().getEndpointInfo().getService();
si.setProperty("soap.force.doclit.bare", true);
}
@Override
public void initialize(InterceptorProvider interceptorProvider, Bus bus)
{
}
@Override
public void initialize(Bus bus)
{
}
}));
client.getRequestContext().put("ws-security.sts.client", sts);
Hi Tom, this indeed solved the issue RSTR Collection in cxf 2.7.0 =)
ReplyDeletedoes this only return the second leg in spnego ? Or why is the response suddenly not a collection anymore.
My problems don´t seem to come to an end though. now that I applied your fix for the RSTR-C issue i get this error :
org.apache.cxf.ws.policy.PolicyException: Cannot encrypt data
at org.apache.cxf.ws.security.wss4j.policyhandlers.AbstractBindingBuilder.policyNotAsserted(AbstractBindingBuilder.java:290)
Is this a password encryption problem?
P.S: Sorry to keep bugging you with my problems.
I am kind of new to the whole Java to CRM thing and you are the only one who could give me any good answers so far.
Jair, the response is still a RSTR Collection, it just skips the extra validation that is throwing the exception. I truly think this is a bug and it should expect both a RSTR or a RSTR Collection, but it would take more investigation to prove that.
ReplyDeleteI don't think it's a password encryption problem. I think you need to install the Java Cryptography Extensions Unlimited Strength Jurisdiction Policy for the JRE/JDK you're using. CRM requires stronger encryption than what the JRE/JDK can do by default.
Hi Tom,
ReplyDeletethanks for the information.
Regarding the Cryptography library i found the following:
if i don´t add the Cryptography Extensions i get this error in the log:
java.lang.ClassNotFoundException: org.bouncycastle.jce.provider.BouncyCastleProvider
If i copy the Cryptography extensions jar files into my jdk\lib folder the above exception vanishes, but my problem still persists in the same way.
i use jre6 and the respective Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 6
Try removing the bouncy castle jars from your classpath. The newer JDK's come with a JCE provider, so bouncy castle shouldn't be necessary. (And it seems like bouncy castle impl is not unlimited strength which might be why it's still failing for you)
ReplyDeleteHi Tom,
ReplyDeleteI had aleady removed the bouncy castle jars when I added the Cryptography extensions to my runtime lib folder.
this is what my class path looks like:
(I ad to remove the ">" and "<" because of the filters =/)
xml version="1.0" encoding="UTF-8"
classpath
classpathentry kind="src" path=""/
classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/
classpathentry kind="con" path="org.eclipse.jst.ws.cxf.core.CXF_CLASSPATH_CONTAINER/Apache CXF/2.7.0"
attributes
attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/
/attributes
/classpathentry
classpathentry kind="lib" path="C:/Apps/Dev/apache-cxf-2.7.0/bin"/
classpathentry kind="output" path=""/
/classpath"
is there any way to force cxf to use the cryptography extensions?
Btw,
ReplyDeletedoes this encryption problem have anything to do with using Using X.509 Certificates?
Because in our scenario there is no certificate involved. I was reading the
http://cxf.apache.org/docs/ws-security.html documentation and could only find information on encryption regarding certificates.
Do I have to set some property in cxf on false so it doesn´t try to use a certificate, or does cxf have a fallback mechanism for this case?
To me the error resembles this one a lot:
ReplyDeletehttps://jira.talendforge.org/browse/TESB-3768?page=com.atlassian.jira.plugin.system.issuetabpanels%3Achangehistory-tabpanel
Jair, it's hard to say what might be causing it since I can't reproduce this. It's working for me and we've checked all the obvious stuff. Your best bet is to grab the source code for CXF and debug through this issue to see where it's getting hung up. There might be a root cause to the exception or you might find more information about what's going wrong.
ReplyDeleteAnother thing you might want to try is to create a stand-alone Java program rather than running this within a war project. That's what I typically do to get it working first before I integrate it into a project. It's easier to debug and quicker to try different things.
Hi Tom,
Deletethanks for your answer on a saturday =). I will look into the cxf code and try debug it to find the root cause for my exception.
I am thinking I might be doing something wrong outside of the cxf code. Can you maybe post an examlple of the OrganizationRequest you sent to your crm server to test the connection?
Because i just send a WhoAmI request like this:
URL wsdlURL = OrganizationService.WSDL_LOCATION;
OrganizationService ss = new OrganizationService(wsdlURL);
IOrganizationService port = ss.getCustomBindingIOrganizationService();
OrganizationRequest tmp = new OrganizationRequest();
tmp.setRequestName("WhoAmI");
port.execute(tmp);
Hi Tom,
ReplyDeleteI have tried to get the cxf source code running,
but many classes are missing in the svn trunk.
Like org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor;
and
org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
Are those classes located elsewhere?
Those are part of wss4j, a separate project:
ReplyDeletehttp://ws.apache.org/wss4j/
Hi Tom,
ReplyDeletei assume you have already once imported the cxf lib into eclipse. On the cxf website it says, that when you checkout the trunk from svn at
http://svn.apache.org/repos/asf/cxf/trunk ,
you can import all the subprojects into eclipse.
My eclipse doesn´t find any projects inside the whole trunk =/
Am I missing something here? Because i tried to import the cxf.apache* packages manually, but the load of errors make it practically impossible to get the code running this way.
Hi Tom,
ReplyDeletejust ignore my last question. I have solved the encryption issue.
I now get an unmarshalling error because of
WARNING: Interceptor for {http://schemas.microsoft.com/xrm/2011/Contracts}OrganizationService#{http://schemas.microsoft.com/xrm/2011/Contracts/Services}Execute has thrown exception, unwinding now
org.apache.cxf.interceptor.Fault: Unmarshalling Error: unknown typename: {http://schemas.microsoft.com/2003/10/Serialization/}guid
It seems like I am an unlucky guy :D
Hi Jair,
DeleteI too am facing the encryption issue. Are you able to share how you went about solving the problem?
Thanks (And also thanks to Tom for this very useful article!)
Hi Matthew,
DeleteSorry for my late answer here.
If you are facing the encryption issue, you will have to install the "Java Cryptographic Extensions unlimited strength" for the JRE you are using.
In this case installing means replacing the "local_policy.jar" and "US_export_policy.jar" in the security folder of your JRE with the ones delivered with the JCE Unlimited strength package.
After that you should be aware, that if the problem still persists - although you replaced the JCE with JCE Unl. strength - the organization request that you use is probably faulty.
Please let me know if this solved your problem
When I look at the
ReplyDeletehttp://schemas.microsoft.com/2003/1/Serialization/
schema, there is indeed no element called "guid".
how did you work around this issue?
Did you bind guid to String somehow?
I have this in my Object factory in the respective schema:
ReplyDeleteprivate final static QName _Guid_QNAME = new QName("http://schemas.microsoft.com/2003/10/Serialization/", "guid");
Here's an example request that's working for me:
ReplyDeleteColumnSet columnSet = new ColumnSet();
columnSet.setAllColumns(true);
columnSet.setColumns(new ArrayOfstring());
Guid guid = new Guid();
guid.setValue("{GUID_AS_STRING}"); // replace with your guid
Entity entity = port.retrieve("account", guid, columnSet);
Hi Tom,
ReplyDeletewhen I generate my classes from the organization wsdl, it doesn´t generate the Class Guid you seem to have. I assume you used a binding file. when u generated the wsdl classes, to bind the jaxb components. Can you maybe post your bindings.txt?
Hi Tom,
ReplyDeleteI have found the problem.
Somehow when i create my java classes from the organization wsdl, Guid and other classes aren´t built. But the unmarshaller can only unmarshall through the object factory of com.microsoft.schemas._2003._10_serialization, which should refer to classes like "Guid" "Duration" etc. But it binds these classes to String =/. Is this a JAXB Binding issue? how does your binding.txt look?
I don't remember doing anything special when generating the java classes from the wsdl. (no binding.txt needed) Are you sure you're using the correct wsdl?
ReplyDeleteI'm actually having the same problem as Jair, no Guid class was generated. Have you already solved this issue? I generated the classes using this wsdl urls:
ReplyDeletewsdl2java -client -b javabindings.xml http://localhost:5555/Demo/XRMServices/2011/Organization.svc?wsdl
The "Demo" part in the URL is the name of my CRM-organization.
I had to use a binding file in order to avoid JAXBElement<String> bindings. The binding file I used is the following:
<jaxb:bindings version="2.1"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<jaxb:globalBindings generateElementProperty="false"/>
</jaxb:bindings>
I couldn't get the wsdl2java to generate those classes
ReplyDeleteeither. At the end I just wrote the classes I needed myself, what isn't difficult. Just define your class with the right name and specify a Return method method with the data type you need. You can take a look at the generated Array type classes for a draft
Hello Tom,
ReplyDeleteI'm currently building an integration to Microsoft CRM using java, i'm having lots of problems. Can you point me to any documentation about the webservices classes that are generated from the wsdl2java?
At this point in time, i can not rewrite in java this lines of C#
Owner owner = new Owner();
owner.type = EntityName.systemuser.ToString();
owner.Value = user.UserId;
Can you help me?
thx
I have an example CRM call in this very blog, check the 2nd half of the last code block. We didn't do much with owner, our work was more around the Contact, Quote and Opportunity entities.
ReplyDeleteHi Miguel,
ReplyDeleteto change owner you have to fire an Organization Request with RequestName="Assign" and two Parameters: Assignee and Target, both EntityReferences
Thx Tom,
ReplyDeleteI've solved my problem using an entity reference to the "systemuser" entity.
Do you have any kind of documentation about the java classes generated by the wsdl2java?
regards
Not really. Microsoft has documentation on their webservices, but it will be generic and not specific to what the jaxb generated classes. That's the closest you'll find, but you have to translate it a bit to get it to work with the wsdl2java generated classes.
ReplyDeleteHi Tom,
ReplyDeletethank you for this blog! I am now able to access the webservie without problems. A hint for anybody trying to access the CRM 2011 Webservice with SSL enabled is to use the new CXF Version 2.7.3 as documented here https://issues.apache.org/jira/browse/CXF-4758
Hi Tom and the rest,
ReplyDeleteI have been struggling with this for quite some time and have managed to get it working somehow. When I provide a valid request, e.g. a Retrieve with an Id that exists, I do get a valid response. However if I change that retrieve using an id that does not exist I get an error. The service is returning a SOAP Fault as far as I understand, at least it is an HTTP 500 message, but the client fails to handle the SoapFault.
Here are some of the error messages:
org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor handleMessage
WARNING:
org.apache.ws.security.WSSecurityException: The signature or decryption was invalid
at org.apache.ws.security.processor.SignatureProcessor.verifyXMLSignature(SignatureProcessor.java:450)
...
Caused by: org.apache.ws.security.WSSecurityException: The signature or decryption was invalid
at org.apache.ws.security.processor.SignatureProcessor.verifyXMLSignature(SignatureProcessor.java:450)
at org.apache.ws.security.processor.SignatureProcessor.handleToken(SignatureProcessor.java:231)
at org.apache.ws.security.WSSecurityEngine.processSecurityHeader(WSSecurityEngine.java:396)
at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.handleMessage(WSS4JInInterceptor.java:274)
... 19 more
javax.xml.ws.soap.SOAPFaultException: The signature or decryption was invalid
at org.apache.cxf.jaxws.DispatchImpl.mapException(DispatchImpl.java:287)
at org.apache.cxf.jaxws.DispatchImpl.invoke(DispatchImpl.java:392)
at org.apache.cxf.jaxws.DispatchImpl.invoke(DispatchImpl.java:243)
...
Caused by: org.apache.cxf.binding.soap.SoapFault: The signature or decryption was invalid
at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.createSoapFault(WSS4JInInterceptor.java:760)
at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.handleMessage(WSS4JInInterceptor.java:331)
at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.handleMessage(WSS4JInInterceptor.java:93)
I have Googled this and found a some references to this specifying that it might be a bug in wss4j or xmlsec. I am using wss4j-1.6.9.jar and xmlsec-1.5.4.jar.
I see that Jair mentions a similar error message above and suggests that providing a correct request will fix it.
My request is valid, the only difference between a successful response and an unsuccessful is that I provide an Id that does not exist in CRM.
The client should be able to handle a SoapFault.
Any ideas?
Regards
Ørjan
Hi Ørjan,
Deletei have experienced your problem too. I think the problem is, that CRM does not encrypt its response if it is a fault. My solution was to install a FaultInterceptor which gets the original (unencrypted) SOAP-Message, extracts the fault cause, and creates a new SoapFault containing the real reason of fault.
This is only a workaround, but at least it returns senseful information.
public class CRMFaultInterceptor extends AbstractSoapInterceptor {
public CRMFaultInterceptor() {
super(Phase.PRE_PROTOCOL);
addBefore(PolicyBasedWSS4JInInterceptor.class.getName());
@Override
public void handleMessage(SoapMessage arg0) throws Fault {
}
@Override
public void handleFault(SoapMessage m) {
XMLStreamReader reader = m.getContent(XMLStreamReader.class);
W3CDOMStreamReader w3r = (W3CDOMStreamReader) reader;
String errorMessage = null;
if (w3r != null) {
try {
while (w3r.hasNext()) {
w3r.next();
if (w3r.getCurrentNode().getLocalName().equals("Body")) {
NodeList l = w3r.getCurrentNode().getChildNodes();
for (int i = 0; i < l.getLength(); i++) {
Node n = l.item(i);
if (n.getLocalName().equals("Fault")) {
NodeList l1 = n.getChildNodes();
for (int j = 0; j < l1.getLength(); j++) {
Node n1 = l1.item(j);
if (n1.getLocalName().equals("Reason")) {
NodeList l2 = n1.getChildNodes();
for (int k = 0; k < l2.getLength(); k++) {
Node n2 = l2.item(k);
if (n2.getLocalName()
.equals("Text")) {
errorMessage = n2
.getTextContent();
}
}
}
}
}
}
}
}
if (errorMessage != null) {
Exception e = m.getContent(Exception.class);
if (e instanceof SoapFault) {
SoapFault fault = (SoapFault) e;
SoapFault newFault = new SoapFault(errorMessage, fault,
fault.getFaultCode());
m.setContent(Exception.class, newFault);
}
}
} catch (XMLStreamException e) {
e.printStackTrace();
}
}
}
Sorry, while copying i lost two brackets, one after the constructor and one at the very end.
DeleteThe response I get is encrypted. That's why I'm assuming it is a SOAP fault since it is a http 500 and I'm not able to un-encrypt it.
ReplyDeleteHi again,
ReplyDeleteI've debugged this a bit further by enabling logging in CXF.
It fails while validating the signature of the body element in the response, below is an excerpt from the log. It doesn't matter what the actual error from CRM is, it fails as long as it is a Fault.
11.apr.2013 11:02:47 org.apache.jcp.xml.dsig.internal.DigesterOutputStream write
FINE: <s:Body xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" u:Id="_2"><env:Fault><Code xmlns="http://www.w3.org/2003/05/soap-envelope"><Value>Sender</Value><Subcode><Value>a:DeserializationFailed</Value></Subcode></Code><Reason xmlns="http://www.w3.org/2003/05/soap-envelope"><Text xml:lang="nb-NO">The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://schemas.microsoft.com/xrm/2011/Contracts/Services:id. The InnerException message was 'There was an error deserializing the object of type System.Guid. The value 'F9D6E5AE-2032-E211-88C7-005056004C9X' cannot be parsed as the type 'Guid'.'. Please see InnerException for more details.</Text></Reason></env:Fault></s:Body>
11.apr.2013 11:02:47 org.apache.jcp.xml.dsig.internal.dom.DOMReference validate
FINE: Expected digest: UXn4DGytwBUfvXX2naORgG76jvk=
11.apr.2013 11:02:47 org.apache.jcp.xml.dsig.internal.dom.DOMReference validate
FINE: Actual digest: NzKaDWYL/OZ7mkGXYLtwVh4KMXk=
11.apr.2013 11:02:47 org.apache.jcp.xml.dsig.internal.dom.DOMXMLSignature validate
FINE: Reference[#_2] is valid: false
I haven't validated that the message/signature from CRM is correct, but I would assume that it is correct and that it is CFX, wss4j or xmlsec which somehow messes this up?
I have updated my client using the latest CXF (2.7.4), wss4j (1.6.10) and xmlsec (1.5.4).
Regards
Ørjan