我创建了一个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方法。我很困惑。
答案 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;
}
}