1
votes

I have been trying to create a java service that uses basic authentication over SSL. Configuring the SSL was quite simple but configuring authentication is much more of a problem. Any time I try to call a method marked with a @RolesAllowed annotation I receive the following exception...

Exception in thread "AWT-EventQueue-0" javax.xml.ws.soap.SOAPFaultException: javax.ejb.EJBAccessException
at com.sun.xml.internal.ws.fault.SOAP11Fault.getProtocolException(SOAP11Fault.java:178)
at com.sun.xml.internal.ws.fault.SOAPFaultBuilder.createException(SOAPFaultBuilder.java:111)
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:108)
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:78)
at com.sun.xml.internal.ws.client.sei.SEIStub.invoke(SEIStub.java:129)
at $Proxy30.reverseString(Unknown Source)
at securitytestclient.SecurityTestClient.reverseStringSOAP(SecurityTestClient.java:305)
at securitytestclient.SecurityTestClient.buttonReverseActionPerformed(SecurityTestClient.java:217)
at securitytestclient.SecurityTestClient.access$000(SecurityTestClient.java:20)
at securitytestclient.SecurityTestClient$1.actionPerformed(SecurityTestClient.java:72)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2018)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2341)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:402)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
at javax.swing.plaf.basic.BasicButtonListener$Actions.actionPerformed(BasicButtonListener.java:303)
at javax.swing.SwingUtilities.notifyAction(SwingUtilities.java:1661)
at javax.swing.JComponent.processKeyBinding(JComponent.java:2879)
at javax.swing.JComponent.processKeyBindings(JComponent.java:2926)
at javax.swing.JComponent.processKeyEvent(JComponent.java:2842)
at java.awt.Component.processEvent(Component.java:6282)
at java.awt.Container.processEvent(Container.java:2229)
at java.awt.Component.dispatchEventImpl(Component.java:4861)
at java.awt.Container.dispatchEventImpl(Container.java:2287)
at java.awt.Component.dispatchEvent(Component.java:4687)
at java.awt.KeyboardFocusManager.redispatchEvent(KeyboardFocusManager.java:1890)
at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(DefaultKeyboardFocusManager.java:752)
at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(DefaultKeyboardFocusManager.java:1017)
at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:889)
at java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:717)
at java.awt.Component.dispatchEventImpl(Component.java:4731)
at java.awt.Container.dispatchEventImpl(Container.java:2287)
at java.awt.Window.dispatchEventImpl(Window.java:2713)
at java.awt.Component.dispatchEvent(Component.java:4687)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:707)
at java.awt.EventQueue.access$000(EventQueue.java:101)
at java.awt.EventQueue$3.run(EventQueue.java:666)
at java.awt.EventQueue$3.run(EventQueue.java:664)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:87)
at java.awt.EventQueue$4.run(EventQueue.java:680)
at java.awt.EventQueue$4.run(EventQueue.java:678)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:677)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:211)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:128)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:117)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:113)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

My server is using JavaEE on Glassfish 3. It's functions are defined in a bean as follows...

package com.intproimp.testing.beans;

import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ejb.Local;
import javax.ejb.Stateless;

@Stateless(mappedName="ejb/StringOps/Bean")
@Local(StringOps.class)
public class StringOpsBean implements StringOps {

    @PermitAll
    @Override
    public String echoString(String str) {
        return str;
    }

    @RolesAllowed({"admin"})
    @Override
    public String reverseString(String str) {
        char[] input = str.toCharArray();
        char[] output = new char[input.length];
        for (int i = 0; i < output.length; i++) {
            output[i] = input[input.length - i - 1];
        }
        return new String(output);
    }
}

I have added the login config to my web.xml file...

<login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>testing-frealm</realm-name>
</login-config>
<security-role>
    <description/>
    <role-name>admin</role-name>
</security-role>
<security-role>
    <description/>
    <role-name>user</role-name>
</security-role>

The security realm is just a file realm with one user(name: "andrew", pass: "12345"). I've also added the role-group mappings to my glassfish-web.xml

<security-role-mapping>
    <role-name>user</role-name>
    <group-name>user</group-name>
</security-role-mapping>
<security-role-mapping>
    <role-name>admin</role-name>
    <group-name>admin</group-name>
</security-role-mapping>

On the client side, I have a simple swing application to test from...

Swing Test Application

The call being made is to the reverse function on a SOAP channel. The SOAP client components are generated with NetBeans via the New->WebService Client function. And the action associated with the reverse button click is...

private void addAuthenticateionSOAP(StringOpsSOAP port) {
    ((BindingProvider) port).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, fieldUsername.getText());
    ((BindingProvider) port).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, fieldPassword.getText());
}

private String reverseStringSOAP(String str) {
    StringOpsSOAP_Service service = new StringOpsSOAP_Service();
    StringOpsSOAP port = service.getStringOpsSOAPPort();
    if (checkAuthentication.isSelected()) addAuthenticateionSOAP(port);
    return port.reverseString(str);
}

I'm sure there is just some little thing that I'm missing, but I've been looking at the problem for days and still haven't found it.

-- Edit -- I realized there might be some confusion since I didn't post my WebService code. The EJB that contains the String functions is accessed through a SOAP WebService Bean...

package com.intproimp.test.web;

import com.intproimp.test.beans.StringOps;
import javax.ejb.EJB;
import javax.jws.WebService;
import javax.ejb.Stateless;
import javax.jws.WebMethod;
import javax.jws.WebParam;

@WebService(serviceName = "StringOpsSOAP")
@Stateless()
public class StringOpsSOAP {

    @EJB
    private StringOps ops;

    @WebMethod(operationName = "echoString")
    public String echoString(@WebParam(name = "str") String str) {
        return ops.echoString(str);
    }

    @WebMethod(operationName = "reverseString")
    public String reverseString(@WebParam(name = "str") String str) {
        return ops.reverseString(str);
    }
}
1
Perhaps, Glassfish is doing some magic, but I would not expect that: you have published your service and EJB, but calling it as SOAP WS. You have either to call it as remote EJB (and follow the authentication game in EJB world) or publish your WS using JSR-181 annotations (see example), namely @WebService on implementation class and @WebMethod on each method in each method you want to publish as SOAP. P.S. Interesting to look into EJBAccessException you've got.dma_k
I should clarify, the EJB is used by a Web Service annotated class. I'll edit my question to add it's code.Andrew Rademacher
Have you been able to solve this issue?TPete
Have you tried enabling an audit module to have all authentication attemps logged to server.log ? It would really be helpful to you, I think.Riduidel

1 Answers

2
votes

You need to add a <security-constraint> to your web.xml which describes who is allowed to access a certain URL. It's described in Securing Web Applications in the Java EE 6 Tutorial. In your case it should be something like this:

<security-constraint>  
<display-name>WebServiceSecurity</display-name>  

<web-resource-collection>  
    <web-resource-name>Authorized users only</web-resource-name>  
    <url-pattern>/yoururl</url-pattern>  
    <http-method>POST</http-method>
</web-resource-collection>  

<auth-constraint>       
    <role-name>user</role-name>
    <role-name>admin</role-name>
</auth-constraint>  

Edit: This should do the trick if you have make your stateless session bean a web service, by adding the @webservice annotation to the class and publishing the methods using @webmethod, as dma_k said in comments.

Edit 2: From the link above:

Specifying Security Constraints

A security constraint is used to define the access privileges to a collection of resources using their URL mapping.

Further on:

Specifying a Web Resource Collection

url-pattern is used to list the request URI to be protected. Many applications have both unprotected and protected resources. To provide unrestricted access to a resource, do not configure a security constraint for that particular request URI.

And:

Specifying an Authorization Constraint

An authorization constraint establishes a requirement for authentication and names the roles authorized to access the URL patterns and HTTP methods declared by this security constraint. If there is no authorization constraint, the container must accept the request without requiring user authentication.

So, no <security-constraint> => no authentication => no roles available.

Use auditing or read info (getUserPrincipal(), isUserInRole()) from WebServiceContext, to verify this.