Thursday, December 18, 2008

Simple custom policy example using JAX-WS on WebLogic

So I little while ago someone asked me how to using a weblogic JAX-WS client to connect to a service that required plain text WS-Security username / token headers. It turns out that non of the default policies that come with weblogic will work in this configuration because they all have a sensible level of security applied. This doesn't help when trying to deal with an older system so in the end I modified the existing Https username token policy and came up with this file:

<?xml version="1.0"?>
<wsp:Policy
  xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
  xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200512"
  >
  <sp:SupportingTokens>
    <wsp:Policy>
      <sp:UsernameToken
        sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200512/IncludeToken/AlwaysToRecipient">
        <wsp:Policy>
          <sp:WssUsernameToken10/>
        </wsp:Policy>
      </sp:UsernameToken>
    </wsp:Policy>
  </sp:SupportingTokens>
</wsp:Policy>

Now there is a lot on the weblogic documentation site about configuring security; but it took a while to absorb into something that worked. (My fault I guess) The most convenient option is to place the policy file in the "policies" sub dir under WEB-INF. So you ended up with a project that looks kinda like this:

You web service can then just refer to the policy using the "policy:" url as you would the built in policies:

package project1;

import javax.jws.WebService;

import weblogic.jws.Policies;
import weblogic.jws.Policy;

@WebService
@Policies( { @Policy(uri = "policy:usernametoken.xml") } )
public class Hello {
    public String sayHello(String name) {
        return "Hello " + name;
    }
}

In JDeveloper at least you have to type this by hand because the custom policy button is very broken in the current released version of JDeveloper. This we hope to address at some point in the future. This is all that is required to set up the service to use the default credential providers. Check out the link above for more information on configuring this.

So if you need to actually invoke this service you can skip some steps if the policies are advertised in the WSDL. Fortunately they are by default so when the JAX-WS client starts it will parse the polices from the WSDL and set itself up to expect the correct configuration to work. Now you might expect BindingProvider username / password properties to be used here; but instead you need to configure a credential provider as follows. Note there is no reference to the policy here.

public static void main(String[] args) {
    helloService = new HelloService();
    Hello hello = helloService.getHelloPort();
    // Add your code to call the desired methods.


    List<CredentialProvider> credProviders =
        new ArrayList<CredentialProvider>();

    String username = "weblogic";
    String password = "weblogic";
    CredentialProvider cp =
        new ClientUNTCredentialProvider(username.getBytes(),
                                        password.getBytes());
    credProviders.add(cp);

    Map<String, Object> rc = ((BindingProvider)hello).getRequestContext();
    rc.put(WSSecurityContext.CREDENTIAL_PROVIDER_LIST, credProviders);


    hello.sayHello("Bob");

}

In some cases you might have a WSDL that for various reasons doesn't publish the policies as you get them through some other side channel. This is fortunately relatively easy to fix with the use of the ClientPolicyFeature as shown in the next code snippet:

public static void main(String[] args) {
    helloService = new HelloService();


    ClientPolicyFeature cpf = new ClientPolicyFeature();
    InputStream asStream =
        HelloPortClient.class.getResourceAsStream("usernametoken.xml");
    cpf.setEffectivePolicy(new InputStreamPolicySource(asStream));

    Hello hello =
        helloService.getHelloPort(
           new WebServiceFeature[] {cpf});

    // Add your code to call the desired methods.


    List<CredentialProvider> credProviders =
        new ArrayList<CredentialProvider>();

    String username = "weblogic";
    String password = "weblogic";
    CredentialProvider cp =
        new ClientUNTCredentialProvider(username.getBytes(),
                                        password.getBytes());
    credProviders.add(cp);

    Map<String, Object> rc = ((BindingProvider)hello).getRequestContext();
    rc.put(WSSecurityContext.CREDENTIAL_PROVIDER_LIST, credProviders);


    hello.sayHello("Bob");

}

And that is it, quite simple once I got used to the weblogic way of working.

23 comments:

jambay said...

Helpful Gerard, this actually came up today in the OTN WLS forums so I'm going to point them to this post.

Michael said...

Cool, I get asked about this for
SAML, all the time.

One small suggestion, though. You don't need to (and probably shouldn't) put the Oracle/BEA/WebLogic-specific @Policy annotation, in the JWS. Doing this hampers portability/migration amongst JAX-WS implementations, plus there is a more "SOA philosophy-conformant" way to achieve the same thing :-)

Putting the custom policy in the WebContent/WEB-INF/policies directory, is still required for this other way. The difference is that you assign (or unassign) the policy using the WebLogic Admin Console:

1. Click the "Deployments" link

2. Expand EAR containing the HelloService WS

3. Click link for HelloService WS

4. Click the "Configuration" tab

5. Expand the HelloService link in the "Service Endpoints and Operations" HTML table, and click the link for the applicable operation

6. If you want to apply the policy to the both the request and response, locate the custom policy file in the "Available Message Policies" list (it will probably be the last entry), move it to the "Chosen Message Policies" list, and click the "Finish" button.

7. If you only want to apply the custom policy to the request, click the "Next" button and move the entry on the next screen.

8. If you only want to apply the custom policy to the response,click the "Next" button and move the entry on the next screen.

Doing it this way also allows you to put the OSWM (Oracle Web Services Manager) product in front of the HelloService, in a completely non-intrusive way. Cross-sell, cross-sell, cross-sell :-)

Gerard Davison said...
This comment has been removed by the author.
Shailesh said...

Hi Gerard,
Could you please let me know how do we associate a policy file to the web service client running in a stateless EJB ? Its urgent

Gerard Davison said...

Michael,

Sure; but that would have required more screen shots. :-) Seriously though as you say, with a bit of deployment plans sprinkled in, was the basis of one of my talks at UKOUG'08. Perhaps in the new year I can write that up.

With regards to SAML, you can do this with -WS; but it is not something I have had time to try. (Fixed previous comment)

Gerard

Gerard Davison said...

Shailesh,

What have you tried?

Gerard

Jose Luis said...

Hello, I've a problem: when a generate the web service with JAX-WS SOAP 1.2 and I've:

@WebService(serviceName = "Hello", portName = "HelloSoap12HttpPort")
@BindingType(SOAPBinding.SOAP12HTTP_BINDING)
@Policies( { @Policy(uri = "policy:usernametoken.xml") } )

When I invok the webservice I get the error:

Exception in thread "main" com.sun.xml.ws.server.UnsupportedMediaException: Unsupported Content-Type: text/html Supported ones are: [application/soap+xml]
at com.sun.xml.ws.encoding.StreamSOAPCodec.decode(StreamSOAPCodec.java:291)
at com.sun.xml.ws.encoding.StreamSOAPCodec.decode(StreamSOAPCodec.java:128)
at com.sun.xml.ws.encoding.SOAPBindingCodec.decode(SOAPBindingCodec.java:287)


why?

Gerard Davison said...

Jose Luis,

It looks like a server error, you need to capture the traffic using something like the HTTP Analyzer in JDeveloper to see what the problem is.

It is most likely a server or proxy fault.

Gerard

Research Writer said...
This comment has been removed by a blog administrator.
Vijay said...

Hi, I have a Jaxws webservice on weblogic10.3 (signed and encrypted). There is proxy between the client and the server. is there a way where the client can first encrypt and then sign so that the proxy can only veify the signature? By default weblogic client first signs and then encrypts

Gerard Davison said...

Vijay,

Can you give me a little bit more information on what policies you are currently using? It might be as simple as just creating a custom policy with the ordering slightly different.

Gerard

Vijay said...
This comment has been removed by the author.
Vijay said...

Hi Gerard,
I am using 3 weblogic policies
Wssp1.2-2007-Wss1.1-X509-Basic256.xml, Wssp1.2-2007-SignBody.xml,Wssp1.2-2007-EncryptBody.xml

Vijay said...

Hi Gerard,
I as able to achieve this using custom policies, by using sp:EncryptBeforeSigning. Your earlier reply did give me a confidence that I was proceeding in the right direction. Thanks for the support

pep said...

Hi,

does this work for '@WebServiceClient' as well?

I have an EJB running within WebLogic 10.3. This is using a WebServiceClient to request a WebService.

Greetings

Gerard Davison said...

Vijay,

Sorry for the very late reply, I am glad you managed to resolve the problems on your own.

Gerard

Gerard Davison said...

Pep,

Yes it should do.

Gerard

Narayanan said...

i have the same set-up in my weblogic workshop. place policy file under WEB-INF/policies/Wssp1.2-2007-Http-UsernameToken-Plain.xml.
And accessing this policies file from webservice as @Policy(uri="policy:Wssp1.2-2007-Http-UsernameToken-Plain.xml").
but i am getting weblogic.wsee.ws.init.WsDeploymentException: Could not process policy ....
Caused by: Unable to find policy: "Wssp1.2-2007-Http-UsernameToken-Plain.xml", please make sure to use dynamic wsdl when initializing the service stub.

Any idea how to resolve this?

Narayanan said...

so i had to use the relative path something like @Policy(uri="../../../WebContent/WEB-INF/policies/....xml") which is awful.

Gerard Davison said...

Narayanan,

Can I ask why you are making a local copy of the policy? This should just work from the pre-populated services.

Gerard

sudan said...

Hi Gerard,

I am new to JDeveloper and Oracle Weblogic. I have followed your blog to create a secured webservice using username token in JDeveloper, but I am wondering where and how to configure the username and password for the service with which the credentials from the client has to be authenticated.

Thanks,
Sudan

imran raza khan said...

Very helpful i was using OCSG5.0 which have old style securtiy policy. to extend it i have to use old policy and this blog helped me lottttttttt.

Umashankar Adha said...

Hi Davison's, Thanks for sharing this information with us. As I have deploy same architecture given by you and tried with number of time, with JDV ide 11G and witor h BEA 10.3 but I am facing issue. Once I am calling webservice via soapUI or even WS client both time I receive same response "



wsse:InvalidSecurity
Error on verifying message against security policy Error code:1000




I am not sure, where am making mistake. Can you please suggest me, what mistake I have had. I am sure, other folks face same issue too, as I can see in comments but no resolution is listed here.

Thanks
USA