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:
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();
}
}
- 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?
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:
See https://docs.spring.io/spring-boot/docs/2.4.5/reference/htmlsingle/#boot-features-security for more information.