spring-boot-starter-security + cxf-spring-boot-starter-jaxws dont work

1.2k views Asked by At

I am trying to mount a spring boot application with CXF based soap services safely. I've gone through a thousand forums but can't get it to work.

I have done different tests that I comment below:

  1. If I only enable security at the level of cxf-rt-ws-security with basic authentication, the application works, and when calling from SOAPUI for example, it validates the basic authentication http credentials. Here I have defined an interceptor for cxf BasicAuthAuthorizationInterceptor

     <dependency>
         <groupId>org.apache.cxf</groupId>
         <artifactId>cxf-spring-boot-starter-jaxws</artifactId>
         <version>3.4.0</version>
     </dependency>
    
     <dependency>
         <groupId>org.apache.cxf</groupId>
         <artifactId>cxf-rt-ws-security</artifactId>
         <version>3.4.0</version>
     </dependency>
    

package com.byl.arquetipo.config;

import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.util.Arrays;
import java.util.HashMap;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BasicAuthAuthorizationInterceptor extends SoapHeaderInterceptor {

    /** La constante logger. */
    private static final Logger logger = LoggerFactory.getLogger(BasicAuthAuthorizationInterceptor.class);

    /** La variable users. */
    Map<String, String> users = new HashMap<String, String>();

    /**
     * Obtiene users.
     *
     * @return users
     */
    public Map<String, String> getUsers() {
        return users;
    }

    /**
     * Establece users.
     *
     * @param users
     *            users
     */
    public void setUsers(Map<String, String> users) {
        this.users = users;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void handleMessage(Message message) throws Fault {

        AuthorizationPolicy policy = message.get(AuthorizationPolicy.class);

        // If the policy is not set, the user did not specify credentials.
        // 401 is sent to the client to indicate that authentication is required.
        if (policy == null) {
            sendErrorResponse(message, HttpURLConnection.HTTP_UNAUTHORIZED);
            return;
        }

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

        // CHECK USERNAME AND PASSWORD
        if (!checkLogin(username, password)) {
            logger.error("handleMessage: Invalid username or password for user: " + policy.getUserName());
            sendErrorResponse(message, HttpURLConnection.HTTP_FORBIDDEN);
        }
    }

    /**
     * Check login.
     *
     * @param username
     *            username
     * @param password
     *            password
     * @return <code>true</code>, si termina correctamente
     */
    private boolean checkLogin(String username, String password) {
        for (Map.Entry<String, String> item : users.entrySet()) {
            if (item.getKey().equals(username) && password.equals(item.getValue())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Send error response.
     *
     * @param message
     *            message
     * @param responseCode
     *            response code
     */
    private void sendErrorResponse(Message message, int responseCode) {
        Message outMessage = getOutMessage(message);
        outMessage.put(Message.RESPONSE_CODE, responseCode);

        // Set the response headers
        @SuppressWarnings("unchecked")
        Map<String, List<String>> responseHeaders = (Map<String, List<String>>) message.get(Message.PROTOCOL_HEADERS);

        if (responseHeaders != null) {
            responseHeaders.put("WWW-Authenticate", Arrays.asList(new String[] { "Basic realm=realm" }));
            responseHeaders.put("Content-Length", Arrays.asList(new String[] { "0" }));
        }
        message.getInterceptorChain().abort();
        try {
            getConduit(message).prepare(outMessage);
            close(outMessage);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Obtiene out message.
     *
     * @param inMessage
     *            in message
     * @return out message
     */
    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;
    }

    /**
     * Obtiene conduit.
     *
     * @param inMessage
     *            in message
     * @return conduit
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    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);
        Conduit conduit = exchange.getDestination().getBackChannel(inMessage);
        exchange.setConduit(conduit);
        return conduit;
    }

    /**
     * Close.
     *
     * @param outMessage
     *            out message
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    private void close(Message outMessage) throws IOException {
        OutputStream os = outMessage.getContent(OutputStream.class);
        os.flush();
        os.close();
    }

}

  1. If I enable spring-boot-starter-security at this point it always gives me a 401 and I can't get it to work. Also by enabling spring-boot-starter-security I can't debug it to enter the BasicAuthAuthorizationInterceptor interceptor

'

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

I have to enable spring-boot-starter-security for the rest of the application, because I also have REST services exposed for spring.

Can someone guide me on how to solve the problem please?

1

There are 1 answers

0
Dennis Kieselhorst On

If you add Spring Security, you probably want to leverage its capabilities for handling basic authentication (and no longer use your interceptor).

So add something like this:

@EnableWebSecurity
public class BasicSecurityConfiguration extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests(authorizeRequests ->
            authorizeRequests
                    .antMatchers("/services/*").hasRole("MEMBER")
                    .antMatchers("/").permitAll()
    )
        .httpBasic().realmName("Your realmname")
        .and()
        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
    ...
}

See https://docs.spring.io/spring-boot/docs/2.4.5/reference/htmlsingle/#boot-features-security for more information.