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