Джексон PropertyFilter не используется при сериализации XML - PullRequest
0 голосов
/ 06 июня 2019

Я создал Jackson PropertyFilter и зарегистрировал его с помощью XmlMapper, но он не используется для фильтрации свойств, возвращаемых Spring @RestController.

Я создал и использовал PropertyFilter Джексона для фильтрации результатов JSON, полученных ObjectMapper для Spring @RestController. Я пытаюсь включить те же функции для XML, но не могу заставить его работать.

Я попытался зарегистрировать фильтр непосредственно в экземпляре XmlMapper и через Jackson2ObjectMapperBuilder. В обоих случаях это не называется.

Я прошел через код, и XmlBeanSerializer, кажется, имеет ссылку на фильтр, но фильтр никогда не вызывается.

Я создал класс LogAllPropertyFilter, чтобы просто вести журнал, если фильтр вызывается и сообщения журнала никогда не создаются.

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);
}
}

Я создаю и регистрирую PropertyFilter следующим образом:

<bean id="logAllFilter" class="calpers.eai.config.auth.jacksonpropertyfilter.LogAllPropertyFilter" />

<bean id="logAllFilterProvider"
    class="com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider">
    <constructor-arg>
        <map>
            <entry key="logAllFilter"
                value-ref="logAllFilter" />
        </map>
    </constructor-arg>
</bean>

<bean id="xmlObjectMapper"
    class="com.fasterxml.jackson.dataformat.xml.XmlMapper" />

<bean  class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="xmlObjectMapper" />
    <property name="targetMethod" value="setFilterProvider" />
    <property name="arguments" ref="logAllFilterProvider" />
</bean>

<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="xmlObjectMapper" />
    <property name="targetMethod" value="disable" />
    <property name="arguments" value="WRITE_DATES_AS_TIMESTAMPS" />
</bean>

<!-- indent json - disable this in prod -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="xmlObjectMapper" />
    <property name="targetMethod" value="enable" />
    <property name="arguments" value="INDENT_OUTPUT" />
</bean>

<bean id="xmlConverter" class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
    <constructor-arg ref="xmlObjectMapper" />
</bean>


<mvc:annotation-driven>
    <mvc:message-converters>
        <!-- json works -->
        <ref bean="jsonConverter" />

                    <!-- xml doesn't work -->
        <ref bean="xmlConverter" />  
    </mvc:message-converters>
</mvc:annotation-driven>

Вывод XML имеет отступ , поэтому я знаю, что экземпляр XmlMapper получен. Однако методы PropertyFilter никогда не вызываются. Я в тупике.

1 Ответ

0 голосов
/ 06 июня 2019

Фильтр не будет применен, если класс не связан каким-либо образом с фильтром.Обычно используется аннотация, но в этом случае мне нужно отфильтровать свойства для всех объектов, независимо от их происхождения, поэтому мы будем использовать встраивание в общий базовый класс всех объектов 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>

С добавлением этого в конфигурацию мои фильтры работают на каждом объекте XML, обслуживаемом моим Spring MVC @RestController.

Вот удобный фильтр для управления доступом к свойствам класса на основе ролей безопасности в 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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...