3

I have a JAX-RS application on WildFly 10 which shall be secured by a simple Basic Auth.

It works so far, but if the authentication fails, the server responds with

<html>
  <head>
    <title>Error</title>
  </head>
  <body>Unauthorized</body>
</html>

which is not my desired response. I would prefer a customized (json) response.

How to do that?

What I did so far:

  1. I configured a new Wildfly security domain in my server configuration with a simple UserRolesLoginModule (which is sufficient in my case):

    <security-domain name="MySecurityDomain" cache-type="default">
      <authentication>
        <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="required">
          <module-option name="usersProperties" value="${jboss.server.config.dir}/users.properties"/>
          <module-option name="rolesProperties" value="${jboss.server.config.dir}/roles.properties"/>                            
          <module-option name="hashAlgorithm" value="MD5"/>
          <module-option name="hashEncoding" value="base64"/>
          <module-option name="hashCharset" value="UTF-8"/>
          <module-option name="unauthenticatedIdentity" value="UnauthenticatedAccess"/> 
        </login-module>                                       
      </authentication>
    </security-domain>
    
  2. I annotated all services in the app:

         @SecurityDomain("MySecurityDomain")
         @RolesAllowed({ "RoleFromPropertyFile", "AnotherRoleFromPropertyFile" })
    
  3. I created a jboss-web.xml with the content

        <jboss-web>
          <security-domain>MySecurityDomain</security-domain>
        </jboss-web>
    
  4. I have a web.xml where I tried a lot of different things without any success... :-(
    Current content:

        <security-constraint>
          <display-name>Deny all HTTP methods except GET and POST</display-name>
          <web-resource-collection>
            <web-resource-name>NextTest</web-resource-name>
            <url-pattern>/mypattern/*</url-pattern>
            <http-method-omission>GET</http-method-omission>
            <http-method-omission>POST</http-method-omission>
         </web-resource-collection>    
       </security-constraint>
       <login-config>
         <auth-method>BASIC</auth-method>
         <realm-name>MySecurityRealm</realm-name>
       </login-config>
      <security-role>
         <description>Access to all application parts</description>
         <role-name>all</role-name>
     </security-role>    
     <!-- and some more roles -->
    
  5. I also implemented a ExceptionMapper<EJBAccessException> to generate my own response. But this mapper is only reached when I remove all content of web.xml.

My guess is that undertow is doing the authorization and handles the response on unauthorized access. If I remove the security configuration in the web.xml, the EJBs are accessed, but without evaluating the BasicAuth header. In this case, all requests are denied.

I possible I would avoid to write a Servlet and use an ExceptionMapper instead.

Any ideas what I missed?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555

2 Answers2

0

I did a little experiment with some code and, while it's not pretty, you could try something like:

import java.io.IOException;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;

@Provider
public class AuthBodyResponseFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext requestContext,
                       ContainerResponseContext responseContext) throws IOException {

        if((responseContext.getStatus() == 401) && 
           (responseContext.getEntity() instanceof String))
            responseContext.setEntity("no services for you!");
    }
}

I tested it a bit and it seems to work. Of course, the challenge is where else is there a 401 with a String response body? I'd have to test more to see if this covers everything.

stdunbar
  • 16,263
  • 11
  • 31
  • 53
0

Here is how I do it:

@POST
@Consumes("application/json")
@Produces("application/json")
public Response create(Entity entity) {
    try {
        Entity created = service().create(entity);
        return Response.created(location(created)).entity(created).build();
    } catch (ServiceException e) {
        return Response.status(e.getStatus()).entity(e).build();
    }
}

Notice the return type, Response. This allows you to customize the response, including setting headers etc. It also means you have to write some more wiring code.

I'm using a custom ServiceException here that already has the status in it and use it to set the response code. Then I pass the exception itself which will be returned as JSON.

Stijn de Witt
  • 40,192
  • 13
  • 79
  • 80