【问题标题】:Redis, SpringBoot and HttpSession: should I encrypt the session data?Redis、SpringBoot 和 HttpSession:我应该加密会话数据吗?
【发布时间】:2016-04-07 20:27:21
【问题描述】:

我正在使用 Spring Boot 1.3.3 来构建 Web 应用程序。我使用 Redis 来处理会话。

我将在HttpSession 中设置一些“关键”数据,我想了解这将如何与 Redis 一起使用。信息存储在服务器端加上浏览器端的密钥还是所有数据都在用户浏览器的cookie中?

我想查看答案的文档参考或获得权威答案(例如 Pivotal 开发人员)。

【问题讨论】:

标签: session redis spring-boot session-variables spring-session


【解决方案1】:

虽然我同意这里其他答案的大部分内容,但没有其他答案真正回答了这个问题。 我将假设您在 Spring Boot 中使用带有 Redis 的 SpringSession。

为了使用 SpringSession,您可能已经(直接或间接)配置了一个扩展 SessionRepositoryFilter 的 servlet 过滤器。

SessionRepositoryFilter 使用SessionRepository。由于您使用的是 Redis,因此您的配置很可能使用了RedisOperationsSessionRepository

RedisOperationsSessionRepository 实现 SessionRepository,正如您可能已经猜到的那样,它最终负责基于密钥(在您的情况下,密钥可能存储为用户浏览器上的 cookie)来获取、存储和删除会话)。

默认情况下,RedisOperationSessionRepository 使用实现了RedisSerializerJdkSerializationRedisSerializer 在将会话数据传递给 Redis 之前序列化会话数据。

根据RedisOperationSessionRepository 的文档,可以通过setDefaultSerializer 方法设置RedisOperationSessionRepository 将使用的默认序列化程序。

理论上你可以扩展JdkSerializationRedisSerializer 并在那里执行加密和解密。 JdkSerializationRedisSerializer 看起来像这样:

public class JdkSerializationRedisSerializer implements RedisSerializer<Object> {

    private Converter<Object, byte[]> serializer = new SerializingConverter();
    private Converter<byte[], Object> deserializer = new DeserializingConverter();

    public Object deserialize(byte[] bytes) {
        if (SerializationUtils.isEmpty(bytes)) {
            return null;
        }

        try {
            return deserializer.convert(bytes);
        } catch (Exception ex) {
            throw new SerializationException("Cannot deserialize", ex);
        }
    }

    public byte[] serialize(Object object) {
        if (object == null) {
            return SerializationUtils.EMPTY_ARRAY;
        }
        try {
            return serializer.convert(object);
        } catch (Exception ex) {
            throw new SerializationException("Cannot serialize", ex);
        }
    }
}

因此添加加密的潜在方法可能如下所示:

public class CrypticRedisSerializer extends JdkSerializationRedisSerializer {

    @Override
    public Object deserialize(byte[] bytes) {
        byte[] decrpyted;
        try {
            decrpyted = EncryptionUtils.decrypt(bytes);
            return super.deserialize(decrpyted);
        } catch (NoSuchPaddingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (GeneralSecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // handle expections or allow to propagate, your choice!
        return null;
    }

    @Override
    public byte[] serialize(Object object) {
        byte[] bytes = super.serialize(object);

        try {
            return EncryptionUtils.encrypt(bytes);
        } catch (NoSuchPaddingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (GeneralSecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // handle expections or allow to propagate, your choice!
        return null;
    }

}

EncrpytionUtils 可能如下所示:

public class EncryptionUtils {
    private static SecretKeySpec skeySpec;

    static {    
        try {           
            ClassPathResource res = new ClassPathResource("key.key");
            if(res != null){
                File file = res.getFile();
                FileInputStream input = new FileInputStream(file);
                byte[] in = new byte[(int)file.length()];
                input.read(in);
                skeySpec = new SecretKeySpec(in, "AES");
                input.close();
            }
        }catch (FileNotFoundException e) {
            e.printStackTrace();
        }catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static byte[] encrypt(byte[] input) 
            throws GeneralSecurityException, NoSuchPaddingException{
           Cipher cipher = Cipher.getInstance("AES");

           cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
           return cipher.doFinal(input);

    }


    public static byte[] decrypt(byte[] input) throws GeneralSecurityException, NoSuchPaddingException{
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        return cipher.doFinal(input);
    }

}

您需要做的就是确保将自定义序列化程序设置为RedisOperationSessionRepository 用户的默认值。

请注意:

  1. 上面的代码我没有测试过
  2. 我并不主张上述代码是一个理想的解决方案或THE 解决方案,而只是展示了一种使用Redis 将加密引入SpringSession 的机制。
  3. 显然,您可以使用任何您想要的 2 路加密算法。 EncrpytionUtils 只是一个例子。
  4. 这会影响性能。多少?没有测试很难说。请注意,这会对性能产生一些影响。
  5. 如果您真的担心加密发送到 Redis 的会话数据,那么我强烈建议您同时确保您的服务器是安全的。确保只有需要访问您的 Redis 服务器的服务器才能访问。将其放在防火墙后面。如果您使用的是 AWS 等云服务,请将您的 Redis 服务器放在 VPN 和私有子网内。 Check out this article.
  6. Redis does not support connection encryption currently. 但是,就像他们建议的那样,您可以使用 Sniped 来确保您的连接是加密的。

查看文档和参考:

  1. SpringSession
  2. RedisOperationsSessionRepository
  3. SessionRepository

【讨论】:

  • 优秀的答案!我唯一挣扎的是如何确保在使用 SpringBoot 和 spring-session-data-redis 时始终应用自定义序列化程序,它们在幕后神奇地配置了大多数涉及的组件。我最终得到了一个配置类,它扩展了 RedisHttpSessionConfiguration 并在 PostConstruct 方法中应用了 CrypticRedisSerializer 实例。现在两个方向都有效,感谢您的示例。
【解决方案2】:

来自创建者或 redis 的关于 redis 安全性的非常好的文章 - http://antirez.com/news/96 读起来非常有趣。同时阅读 cmets。

我很好奇的一件事是,您的“关键”数据是否必须存储在会话中?如果它对性能不是超级关键,则可以将其保存在数据库中。我在我们的产品中使用 redis 只是为了存储令牌以及基本的用户数据,我看到人们将大数据大小作为会话数据转储,这很好,但我认为这不是一个好主意。

【讨论】:

  • 我想存储“经典”的东西,比如代币、购物车、非注册用户的东西。
【解决方案3】:

在我看来,你应该避免在 redis 中加密数据,否则会带来性能开销。因此,您可能希望将 redis 节点放在保护区(内部)中,仅允许来自您的应用程序的流量到达。如果不可能,那么可以使用 IPSec/Stunnel 来保护通信。

顺便说一句,将会话数据存储为 HTTPSession 属性会比从 Redis 中检索数据要快。但我相信你会选择redis可能是因为数据量大。

【讨论】:

    猜你喜欢
    • 2011-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-05
    • 2011-11-29
    • 1970-01-01
    • 2016-09-13
    • 1970-01-01
    相关资源
    最近更新 更多