Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CXF Logging request & response with content filtering or masking soap fields

I would like to log all incoming requests & responses from some particular endpoint, with content filtering. I.e. when i have a request like that:

<soap:Envelope xmlns:soap="http://www.w3.org/2001/12/soap-envelope">
<soap:Body>
  <m:ProcessPhoto xmlns:m="http://www.w3schools.com/photos">
    <m:Name>Apples</m:Name>
    <m:Description>Photo with some apples in it</m:Description>
    <!-- large encoded binary below -->
    <m:Photo>anVzdCBhIHJhbmRvbSB0ZXh0DQpqdXN0IGEgcmFuZG9tIHRleHQNCmp1c3QgYSByYW5kb20gdGV4dA0KanVzdCBhIHJhbmRvbSB0ZXh0DQpqdXN0IGEgcmFuZG9tIHRleHQNCmp1c3QgYSByYW5kb20gdGV4dA0KanVzdCBhIHJhbmRvbSB0ZXh0DQp3b3csIGkgZGlkbid0IHRob3VnaHQgdGhhdCBhbnlvbmUgd291bGQgYmUgaW50ZXJlc3RlZCBpbiBkZWNvZGluZyB0aGlzLiBjb25ncmF0cyE=</m:Photo>
  </m:ProcessPhoto>
</soap:Body>
</soap:Envelope>

I would like to filter it, so that it looks in logs like that

<soap:Envelope xmlns:soap="http://www.w3.org/2001/12/soap-envelope">
<soap:Body>
  <m:ProcessPhoto xmlns:m="http://www.w3schools.com/photos">
    <m:Name>Apples</m:Name>
    <m:Description>Photo with some apples in it</m:Description>
    <m:Photo>hidden</m:Photo>
  </m:ProcessPhoto>
</soap:Body>
</soap:Envelope>

Or with completely removed m:Photo element.

I found that CXF has some LoggingInInterceptor and LoggingOutInterceptor and I could write my own interceptors that does that. However this would be a some work to do, so my question is: do you know any better, out of the box solution?

like image 658
Tomasz Bekas Avatar asked Apr 22 '14 06:04

Tomasz Bekas


People also ask

What is CXF logging?

CXF uses Java SE Logging for both client- and server-side logging of SOAP requests and responses. Logging is activated by use of separate in/out interceptors that can be attached to the client and/or service as required.

What is Apache CXF used for?

Apache CXF™ is an open source services framework. CXF helps you build and develop services using frontend programming APIs, like JAX-WS and JAX-RS. These services can speak a variety of protocols such as SOAP, XML/HTTP, RESTful HTTP, or CORBA and work over a variety of transports such as HTTP, JMS or JBI.

What is CXF endpoint?

In Apache Camel, the Camel CXF component is the key to integrating routes with Web services. You can use the Camel CXF component to create a CXF endpoint, which can be used in either of the following ways: Consumer — (at the start of a route) represents a Web service instance, which integrates with the route.


2 Answers

I had similar problem, where I needed to mask passwords in my input request. I made a small change to existing LogginInterceptor and overridden format method and added my method to mask the password. Here is an example

public class CustomLogInInterceptor extends LoggingInInterceptor {

    @Override
    protected String formatLoggingMessage(LoggingMessage loggingMessage) {

        String str = loggingMessage.toString();

        String output = maskPasswords(str);
        return(output);
    }


    private String maskPasswords(String str) {

                final String[] keys = { "password", "passwords" };
                for (String key : keys) {
                    int beginIndex = 0;
                    int lastIndex = -1;
                    boolean emptyPass = false;
                    while (beginIndex != -1
                            && (beginIndex = StringUtils.indexOfIgnoreCase(str, key,
                                    beginIndex)) > 0) {

                        beginIndex = StringUtils.indexOf(str, ">", beginIndex);
                        if (beginIndex != -1) {
                            char ch = str.charAt(beginIndex - 1);
                            if (ch == '/') {
                                emptyPass = true;
                            }
                            if (!emptyPass) {
                                lastIndex = StringUtils.indexOf(str, "<", beginIndex);
                                if (lastIndex != -1) {
                                    String overlay = "*";
                                    String str2 = StringUtils.substring(str,
                                            beginIndex + 1, lastIndex);
                                    if (str2 != null && str2.length() > 1) {
                                        overlay = StringUtils.rightPad(overlay,
                                                str2.length(), "*");
                                        str = StringUtils.overlay(str, overlay,
                                                beginIndex + 1, lastIndex);
                                    }
                                }
                            }
                            if (emptyPass) {
                                emptyPass = false;
                                lastIndex = beginIndex + 1;
                            } else {
                                if (lastIndex != -1) {
                                    lastIndex = StringUtils
                                            .indexOf(str, ">", lastIndex);
                                }
                            }
                        }
                        beginIndex = lastIndex;
                    }
                }
                return str;

            }

}

And added custtom cxf logging bean in my cxf-bean.xml

<bean id="kpInInterceptor" class="com.kp.util.CustomLogInInterceptor" />

<cxf:bus>
        <cxf:inInterceptors>
            <ref bean="kpInInterceptor" />
        </cxf:inInterceptors>
        <cxf:inFaultInterceptors>
            <ref bean="kpInInterceptor" />
        </cxf:inFaultInterceptors>
</cxf:bus>

Note I've used Apache commons-lang3 jar for String manipulation. Similarly you can use for response as well


UPDATE

using Patterns and making keys configurable using properties.

Interceptor

public class CustomLogInInterceptor extends LoggingInInterceptor {

    private static final String MASK_PATTERN = "<\\s*{}\\s*>(.*)</\\s*{}\\s*>|<\\s*name\\s*>\\s*{}\\s*</\\s*name\\s*>\\s*<\\s*value\\s*>(.*)<";

    private Pattern pattern;

    private String[] keys;

    public void init() {
        StringBuilder builder = new StringBuilder();
        for (String str : keys) {
            builder.append(MASK_PATTERN.replace("{}", str));
            builder.append("|");
        }
        builder.setLength(builder.length()-1);
        pattern = Pattern.compile(builder.toString(), Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
    }

    public void setKeys(String[] keys) {
        this.keys = keys;
    }


    @Override
    protected String formatLoggingMessage(LoggingMessage loggingMessage) {

        String output = maskPasswords(loggingMessage.toString());
        return(output);
    }


    private String maskPasswords(String str) {

        Matcher matcher = pattern.matcher(str);
        final StringBuilder builder = new StringBuilder(str);
        while (matcher.find()) {

            int group = 1;
            while (group <= matcher.groupCount()) {
                if (matcher.group(group) != null) {
                    for (int i = matcher.start(group); i < matcher.end(group); i++) {
                        builder.setCharAt(i, '*');
                    }
                }
                group++;
            }
        }
        return builder.toString();

    }

}

Bean creation

<bean id="kpInInterceptor" class="com.kp.util.CustomLogInInterceptor" init-method="init">
    <property name="keys">
        <array value-type="java.lang.String">
            <value>password</value>
            <value>accountId</value>
        </array>
    </property>
</bean>

Sample Input

<?xml version="1.0" encoding="UTF-8"?>
<test>
    <hello>adffas</hello>
    <vsdsd>dfsdf</vsdsd>
    <password>sdfsfs</password>
    <sdfsfsf>sdfsfsf</sdfsfsf>
    <password>3434</password>
    <name>password</name>
    <value>sdfsfs</value>
    <password />
    <name>password</name>
    <value />
    <accountId>123456</accountId>
    <hello>
        <inner1>
            <password>
                <password>sdfsfs</password>
            </password>
        </inner>
    </hello>
</test>

And the output

<?xml version="1.0" encoding="UTF-8"?>
<test>
    <hello>adffas</hello>
    <vsdsd>dfsdf</vsdsd>
    <password>******</password>
    <sdfsfsf>sdfsfsf</sdfsfsf>
    <password>****</password>
    <name>password</name>
    <value>******</value>
    <password />
    <name>password</name>
    <value />
    <accountId>******</accountId>
    <hello>
        <inner1>
            <password>
                <password>******</password>
            </password>
        </inner>
    </hello>
</test>
like image 71
Karthik Prasad Avatar answered Sep 22 '22 21:09

Karthik Prasad


I have written an open source library aimed at effectively pretty-printing SOAP messages: xml-formatter-components-cxf. Features include:

  • High-performance reformatting of XML
  • Advanced filtering options
    • Max text and/or CDATA node sizes
    • Reformatting of XML within text and/or CDATA nodes
    • Anonymize of element and/or attribute contents
    • Removal of subtrees

complete configuration reference for spring.

Edit: The above library has been refactored to a more generic library which probably is better for most normal use-cases, i.e. no XML wrapped in XML.

like image 45
ThomasRS Avatar answered Sep 23 '22 21:09

ThomasRS