序列化XML时不使用Jackson PropertyFilter

时间:2019-06-06 17:23:08

标签: spring-mvc jackson-databind

我创建了一个Jackson的PropertyFilter,并在XmlMapper中注册了它,但是它不用于过滤从Spring @RestController返回的属性。

我已经创建并使用了一个Jackson PropertyFilter来过滤ObjectMapper为Spring @RestController产生的JSON结果。我正在尝试为XML启用相同的功能,但无法使其正常工作。

我尝试直接在XmlMapper实例上并通过Jackson2ObjectMapperBuilder注册过滤器。在两种情况下都不会调用。

我已经遍历了代码,并且XmlBeanSerializer似乎具有对该过滤器的引用,但是从未调用该过滤器。

我创建了一个LogAllPropertyFilter类,以仅记录是否调用了过滤器并且从未生成任何日志消息。

"printDocLink"

我正在这样创建和注册PropertyFilter:

public class LogAllPropertyFilter extends SimpleBeanPropertyFilter implements PropertyFilter {
private Logger logger = LoggerFactory.getLogger(getClass());

@Override
public void serializeAsField(Object pojo, JsonGenerator gen, SerializerProvider prov, PropertyWriter writer)
        throws Exception {
    logger.info(" *** *** serializeAsField {}.{}", 
            pojo.getClass().getSimpleName(),
            writer.getName());
    super.serializeAsField(pojo, gen, prov, writer);
}

@Override
public void serializeAsElement(Object elementValue, JsonGenerator gen, SerializerProvider prov,
        PropertyWriter writer) throws Exception {
    logger.info(" *** *** serializeAsElement {}.{}", 
            elementValue.getClass().getSimpleName(),
            writer.getName());
    super.serializeAsElement(elementValue, gen, prov, writer);
}

@SuppressWarnings("deprecation")
@Override
public void depositSchemaProperty(PropertyWriter writer, ObjectNode propertiesNode, SerializerProvider provider)
        throws JsonMappingException {
    logger.info(" *** *** depositSchemaProperty {} (deprecated)",
            writer.getName());
    super.depositSchemaProperty(writer, propertiesNode, provider);
}

@Override
public void depositSchemaProperty(PropertyWriter writer, JsonObjectFormatVisitor objectVisitor,
        SerializerProvider provider) throws JsonMappingException {
    logger.info(" *** *** depositSchemaProperty {} (deprecated)",
            writer.getName());
    super.depositSchemaProperty(writer, objectVisitor, provider);
}
}

XML输出 已缩进,因此我知道XmlMapper实例已被拾取。但是,永远不会调用PropertyFilter方法。我很困惑。

1 个答案:

答案 0 :(得分:0)

除非将类以某种方式链接到过滤器,否则将不应用过滤器。通常使用注释,但是在这种情况下,我需要过滤所有对象的属性,而不论它们的出处如何,因此我们将在所有Java对象的通用基类上使用混合方法:

<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="xmlObjectMapper" />
    <property name="targetMethod" value="addMixIn" />
    <property name="arguments">
        <list>
            <value type="java.lang.Class">java.lang.Object</value>
            <value type="java.lang.Class">eai.config.auth.jacksonpropertyfilter.SecurityRoleAwareJacksonMixIn</value>
        </list>
    </property>
</bean>

将其添加到配置中后,我的过滤器将在由Spring MVC @RestController服务的每个XML对象上运行。

这是一个方便的过滤器,用于基于Spring Security中的安全角色来控制对类属性的访问。享受吧!

package eai.config.auth.jacksonpropertyfilter;

import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.ldap.userdetails.LdapAuthority;

import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;

import eai.config.auth.jacksonpropertyfilter.xml.SecurityRole;
import eai.config.refreshable.Refreshable;

/**
 * Filters based on the union of properties a principal can view. In JsonViewConfiguration a user
 * with multiple views will be assigned the highest ranked view and only see the properties that are
 * included in that view. With SecurityRoleAwareJacksonFilterImpl, the user will see any property they
 * have access to based on ALL the groups they are members of. Therefore, it is the union of
 * all @JsonView's.
 *
 * This class should be instantiated as a Spring Bean, probably in the XML config to maximize
 * configuration options that avoid a re-compile.
 *
 * @author TPerry2
 */
public class SecurityRoleAwareJacksonFilterImpl extends SimpleBeanPropertyFilter
        implements SecurityRoleAwareJacksonFilter, Refreshable {
    private final Logger logger = LoggerFactory.getLogger(
            SecurityRoleAwareJacksonFilterImpl.class);
    Map<Class<?>, Map<String, Collection<SecurityRole>>> classPropertyRoles = 
            new HashMap<>();
    List<SecurityRoleToClassPropertyReader> securityRoleToClassPropertyReaders = 
            new ArrayList<>();

    private ConcurrentHashMap<String, String> knownUserNoRole = 
            new ConcurrentHashMap<>();
    private ConcurrentHashMap<Class<?>, Set<String>> classPropsWithNoAccess = 
            new ConcurrentHashMap<>();


    /**
     * Add mapping for what class properties a LDAP role can view.
     *
     * @param securityRoleToClassPropertyXmlReaders to obtain mapping data from.
     * @throws ClassNotFoundException if the java class can not be found.
     * @throws IOException when security role to class property XML files can't be read.
     */
    @Override
    @Autowired
    public void setSecurityRoleToClassPropertyReaders(
            List<SecurityRoleToClassPropertyReader> securityRoleToClassPropertyReaders)
            throws ClassNotFoundException, IOException {
        this.securityRoleToClassPropertyReaders = securityRoleToClassPropertyReaders;
        loadClassPropertyRoles();
    }


    /**
     * Method called to determine whether property will be included
     * (if 'true' returned) or filtered out (if 'false' returned)
     */
    protected boolean include(BeanPropertyWriter writer) {
        AnnotatedMember memberToSerialize = writer.getMember();
        if (memberToSerialize == null) {
            logger.warn("Could not get member to serialize for writer {}",
                    writer.getClass().getName());
            return false;
        }
        final Class<?> clazz = memberToSerialize.getDeclaringClass();
        return include(clazz, writer.getName());
    }

    /**
     * Method called to determine whether property will be included
     * (if 'true' returned) or filtered out (if 'false' returned)
     */
    protected boolean include(PropertyWriter writer) {
        AnnotatedMember memberToSerialize = writer.getMember();
        if (memberToSerialize == null) {
            logger.warn("Could not get member to serialize for writer {}",
                    writer.getClass().getName());
            return false;
        }
        final Class<?> clazz = memberToSerialize.getDeclaringClass();
        return include(clazz, writer.getName());
    }

    protected boolean include(
            Class<?> clazz,
            String propertyName) {
        logger.info("Checking {}.{}", clazz.getSimpleName(), propertyName);
        final Map<String, Collection<SecurityRole>> propertyLdapRoleMap = 
                classPropertyRoles.get(clazz);
        if (propertyLdapRoleMap != null) {
            final Collection<SecurityRole> securityRoles = 
                    propertyLdapRoleMap.get(propertyName);
            if (securityRoles != null && securityRoles.size() > 0) {
                Authentication auth = getAuthentication();

                if (isAuthorized(getGrantedAuthorities(auth), securityRoles)) {
                    logger.info("allowing {}.{}", clazz.getSimpleName(), propertyName);
                    return true;
                } else {
                    logUserNoRole(clazz, propertyName, securityRoles, auth);
                }
            } else {
                logPropertyWithNoAccess(clazz, propertyName);
            }
        } else {
            logPropertyWithNoAccess(clazz, "-- all properties --");
        }
        return false;
    }

    private void logUserNoRole(
            Class<?> clazz, 
            String propertyName, 
            Collection<SecurityRole> allowedRoles,
            Authentication auth) {
        if (!logger.isDebugEnabled()) {
            return;
        }

        String username = (auth == null ? "anonymous" : auth.getName());

        final String knownUserNoRoleString = "" 
                + clazz.getName() + "." + propertyName + "." 
                + username;

        boolean known = knownUserNoRole.containsKey(knownUserNoRoleString);
        if (!known) {           
            knownUserNoRole.put(knownUserNoRoleString, "");
            logger.debug("User {} does not have valid role for {}.{}. "
                    + "Requires one of {}", username, clazz.getName(), 
                    propertyName, allowedRoles);
        }
    }

    private void logPropertyWithNoAccess(Class<?> clazz, String propertyName) {
        Set<String> knownPropsWithNoAccess = classPropsWithNoAccess.get(clazz);

        if (knownPropsWithNoAccess == null) {
            logger.warn("No roles enable access to {}.{}", 
                    clazz.getSimpleName(), propertyName);
            knownPropsWithNoAccess = new HashSet<>();           
            classPropsWithNoAccess.put(clazz, knownPropsWithNoAccess);          
        }

        boolean wasAdded = false;
        synchronized (knownPropsWithNoAccess) {         
            wasAdded = knownPropsWithNoAccess.add(propertyName);
        }

        if (wasAdded) {
            logger.warn("No roles enable access to {}.{}", 
                    clazz.getSimpleName(), propertyName);           
        }
    }

    private boolean isAuthorized(
            Collection<? extends GrantedAuthority> grantedAuths,
            Collection<SecurityRole> securityRoles) {
        try {
            if (grantedAuths == null) {
                return false;
            }

            for (GrantedAuthority grantedAuth : grantedAuths) {
                if (grantedAuth instanceof LdapAuthority) {
                    LdapAuthority ldapAuth = (LdapAuthority) grantedAuth;


                    for (SecurityRole secRole : securityRoles) {
                        if (secRole.distinguishedNameIsAuthorized(
                        ldapAuth.getDn())) {
                            return true;
                        }

                        if (secRole.displayNameIsAuthorized(
                        ldapAuth.getAuthority())) {
                            return true;
                        }
                    }                   
                } else  {
                    for (SecurityRole secRole : securityRoles) {
                        if (secRole.displayNameIsAuthorized(
                        grantedAuth.getAuthority())) {
                            return true;
                        }
                    }
                }

            }

            return false;
        } catch (NullPointerException npe) {
            logger.error("FIXME", npe);
            return false;
        }
    }

    private Collection<? extends GrantedAuthority> getGrantedAuthorities(
            Authentication auth) {
        if (auth == null) {
            return Collections.emptyList();
        }

        try {
            return auth.getAuthorities();
        }
        catch (Exception e) {
            logger.error("Could not retrieve authorities", e);
            return Collections.emptyList();
        }
    }

    private Authentication getAuthentication() {
        try {
            SecurityContext secCtxt = SecurityContextHolder.getContext();
            if (secCtxt == null) {
                logger.warn("SecurityContextHolder.getContext() returned null, " +
                        + "no authorities present");
                return null;
            }
            Authentication auth = secCtxt.getAuthentication();
            if (auth == null) {
                logger.warn("SecurityContextHolder.getContext().getAuthentication() "
                        + "returned null, no authorities present");
            }
            return auth;
        } catch (Exception e) {
            logger.error("Could not retrieve Authentication", e);
            return null;
        }
    }


    private void loadClassPropertyRoles() {
        Map<Class<?>, Map<String, Collection<SecurityRole>>> newClassPropertyRoles = 
                new HashMap<>();

        for (SecurityRoleToClassPropertyReader reader : securityRoleToClassPropertyReaders) {
            Map<Class<?>, Map<String, Collection<SecurityRole>>> readerClassPropertyRoles = 
                    reader.loadClassPropertyRoles();

            for (Class<?> clazz : readerClassPropertyRoles.keySet()) {
                Map<String, Collection<SecurityRole>> propertyRoles = 
                        newClassPropertyRoles.get(clazz);
                if (propertyRoles == null) {
                    propertyRoles = new HashMap<>();
                    newClassPropertyRoles.put(clazz, propertyRoles);
                }

                for (String propertyName : readerClassPropertyRoles.get(clazz).keySet()) {
                    Collection<SecurityRole> allowedRolesForProp = 
                            propertyRoles.get(propertyName);

                    if (allowedRolesForProp == null) {
                        allowedRolesForProp = new ArrayList<>();
                        propertyRoles.put(propertyName, allowedRolesForProp);
                    }

                    Collection<SecurityRole> newLdapRoles = 
                            readerClassPropertyRoles.get(clazz).get(propertyName);
                    for (SecurityRole securityRole : newLdapRoles) {
                        if (!allowedRolesForProp.contains(securityRole)) {
                            allowedRolesForProp.add(securityRole);
                        }
                    }
                }
            }
        }

        this.classPropertyRoles = newClassPropertyRoles;
    }
}