Apache CXF client for claims-mode xRM (Microsoft Dynamics CRM 2011)?

梦想的初衷 提交于 2019-12-03 08:05:29

We've finally gotten it to work, using not only "CXF and MS CRM 2011" on Groovy Tom's Blog which I mentioned in this question, but also "Using Apache CXF to connect to Microsoft Dynamics" by Jan-Hendrik Kuperus on the JH on Java blog, and in addition correcting (?) the AD FS 2.0 WSDL.

Unfortunately I cannot directly post any code for licensing reasons, but here is an overview of what we did.


The key part of Jan-Hendrik Kuperus's solution is that we create our own STSClient, instead of letting CXF create one. That works around the issue of the ignored <wsx:MetadataSection Dialect="http://www.w3.org/2001/XMLSchema"> sections. It also works around the addressing issue from my question, that since has been fixed in CXF trunk. (We cannot switch to the latest CXF version, unfortunately: all of this was done with CXF 2.7.5.)

In that custom STS client, we point to a specific AD FS endpoint, make sure we use SOAP 1.2 (prevent HTTP error 500, see question), and switch off 'renewing':

STSClient stsClient = new STSClient(bus);
stsClient.setSoap12();
stsClient.setWsdlLocation(wsdlLocation.toExternalForm());
stsClient.setServiceQName(new QName("http://schemas.microsoft.com/ws/2008/06/identity/securitytokenservice", "SecurityTokenService"));
stsClient.setEndpointQName(new QName("http://schemas.microsoft.com/ws/2008/06/identity/securitytokenservice", "UserNameWSTrustBinding_IWSTrust13Async"));
stsClient.setSendRenewing(false);

(If 'renewing' is not switched off, then AD FS 2.0 returns a SOAP fault "ID3035: The request was not valid or is malformed." The AD FS trace says, "Microsoft.IdentityModel.SecurityTokenService.InvalidRequestException: MSIS3137: The RequestSecurityTokenElement contained an unsupported WS-Trust parameter: 'Renewing'.")

Now register stsClient in the request context under property SecurityConstants.STS_CLIENT ("ws-security.sts.client"), set request context property SecurityConstants.USERNAME, and in property SecurityConstants.CALLBACK_HANDLER register a CallbackHandler that handles the resulting WSPasswordCallback and sets the password, and you're in business. Except.

Except that at that point we found that CXF 2.7.5 chokes on AD FS's WSDL: java.lang.IllegalArgumentException: sp:KeyValueToken/wsp:Policy must have a value in KeyValueTokenBuilder#build(). It turns out that the WSDL contains a number of security policies with attribute wsp:Optional="true", and for each of these CXF expects a nested <wsp:Policy> element. So what we did is pre-process the AD FS WSDL, and just added empty <wsp:Policy/> elements in these places.

(We have no idea whether CXF 2.7.5 is too strict here, or whether AD FS 2.0's WSDL does not follow the standards.)


Also, we achieved to dynamically switch between a Windows-mode xRM and claims-mode xRM system, by looking at the <ms-xrm:AuthenticationPolicy> element in the xRM WSDL's security policy, and check whether <ms-xrm:Authentication> contains either ActiveDirectory or Federation. We did this by creating a custom policy extension extending XmlPrimitiveAssertion and registering the corresponding custom builder in bus.getExtension(AssertionBuilderRegistry.class). Then we create the custom STSClient in a custom 'out interceptor':

private static class XRMAuthSecurityModeInterceptor extends AbstractSoapInterceptor {
    public XRMAuthSecurityModeInterceptor() {
        super(Phase.PREPARE_SEND);
        addBefore("IssuedTokenOutInterceptor");
    }

    public void handleMessage(SoapMessage message) throws Fault {
        // if the custom assertion with security mode Federation is present, then create STSClient and...
            message.setContextualProperty(SecurityConstants.STS_CLIENT, stsClient);
    }
}

Finally, since we did not want to work with a downloaded version of AD FS's WSDL, we did the 'fixing' of that WSDL in the same 'out interceptor', by getting the SP12Constants.ISSUED_TOKEN assertion , getting its .getIssuerEpr().getMetadata().getAny(), and from that the {http://www.w3.org/2005/08/addressing}Address. The result is something like http://example.com:12345/adfs/services/trust/mex. We retrieve that URL, parse the XML, add the <wsp:Policy/> elements as described above, save the result to a file, and use that file's URL as the STSClient's wsdlLocation.


Hopefully this helps others; feel free to ask questions if you don't get this to work.

I've merged a fix for the "anonymous" issue to CXF:

https://issues.apache.org/jira/browse/CXF-5807

When the Issuer address is "anonymous", you can specify the desired STS Endpoint name via the Metadata, or failing that it will fall back to just picking the first Port in the received WSDL.

Colm.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!