Search code examples
javaloggingsoapcxffiltering

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?


Solution

  • 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>