JAX-WS Authentication in a Nutshell

TL;DR: SOAP Authentication on WildFly 41

Implement secure authentication for JAX-WS Web Services using Jakarta EE 11 and WildFly 41.0.1.Final. Learn how to configure standard HTTP Basic Authentication secured over HTTPS/TLS, utilize high-performance framework-native Apache CXF Interceptors, or manually intercept authorization headers using the standard WebServiceContext.

In the context of enterprise web transactions, HTTP Basic Access Authentication is a highly standard method for client programs to supply a username and password with their requests. This tutorial walks you through setting up BASIC authentication for modern JAX-WS Web Services. Additionally, we will explore Apache CXF Interceptors as an alternative, performance-focused authentication mechanism.

While REST has become the default for standard web APIs, SOAP remains vital in modern enterprise systems. SOAP's adherence to rigid schemas (WSDL), built-in standards like WS-Security, and its suitability for complex transaction-heavy enterprise integrations make it irreplaceable in industries like banking and B2B integration where REST is often too loose. However, regardless of the protocol, transmitting authentication credentials insecurely is a critical risk.

Security Warning: Before transmission, credentials are simply joined by a colon and encoded with the Base64 algorithm. For example, 'Aladdin:open sesame' becomes 'QWxhZGRpbjpvcGVuIHNlc2FtZQ=='. Because Base64 is trivial to decode, you MUST always use HTTPS/TLS (SSL) to encrypt the transport channel. Never use Basic Authentication over plain HTTP.

Credentials are stored in the HTTP transport headers rather than inside the SOAP envelope payload itself. Here is a transport dump of a secure SOAP request over HTTPS using Basic Authentication:

POST /windcpms/ws/CfmsInterfaceService HTTP/1.1
Host: secure.mastertheboss.com:8443
Content-Type: text/xml; charset=UTF-8
Accept: */*
Authorization: Basic YWRtaW5BQUE6dGVzdG53aw==
SOAPAction: ""
User-Agent: Apache CXF 4.1.0
Cache-Control: no-cache
Pragma: no-cache
Connection: keep-alive
Content-Length: 368

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
     <!-- Payload here -->
  </soap:Body>
</soap:Envelope>

Let's look at the implementation options for protecting your Jakarta EE 11 / WildFly 41 Web Services.

JAX-WS Container-Managed Authentication

To enable standard, container-managed authentication in Jakarta EE 11, we leverage security annotations and configure standard deployment descriptors mapped to WildFly's modern Elytron security subsystem.

You can declare security constraints directly on your EJB-based Web Service endpoint using standard Jakarta Security and JBossWS-CXF's proprietary @WebContext annotation:

package com.sample.ws;

import jakarta.ejb.Stateless;
import jakarta.annotation.security.RolesAllowed;
import jakarta.annotation.security.DeclareRoles;
import org.jboss.ws.api.annotation.WebContext;

@Stateless
@DeclareRoles("dev")
@RolesAllowed("dev")
@WebContext(
    contextRoot="/webapp", 
    urlPattern="/*", 
    authMethod="BASIC", 
    transportGuarantee="CONFIDENTIAL", 
    secureWSDLAccess=true
)
public class EndpointEJB implements EndpointInterface {
    // Web Service Implementation
}

Notice that transportGuarantee="CONFIDENTIAL" is declared. This enforces HTTPS, auto-redirecting plain HTTP requests to a secure port on WildFly. Security domains in WildFly 41 are integrated via Elytron. You can associate your deployment with a specific Elytron domain using a wildfly-web.xml deployment descriptor:

<?xml version="1.0" encoding="UTF-8"?>
<wildfly-web xmlns="urn:jboss-web:15.0">
   <security-domain>ApplicationDomain</security-domain>
</wildfly-web>

Finally, update your web.xml descriptor to use the modern Jakarta EE 11 schema, mandating HTTPS-encrypted communication:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">
         
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Protected Web Services</web-resource-name>
      <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>dev</role-name>
    </auth-constraint>
    <user-data-constraint>
      <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
  </security-constraint>
  
  <login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>ApplicationRealm</realm-name>
  </login-config>
  
  <security-role>
    <role-name>dev</role-name>
  </security-role>
</web-app>

To invoke this secured service, a client application uses the standard jakarta.xml.ws.BindingProvider interface to supply the appropriate HTTP headers programmatically:

import jakarta.xml.ws.BindingProvider;
import jakarta.xml.ws.Service;
import javax.xml.namespace.QName;
import java.net.URI;
import java.net.URL;

URL wsdlURL = new URI("https://localhost:8443/webapp/TestEndpoint?wsdl").toURL();
QName qname = new QName("http://org.jboss.ws/jaxws/context", "TestEndpointService");
Service service = Service.create(wsdlURL, qname);
TestEndpoint port = service.getPort(TestEndpoint.class);
 
BindingProvider bp = (BindingProvider) port;
bp.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "user");
bp.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "password");

Apache CXF Interceptors

If your environment leverages Apache CXF (the default reference SOAP implementation bundled inside WildFly 41), you can bypass standard servlet container-managed authentication and write a custom CXF Interceptor.

JAX-WS Handlers vs. CXF Interceptors: Standard JAX-WS Handlers (jakarta.xml.ws.handler.Handler) are completely portable across application servers, but they process raw XML message structures (SOAP envelopes). Apache CXF Interceptors are engine-specific, but much more powerful and performance-efficient because they operate directly on incoming network data streams, letting you reject unauthorized calls before the SOAP body is even parsed.

Here is a modern, clean Apache CXF Interceptor compatible with Jakarta EE 11 / CXF 4.1+:

package com.sample.ws;

import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.cxf.binding.soap.interceptor.SoapHeaderInterceptor;
import org.apache.cxf.configuration.security.AuthorizationPolicy;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.Message;
import org.apache.cxf.transport.Conduit;
import org.apache.cxf.ws.addressing.EndpointReferenceType;

public class BasicAuthAuthorizationInterceptor extends SoapHeaderInterceptor {  

    @Override
    public void handleMessage(Message message) throws Fault {
        AuthorizationPolicy policy = message.get(AuthorizationPolicy.class);
 
        // If the policy is null, credentials were not provided. Send 401.
        if (policy == null) {
            sendErrorResponse(message, HttpURLConnection.HTTP_UNAUTHORIZED);
            return;
        }

        String username = policy.getUserName();
        String password = policy.getPassword();

        // Perform programmatic authentication check
        if (!checkLogin(username, password)) {
            System.err.println("Authentication failed for user: " + policy.getUserName());
            sendErrorResponse(message, HttpURLConnection.HTTP_FORBIDDEN);
        }
    }

    private boolean checkLogin(String username, String password) {
        // Integrate with Elytron, DB, LDAP, or properties
        return "admin".equals(username) && "securePassword123".equals(password);
    }

    private void sendErrorResponse(Message message, int responseCode) {
        Message outMessage = getOutMessage(message);
        outMessage.put(Message.RESPONSE_CODE, responseCode);
 
        @SuppressWarnings("unchecked")
        Map<String, List<String>> responseHeaders = (Map<String, List<String>>) message.get(Message.PROTOCOL_HEADERS);
 
        if (responseHeaders != null) {
            responseHeaders.put("WWW-Authenticate", Collections.singletonList("Basic realm=\"SecureRealm\""));
            responseHeaders.put("Content-Length", Collections.singletonList("0"));
        }
        
        message.getInterceptorChain().abort();
        try {
            getConduit(message).prepare(outMessage);
            close(outMessage);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private Message getOutMessage(Message inMessage) {
        Exchange exchange = inMessage.getExchange();
        Message outMessage = exchange.getOutMessage();
        if (outMessage == null) {
            Endpoint endpoint = exchange.get(Endpoint.class);
            outMessage = endpoint.getBinding().createMessage();
            exchange.setOutMessage(outMessage);
        }
        outMessage.putAll(inMessage);
        return outMessage;
    }

    private Conduit getConduit(Message inMessage) throws IOException {
        Exchange exchange = inMessage.getExchange();
        EndpointReferenceType target = exchange.get(EndpointReferenceType.class);
        Conduit conduit = exchange.getDestination().getBackChannel(inMessage, null, target);
        exchange.setConduit(conduit);
        return conduit;
    }

    private void close(Message outMessage) throws IOException {
        OutputStream os = outMessage.getContent(OutputStream.class);
        if (os != null) {
            os.flush();
            os.close();
        }
    }
}

You can register this custom CXF interceptor declaratively on your Web Service class using standard Apache CXF annotations:

import org.apache.cxf.interceptor.InInterceptors;

@InInterceptors(interceptors = "com.sample.ws.BasicAuthAuthorizationInterceptor")
public class SecuredSoapWS {
    // ...
}

Alternatively, if you are leveraging Spring-based endpoints, configure the CXF bean wiring explicitly inside your XML configuration:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:cxf="http://cxf.apache.org/core"
       xmlns:jaxws="http://cxf.apache.org/jaxws"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
         http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
         http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

    <import resource="classpath:META-INF/cxf/cxf-all.xml" />

    <cxf:bus>
        <cxf:inInterceptors>
            <ref bean="basicAuthAuthorizationInterceptor"/>
        </cxf:inInterceptors>
    </cxf:bus>

    <bean id="basicAuthAuthorizationInterceptor" class="com.sample.ws.BasicAuthAuthorizationInterceptor" />
</beans>

Programmatic Context-Based Authentication

Another option is using the standard jakarta.xml.ws.WebServiceContext interface to access and manually parse HTTP Authorization headers directly within your endpoint logic.

Historically, developers relied on external codecs like Apache Commons Codec to perform the Base64 transformations. You can still download and use those libraries if required:

http://commons.apache.org/codec/

However, inside **Jakarta EE 11 and Java 17/21**, you should always prefer Java's built-in, highly optimized java.util.Base64 class. This eliminates third-party dependencies completely. Here is how to perform programmatic security parsing using the standard runtime APIs:

package com.sample.ws;

import jakarta.annotation.Resource;
import jakarta.jws.WebService;
import jakarta.xml.ws.WebServiceContext;
import jakarta.xml.ws.handler.MessageContext;
import java.util.Base64;
import java.util.List;
import java.util.Map;

@WebService
public class SecuredWSImpl implements SecuredWS {

    @Resource
    private WebServiceContext wsctx;
 
    public void doSomething() {
        doAuthentication();
        // Execute business logic
    }
 
    private void doAuthentication() {
        MessageContext mctx = wsctx.getMessageContext();
        @SuppressWarnings("unchecked")
        Map<String, List<String>> httpHeaders = (Map<String, List<String>>) mctx.get(MessageContext.HTTP_REQUEST_HEADERS);
 
        if (httpHeaders == null || !httpHeaders.containsKey("Authorization")) {
            throw new RuntimeException("Authentication failed! This service requires HTTPS Basic Authentication!");
        }

        List<String> authHeader = httpHeaders.get("Authorization");
        if (authHeader == null || authHeader.isEmpty()) {
            throw new RuntimeException("Authentication failed! Empty Authorization header.");
        }
 
        String authString = authHeader.get(0);
        if (!authString.toUpperCase().startsWith("BASIC ")) {
            throw new RuntimeException("Authentication failed! Only HTTP Basic Auth is accepted.");
        }

        // Extract and decode credentials using standard java.util.Base64
        String base64Credentials = authString.substring(6).trim();
        byte[] decodedBytes = Base64.getDecoder().decode(base64Credentials);
        String credentials = new String(decodedBytes);
 
        String username = null;
        String password = null;
        int colonIndex = credentials.indexOf(":");
        if (colonIndex > -1) {
            username = credentials.substring(0, colonIndex);
            password = credentials.substring(colonIndex + 1);
        } else {
            throw new RuntimeException("Malformed authentication header.");
        }
 
        // Delegate credentials to Elytron, database, or directory server checks
        if ("admin".equals(username) && "securePassword123".equals(password)) { 
            System.out.println("============== Authentication Successful for: " + username + " =============");
        } else {
            throw new RuntimeException("Authentication failed! Wrong username / password!");
        }
    }
}

Testing Secure Services

To test security credentials properly, use modern tools such as SoapUI or simple CLI testing via curl. Always use the -k switch with curl to bypass trust verification only when developing locally over self-signed certificates:

curl -k -u "admin:securePassword123" \
     -H "Content-Type: text/xml; charset=utf-8" \
     -d @soap-request.xml \
     https://localhost:8443/webapp/TestEndpoint

Frequently Asked Questions (FAQs)

1. Is HTTP Basic Authentication safe for enterprise Jakarta EE SOAP web services?

Basic Authentication is simple to implement but highly insecure if transmitted over plain HTTP, as credentials are merely Base64-encoded plain text. It must always be combined with HTTPS/TLS (transport-level security) or augmented by message-level security like WS-Security (WSS) to ensure confidentiality and integrity.

2. What is the difference between JAX-WS Handlers and Apache CXF Interceptors?

JAX-WS Handlers are part of the standard Jakarta XML Web Services specification (portable across WildFly, Open Liberty, WebSphere, etc.) and handle full SOAP messages. Apache CXF Interceptors are proprietary to the CXF engine. They are highly performant because they can inspect incoming message streams before the SOAP message is fully unmarshalled, allowing you to reject unauthorized requests earlier in the execution chain.

3. How do I configure WildFly 41 Elytron for modern JAX-WS security?

In WildFly 41, security domains are managed through the WildFly Elytron subsystem. You define an Elytron Security Domain (e.g., using a properties realm, database, or LDAP) in your WildFly configuration (standalone.xml). You then reference this domain in your application's wildfly-web.xml or jboss-web.xml and control role-based access using standard annotations like @RolesAllowed on your WebService bean.


Recommended Articles

Apache CXF Interceptors on WildFly 41 and Jakarta EE 11

Learn how to write, configure, and customize Apache CXF Interceptors on WildFly 41.0.1.Final and Jakarta EE 11. Upgrade from legacy configuration to modern Jakarta XML Web Services.

How to develop CXF Web services with JBoss / WildFly

Learn how to develop, deploy, and secure SOAP Web Services on WildFly 41.0.1.Final using Jakarta EE 11 and Apache CXF. Master JAX-WS handlers, CXF interceptors, and robust cloud configurations.

Developing SOAP Web Services on WildFly 41 (Jakarta EE 11) with Apache CXF

Learn how to develop, deploy, and secure Jakarta XML Web Services (SOAP) on WildFly 41.0.1.Final and Jakarta EE 11 using Apache CXF, WS-Security, and modern testing tools.

Enhance JBoss Web Services Debugging with System Property and Apache CXF Features

Learn how to trace JBoss Web Services Requests using a System Property or CXF features for improved debugging.