有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java将序列化过滤器(ObjectInputFilter)与KeyClope适配器和Memcached一起使用

我正在使用SpringSecurity密钥斗篷适配器12.0.1和Memcached进行会话复制。从Memcached加载会话时,Keyclaok适配器中的类将被反序列化。类KeycloakSecurityContext的read方法包含

DelegatingSerializationFilter.builder()
    .addAllowedClass(KeycloakSecurityContext.class)
    .setFilter(in);

。。。它为当前ObjectInputStream设置一个ObjectFilter

我发现我必须将系统属性jdk.serialSetFilterAfterRead设置为true,否则会抛出异常filter can not be set after an object has been read,并且DelegatingSerializationFilter抱怨无法设置对象筛选器。结果是根本没有应用任何对象筛选器,日志中会出现垃圾邮件警告

在应用jdk.serialSetFilterAfterRead之后,我遇到了具有memcached属性的ObjectInputStream包含的其他类没有从DelegatingSerializationFilter设置为允许的类,例如:

org.springframework.security.web.savedrequest.DefaultSavedRequest

结果是这些类在序列化过程中被拒绝

所以我的问题是:有人知道如何配置对象过滤器以使序列化正常工作吗


共 (3) 个答案

  1. # 1 楼答案

    我知道这个问题是关于Memcached的,但我在这里使用Spring Session w/ Hazelcast时出现了完全相同的错误。我会把我发现的东西贴出来,以防对别人有帮助

    基本上,Spring会话Hazelcast 2.4.2提供了HazelcastSessionSerializer。我以前使用的是2.3.2,它没有这个类,默认为Java序列化,在反序列化DefaultSavedRequest时导致“拒绝”错误

    latest configuration example包含一些新行来设置此序列化程序:

    SerializerConfig serializerConfig = new SerializerConfig();
    serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
    config.getSerializationConfig().addSerializerConfig(serializerConfig);
    

    这为我解决了这个问题

  2. # 2 楼答案

    KeycloakSecurityContext和其他related classes中实现的代码包括在内,以缓解与CVE CVE-2020-1714相关的bug

    A flaw was found in Keycloak, where the code base contains usages of ObjectInputStream without type checks. This flaw allows an attacker to inject arbitrarily serialized Java Objects, which would then get deserialized in a privileged context and potentially lead to remote code execution.

    该解决方案试图通过实现自定义^{}来解决上述漏洞

    如错误描述所示,Java序列化过滤机制背后的思想是防止反序列化漏洞,这些漏洞可能导致远程代码执行,从而导致应用程序出现安全问题

    在许多情况下,例如在处理会话时,存储在下面示例中的会话中的多个对象被序列化,随后将一起反序列化,换句话说,它们将被写入相同的^{},然后从相同的^{}读取

    当应用ObjectInputFilter时,可以在多个级别上执行—流程、应用程序和特定ObjectInputStream—只有满足配置的filter pattern的对象将被反序列化;取决于模式本身,其余部分要么被拒绝,要么将决策委托给process-wide filter if one exists

    请考虑下面的示例,其中^ {< 8CD> }、^ {CD9>}、^ {< CD10>}和^ {CD11>}是类,并且过滤器^ {< CD12>}应用在假设的对象输入流上,以顶部大理石图线表示,是应用过滤器后的荒漠化结果的下端:

    ObjectInputFilter semantics

    此模式可以根据模块、包和/或单个类来定义,并且可以设置为jdk.serialFilter系统属性,或者通过编辑java.security属性文件来设置

    您还可以创建自定义过滤器。它们使用ObjectInputFilter提供的API实现,并允许更细粒度的序列化控制,因为它们可以特定于特定的ObjectInputStream

    请参阅相关的Oracle Serialization documentation了解更多信息

    Keycloak序列化筛选器使用实用程序类^{}

    在提供的实现中,过滤器应用于^{} ^{} method内部:

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        DelegatingSerializationFilter.builder()
                .addAllowedClass(KeycloakSecurityContext.class)
                .setFilter(in);
        in.defaultReadObject();
    
        token = parseToken(tokenString, AccessToken.class);
        idToken = parseToken(idTokenString, IDToken.class);
    }
    

    因此,如您所指出的,为了使其正常工作,有必要在运行程序时将jdk.serialSetFilterAfterRead系统属性定义为true

    除非先前的非进程范围筛选器(请参阅Oracle documentation中标记为设置进程范围自定义筛选器的部分)已应用于^{,否则将始终应用此筛选器;它将由^{}方法保证,它也是checked by the ^{} class

    private void setFilter(ObjectInputStream ois, String filterPattern) {
        LOG.debug("Using: " + serializationFilterAdapter.getClass().getSimpleName());
    
        if (serializationFilterAdapter.getObjectInputFilter(ois) == null) {
            serializationFilterAdapter.setObjectInputFilter(ois, filterPattern);
        }
    }
    

    换句话说,避免应用自定义筛选器的唯一方法是首先在目标ObjectInputStream上提供初始筛选器,这一次包含会话数据。如docs所示:

    The filter mechanism is called for each new object in the stream. If more than one active filter (process-wide filter, application filter, or stream-specific filter) exists, only the most specific filter is called.

    这个初始过滤器的创建方式在很大程度上取决于实际处理反序列化功能的代码

    在您的用例中,您可能正在使用memcached-session-manager,Github中的original version或某些more updated projects

    在正常用例中,memcached-session-manager中的会话主要由^{}中定义的代码处理

    这个类使用^{}来处理Java序列化内容

    TranscoderService反过来将这一责任委托给^{}^{}的适当实施

    ^{}和相关类^{}是这些接口的默认实现

    请注意JavaSerializationTranscoderdeserializeAttributes方法,它定义了会话反序列化的逻辑:

    /**
      * Get the object represented by the given serialized bytes.
      *
      * @param in
      *            the bytes to deserialize
      * @return the resulting object
      */
    @Override
    public ConcurrentMap<String, Object> deserializeAttributes(final byte[] in ) {
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;
        try {
            bis = new ByteArrayInputStream( in );
            ois = createObjectInputStream( bis );
    
            final ConcurrentMap<String, Object> attributes = new ConcurrentHashMap<String, Object>();
            final int n = ( (Integer) ois.readObject() ).intValue();
            for ( int i = 0; i < n; i++ ) {
                final String name = (String) ois.readObject();
                final Object value = ois.readObject();
                if ( ( value instanceof String ) && ( value.equals( NOT_SERIALIZED ) ) ) {
                    continue;
                }
                if ( LOG.isDebugEnabled() ) {
                    LOG.debug( "  loading attribute '" + name + "' with value '" + value + "'" );
                }
                attributes.put( name, value );
            }
    
            return attributes;
        } catch ( final ClassNotFoundException e ) {
            LOG.warn( "Caught CNFE decoding "+ in.length +" bytes of data", e );
            throw new TranscoderDeserializationException( "Caught CNFE decoding data", e );
        } catch ( final IOException e ) {
            LOG.warn( "Caught IOException decoding "+ in.length +" bytes of data", e );
            throw new TranscoderDeserializationException( "Caught IOException decoding data", e );
        } finally {
            closeSilently( bis );
            closeSilently( ois );
        }
    }
    

    如您所见,问题在于由输入字节数组表示的会话信息可能包含多个属性,并且所有属性都是从同一个ObjectInputStream反序列化的。一旦Keycloak{}应用于此ObjectInputStream,如您所示,它将拒绝筛选器不允许的其余类。原因是DelegatingSerializationFilter{a24}是正在构造的过滤器模式的最终!*,不包括t以外的所有内容他明确地提供了基于类和文本的模式(以及java.util.*类以允许集合)

    为了避免这个问题,请尝试提供您自己的SessionAttributesTranscoder实现,并包含一个类似于deserializeAttributes的方法,但在构造的ObjectInputStream上定义一个初始过滤器

    例如(请原谅,为了定义整个类,您可能可以以某种方式重用JavaSerializationTranscoder的代码):

    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.NotSerializableException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.OutputStream;
    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ConcurrentMap;
    
    import org.apache.catalina.session.StandardSession;
    import org.apache.catalina.util.CustomObjectInputStream;
    import org.apache.juli.logging.Log;
    import org.apache.juli.logging.LogFactory;
    
    import de.javakaffee.web.msm.MemcachedSessionService.SessionManager;
    
    public class CustomJavaSerializationTranscoder implements SessionAttributesTranscoder {
    
        private static final Log LOG = LogFactory.getLog( CustomJavaSerializationTranscoder.class );
    
        private static final String EMPTY_ARRAY[] = new String[0];
    
        /**
         * The dummy attribute value serialized when a NotSerializableException is
         * encountered in <code>writeObject()</code>.
         */
        protected static final String NOT_SERIALIZED = "___NOT_SERIALIZABLE_EXCEPTION___";
    
        private final SessionManager _manager;
    
        /**
         * Constructor.
         *
         * @param manager
         *            the manager
         */
        public CustomJavaSerializationTranscoder() {
            this( null );
        }
    
        /**
         * Constructor.
         *
         * @param manager
         *            the manager
         */
        public CustomJavaSerializationTranscoder( final SessionManager manager ) {
            _manager = manager;
        }
    
        /**
         * {@inheritDoc}
         */
        @Override
        public byte[] serializeAttributes( final MemcachedBackupSession session, final ConcurrentMap<String, Object> attributes ) {
            if ( attributes == null ) {
                throw new NullPointerException( "Can't serialize null" );
            }
    
            ByteArrayOutputStream bos = null;
            ObjectOutputStream oos = null;
            try {
                bos = new ByteArrayOutputStream();
                oos = new ObjectOutputStream( bos );
    
                writeAttributes( session, attributes, oos );
    
                return bos.toByteArray();
            } catch ( final IOException e ) {
                throw new IllegalArgumentException( "Non-serializable object", e );
            } finally {
                closeSilently( bos );
                closeSilently( oos );
            }
    
        }
    
        private void writeAttributes( final MemcachedBackupSession session, final Map<String, Object> attributes,
                final ObjectOutputStream oos ) throws IOException {
    
            // Accumulate the names of serializable and non-serializable attributes
            final String keys[] = attributes.keySet().toArray( EMPTY_ARRAY );
            final List<String> saveNames = new ArrayList<String>();
            final List<Object> saveValues = new ArrayList<Object>();
            for ( int i = 0; i < keys.length; i++ ) {
                final Object value = attributes.get( keys[i] );
                if ( value == null || session.exclude( keys[i], value ) ) {
                    continue;
                } else if ( value instanceof Serializable ) {
                    saveNames.add( keys[i] );
                    saveValues.add( value );
                } else {
                    if ( LOG.isDebugEnabled() ) {
                        LOG.debug( "Ignoring attribute '" + keys[i] + "' as it does not implement Serializable" );
                    }
                }
            }
    
            // Serialize the attribute count and the Serializable attributes
            final int n = saveNames.size();
            oos.writeObject( Integer.valueOf( n ) );
            for ( int i = 0; i < n; i++ ) {
                oos.writeObject( saveNames.get( i ) );
                try {
                    oos.writeObject( saveValues.get( i ) );
                    if ( LOG.isDebugEnabled() ) {
                        LOG.debug( "  storing attribute '" + saveNames.get( i ) + "' with value '" + saveValues.get( i ) + "'" );
                    }
                } catch ( final NotSerializableException e ) {
                    LOG.warn( _manager.getString( "standardSession.notSerializable", saveNames.get( i ), session.getIdInternal() ), e );
                    oos.writeObject( NOT_SERIALIZED );
                    if ( LOG.isDebugEnabled() ) {
                        LOG.debug( "  storing attribute '" + saveNames.get( i ) + "' with value NOT_SERIALIZED" );
                    }
                }
            }
    
        }
    
        /**
         * Get the object represented by the given serialized bytes.
         *
         * @param in
         *            the bytes to deserialize
         * @return the resulting object
         */
        @Override
        public ConcurrentMap<String, Object> deserializeAttributes(final byte[] in ) {
            ByteArrayInputStream bis = null;
            ObjectInputStream ois = null;
            try {
                bis = new ByteArrayInputStream( in );
                ois = createObjectInputStream( bis );
    
                // Fix deserialization
                fixDeserialization(ois);
    
                final ConcurrentMap<String, Object> attributes = new ConcurrentHashMap<String, Object>();
                final int n = ( (Integer) ois.readObject() ).intValue();
                for ( int i = 0; i < n; i++ ) {
                    final String name = (String) ois.readObject();
                    final Object value = ois.readObject();
                    if ( ( value instanceof String ) && ( value.equals( NOT_SERIALIZED ) ) ) {
                        continue;
                    }
                    if ( LOG.isDebugEnabled() ) {
                        LOG.debug( "  loading attribute '" + name + "' with value '" + value + "'" );
                    }
                    attributes.put( name, value );
                }
    
                return attributes;
            } catch ( final ClassNotFoundException e ) {
                LOG.warn( "Caught CNFE decoding "+ in.length +" bytes of data", e );
                throw new TranscoderDeserializationException( "Caught CNFE decoding data", e );
            } catch ( final IOException e ) {
                LOG.warn( "Caught IOException decoding "+ in.length +" bytes of data", e );
                throw new TranscoderDeserializationException( "Caught IOException decoding data", e );
            } finally {
                closeSilently( bis );
                closeSilently( ois );
            }
        }
    
        private ObjectInputStream createObjectInputStream( final ByteArrayInputStream bis ) throws IOException {
            final ObjectInputStream ois;
            ClassLoader classLoader = null;
            if ( _manager != null && _manager.getContext() != null ) {
                classLoader = _manager.getContainerClassLoader();
            }
            if ( classLoader != null ) {
                ois = new CustomObjectInputStream( bis, classLoader );
            } else {
                ois = new ObjectInputStream( bis );
            }
            return ois;
        }
    
        private void closeSilently( final OutputStream os ) {
            if ( os != null ) {
                try {
                    os.close();
                } catch ( final IOException f ) {
                    // fail silently
                }
            }
        }
    
        private void closeSilently( final InputStream is ) {
            if ( is != null ) {
                try {
                    is.close();
                } catch ( final IOException f ) {
                    // fail silently
                }
            }
        }
    
        // Helper method, reusing the `DelegatingSerializationFilter` class, which in fact is convenient because of its portability
        // accross JDK versions, to define an allow everything pattern
        // Probably it should be improved to restrict to certain patterns to
        // prevent security vulnerabilities
        private void fixDeserialization(ObjectInputStream ois) {
          DelegatingSerializationFilter.builder()
              .addAllowedPattern("*")
              .setFilter(ois);
        }
    
    }
    

    现在,定义一个自定义TranscoderFactory。这次让我们重用类JavaSerializationTranscoderFactory的代码:

    import de.javakaffee.web.msm.MemcachedSessionService.SessionManager;
    
    public class CustomJavaSerializationTranscoderFactory extends JavaSerializationTranscoderFactory {
    
        /**
         * {@inheritDoc}
         */
        @Override
        public SessionAttributesTranscoder createTranscoder( final SessionManager manager ) {
            return new CustomJavaSerializationTranscoder( manager );
        }
    
    }
    

    将这些类与memcached-session-manager中的其余库一起放在类路径上,并为transcoderFactoryClass{}配置属性提供一个方便的值,如docs中所示:

    The class name of the factory that creates the transcoder to use for serializing/deserializing sessions to/from memcached. The specified class must implement de.javakaffee.web.msm.TranscoderFactory and provide a no-args constructor.

    我没有能力测试解决方案,尽管一个简单的测试似乎可以正常工作:

    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.NotSerializableException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.OutputStream;
    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ConcurrentMap;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    public class SerializationTest {
    
      private static final Log LOG = LogFactory.getLog( SerializationTest.class );
    
      protected static final String NOT_SERIALIZED = "___NOT_SERIALIZABLE_EXCEPTION___";
    
      private static final String EMPTY_ARRAY[] = new String[0];
    
      public byte[] serializeAttributes( final ConcurrentMap<String, Object> attributes ) {
        if ( attributes == null ) {
          throw new NullPointerException( "Can't serialize null" );
        }
    
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        try {
          bos = new ByteArrayOutputStream();
          oos = new ObjectOutputStream( bos );
    
          writeAttributes( attributes, oos );
    
          return bos.toByteArray();
        } catch ( final IOException e ) {
          throw new IllegalArgumentException( "Non-serializable object", e );
        } finally {
          closeSilently( bos );
          closeSilently( oos );
        }
    
      }
    
      private void writeAttributes(final Map<String, Object> attributes, final ObjectOutputStream oos ) throws IOException {
    
        // Accumulate the names of serializable and non-serializable attributes
        final String keys[] = attributes.keySet().toArray( EMPTY_ARRAY );
        final List<String> saveNames = new ArrayList<String>();
        final List<Object> saveValues = new ArrayList<Object>();
        for ( int i = 0; i < keys.length; i++ ) {
          final Object value = attributes.get( keys[i] );
          if ( value == null ) {
            continue;
          } else if ( value instanceof Serializable) {
            saveNames.add( keys[i] );
            saveValues.add( value );
          } else {
            if ( LOG.isDebugEnabled() ) {
              LOG.debug( "Ignoring attribute '" + keys[i] + "' as it does not implement Serializable" );
            }
          }
        }
    
        // Serialize the attribute count and the Serializable attributes
        final int n = saveNames.size();
        oos.writeObject( Integer.valueOf( n ) );
        for ( int i = 0; i < n; i++ ) {
          oos.writeObject( saveNames.get( i ) );
          try {
            oos.writeObject( saveValues.get( i ) );
            if ( LOG.isDebugEnabled() ) {
              LOG.debug( "  storing attribute '" + saveNames.get( i ) + "' with value '" + saveValues.get( i ) + "'" );
            }
          } catch ( final NotSerializableException e ) {
            LOG.warn(  "standardSession.notSerializable" + saveNames.get( i ), e );
            oos.writeObject( NOT_SERIALIZED );
            if ( LOG.isDebugEnabled() ) {
              LOG.debug( "  storing attribute '" + saveNames.get( i ) + "' with value NOT_SERIALIZED" );
            }
          }
        }
    
      }
    
      public ConcurrentMap<String, Object> deserializeAttributes(final byte[] in ) {
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;
        try {
          bis = new ByteArrayInputStream( in );
          ois = new ObjectInputStream( bis );
    
          fixDeserialization(ois);
    
          final ConcurrentMap<String, Object> attributes = new ConcurrentHashMap<String, Object>();
          final int n = ( (Integer) ois.readObject() ).intValue();
          for ( int i = 0; i < n; i++ ) {
            final String name = (String) ois.readObject();
            final Object value = ois.readObject();
            if ( ( value instanceof String ) && ( value.equals( NOT_SERIALIZED ) ) ) {
              continue;
            }
            if ( LOG.isDebugEnabled() ) {
              LOG.debug( "  loading attribute '" + name + "' with value '" + value + "'" );
            }
            attributes.put( name, value );
          }
    
          return attributes;
        } catch ( final ClassNotFoundException e ) {
          LOG.warn( "Caught CNFE decoding "+ in.length +" bytes of data", e );
          throw new RuntimeException( "Caught CNFE decoding data", e );
        } catch ( final IOException e ) {
          LOG.warn( "Caught IOException decoding "+ in.length +" bytes of data", e );
          throw new RuntimeException( "Caught IOException decoding data", e );
        } finally {
          closeSilently( bis );
          closeSilently( ois );
        }
      }
    
      private void fixDeserialization(ObjectInputStream ois) {
        DelegatingSerializationFilter.builder()
            .addAllowedPattern("*")
            .setFilter(ois);
      }
    
      private void closeSilently( final OutputStream os ) {
        if ( os != null ) {
          try {
            os.close();
          } catch ( final IOException f ) {
            // fail silently
          }
        }
      }
    
      private void closeSilently( final InputStream is ) {
        if ( is != null ) {
          try {
            is.close();
          } catch ( final IOException f ) {
            // fail silently
          }
        }
      }
    
      public static void main(String[] args) throws Exception{
    
        Person person = new Person("Sherlock Holmes","Consulting detective");
        Address address = new Address("221B Baker Street");
        ConcurrentMap<String, Object> attributes = new ConcurrentHashMap<String, Object>();
        attributes.put("person", person);
        attributes.put("address", address);
    
        SerializationTest test = new SerializationTest();
        byte[] in = test.serializeAttributes(attributes);
    
        System.setProperty("jdk.serialSetFilterAfterRead", "true");
    
        ConcurrentMap<String, Object> attributesAfter = test.deserializeAttributes(in);
        System.out.println(attributesAfter);
      }
    }
    

    PersonAddress两个简单的POJO。请注意定义Person{}方法:

    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
    
    public class Person implements Serializable {
    
      private static final long serialVersionUID = 1L;
    
      String name;
      String title;
    
      public Person() {
      }
    
      public Person(String name, String title) {
        this.name = name;
        this.title = title;
      }
    
      private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
      }
    
      private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        DelegatingSerializationFilter.builder()
            .addAllowedClass(Person.class)
            .setFilter(in);
        in.defaultReadObject();
      }
    
      @Override
      public String toString() {
        return "Person{" +
            "name='" + name + '\'' +
            ", title='" + title + '\'' +
            '}';
      }
    }
    
    import java.io.Serializable;
    
    public class Address implements Serializable {
    
      private static final long serialVersionUID = 1L;
    
      String address;
    
      public Address() {
      }
    
      public Address(String address) {
        this.address = address;
      }
    
      @Override
      public String toString() {
        return "Address{" +
            "address='" + address + '\'' +
            '}';
      }
    }
    

    请注意,过滤器的存在是为了防止安全缺陷:正如代码注释中所建议的,建议改进fixDeserialization中实现的逻辑,以某种方式限制会话应该包含的可能类,而不是像示例中那样使用通配符*

    事实上,这个功能可以包含在memcached-session-manager库中,可能是通过定义某种配置属性serialFilter,例如,应该向指定的底层Java反序列化机制提供哪个值(有效的过滤器模式)

    我创建了这个项目的一个分支:它仍然是一个WIP,但是请看this commit,我希望你能理解这个想法。完成后,我将try to pull a requestforked repo

  3. # 3 楼答案

    发生了什么事

    您的问题是memcached会话处理程序同时反序列化了一组不同的类。也就是说,在相同的ObjectInputStream中。这是因为您的(Tomcat?或任何应用程序服务器,无所谓)会话由许多不同的对象组成;因此memcached将它们逐个序列化到其存储中,然后以相同的方式将它们反序列化回应用程序中。这非常有效,直到您反序列化一个bitchy类的对象,KeycloakSecurityContext,它引入了一个邪恶的过滤器😱 在你的ObjectInputStreamLink to the evil class

    之后发生的事情是,允许使用keydove类,因此它一直被正确地反序列化,但现在神奇地不允许使用所有其他类,因为keydove通过在其过滤器末尾添加“!*”来排除所有类Link to the filter

    现在你头痛得厉害😵 你在想“废话够多了,我该怎么解决这个问题?”。我明白,而且我头疼得厉害,继续读下去


    解决方案

    这个问题有多种解决方案(好消息!😀) 取决于你胆量有多大(😣) 去更改应用程序中的内容

    正确的™ 解决方案是确保KeycloakSecurityContext对象(以及引入此类过滤器的任何其他类)在它们自己的流中反序列化,而不是在相同的公共流中。现在,我不是这个memcached会话处理程序方面的专家,所以我不知道您对整个会话反序列化过程的实际控制程度;但我很有信心,它应该是有可能侵入它

    如果由于某种原因,这是不可能的,那么您需要通过扩展类来重写KeycloakSecurityContext中的过滤器。如果你选择了这条路(飞,你们这些傻瓜😈) 你必须使用过滤器并决定要做什么。我认为这是最正确的方法™ 在本例中,是从KeycloakSecurityContext中完全删除过滤器,然后在应用程序级别添加过滤器,在应用程序级别定义允许反序列化的所有类和包。 Link on how to do it

    另一种有点老套但更直接的方法是在类扩展KeycloakSecurityContext中的过滤器中添加所有相关类(说真的,不要这样做)


    好公民奖励积分

    有些人甚至可能认为这是memcached-session-manager中的一个bug,或者说是一个缺失的功能。也许值得在那里打开一个话题,看看他们是怎么想的。 https://github.com/magro/memcached-session-manager


    现在是核心的东西

    为无所事事的书呆子们深入实际地解释这个问题

    考虑以下场景:您有两种类型的类:AB,两个类都是Serializable

    A是一个简单的、有点幼稚的类,在其readObject方法中不添加任何自定义过滤器。在同一个ObjectInputStream中,反序列化对象:a1, a2, a3, a4。类A的所有实例。 所有对象都反序列化正确,操耶!🐒

    相反,类B在其readObject中有一个非常平均的过滤器,它只允许在当前ObjectInputStream中反序列化相同类B的对象

    现在我们读取流中的以下对象:a1, b1, a2, b2

    • a1反序列化正确,我们的{}中现在没有筛选器,太好了
    • b1反序列化正确,酷;但是b1也是一个小婊子(作为类B的一个实例),所以它为我们喜爱的流设置了一个新的过滤器,从现在起,类B是唯一允许反序列化的类
    • 然后是a2,它是未反序列化的(whaaat😩), 因为b1,在上一步中,设置了一个不包含类a的过滤器,这是所有发生在同一个ObjectInputStream实例中的情况
    • 最后,b2被正确反序列化,因为类B包含在过滤器中