Redis、SpringBoot 和 HttpSession:我应该加密会话数据吗?

mat*_*boy 5 session session-variables redis spring-boot spring-session

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

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

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

Pet*_*rby 9

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

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

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

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

RedisOperationSessionRepository默认情况下,使用JdkSerializationRedisSerializer实现 的RedisSerializer来序列化会话数据,然后再将所述数据交给 Redis。

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

理论上,您可以在那里扩展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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

@Component
@Qualifier("springSessionDefaultRedisSerializer") //SB 2.0.0+
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;
    }
    
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
    
}
Run Code Online (Sandbox Code Playgroud)

要实现此功能,您所需要做的就是确保将自定义序列化器设置为RedisOperationSessionRepository用户的默认序列化器。

请注意:

  1. 上面的代码我没有测试过
  2. 我并不是主张上面的代码是理想的解决方案或THE解决方案,只是简单地演示一种使用Redis将encrpytion引入SpringSession的机制。
  3. 显然,您可以使用任何您想要的双向加密算法。EncrpytionUtils 只是一个示例。
  4. 这会影响性能。多少?未经测试很难说。请注意,这会对性能产生一些影响。
  5. 如果您确实担心加密发送到 Redis 的会话数据,那么我强烈建议您也确保您的服务器是安全的。确保只有需要访问您的 Redis 服务器的服务器才能访问。将其放置在防火墙后面。如果您使用的是 AWS 等云服务,请将 Redis 服务器放置在 VPN 和私有子网内。看看这篇文章。
  6. Redis 目前不支持连接加密。然而,就像他们建议的那样,您可以使用Sniped来确保您的连接是加密的。

要查看的文档和参考:

  1. 春季会议
  2. RedisOperationsSessionRepository
  3. SessionRepository


Pur*_*ran 1

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

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