7

I'm using Spring Boot 1.3.3 to build a web application. I use Redis for handling the session.

I'll set some "crucial" data into the HttpSession and I'd like to understand how this will work with Redis. Is the information stored server side plus a key on browser side or all the data is in a cookie in the user browser?

I'd like to see a documentation reference for the answer or to get an authoritative answer (e.g. a Pivotal dev).

mp911de
  • 17,546
  • 2
  • 55
  • 95
mat_boy
  • 12,998
  • 22
  • 72
  • 116
  • Are you using SpringSession (http://docs.spring.io/spring-session/docs/current/reference/html5/#introduction) with Redis? If so, I can answer this question. Let me know. – Peter Kirby Apr 15 '16 at 15:18

3 Answers3

8

While I agree with most of what the other answers in here have said, none of the other answers actually answered the question. I'm going to assume that you are using SpringSession with Redis in Spring Boot.

In order to use SpringSession, you have likely configured (directly or indirectly) a servlet filter that extends SessionRepositoryFilter.

SessionRepositoryFilter uses a SessionRepository. Since you are using Redis, it is likely that your configuration makes use of RedisOperationsSessionRepository.

RedisOperationsSessionRepository implements SessionRepository, as you might have guessed and is ultimately responsible for fetching, storing, and deleting sessions based on a key (in your case, a key that is probably stored as a cookie on the user's browser).

RedisOperationSessionRepository, by default, uses JdkSerializationRedisSerializer, which implements RedisSerializer, to serialize session data prior to handing said data off to Redis.

According to the documentation for RedisOperationSessionRepository, it is possible to set the default serializer that RedisOperationSessionRepository will use, via it's setDefaultSerializer method.

You could theoretically extend JdkSerializationRedisSerializer and perform encryption and decryption there. JdkSerializationRedisSerializer looks like this:

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);
        }
    }
}

So a potential way to add encryption might look like:

@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;
    }
    
}

Where EncrpytionUtils might look like:

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);
    }
    
}

All you would need to do to implement this is make sure that you set your custom serializer as the default that RedisOperationSessionRepository users.

Please note:

  1. I have not tested the above code
  2. I am not advocating that the above code is an ideal solution or THE solution, but simply demonstrating a mechanism for introducing encrpytion into SpringSession with Redis.
  3. Obviously, you can use whatever 2-way encrpytion algorithm you want. EncrpytionUtils is just an example.
  4. This will impact performance. How much? Hard to say without testing. Just be aware that there will be some performance impact.
  5. If you are really worried about encrypting session data sent to Redis, then I highly recommend that you also make sure that your servers are secured. Make sure that only the servers that need to access your Redis server can. Place it behind a firewall. If you are using a cloud service like AWS, place your Redis server in a VPN and inside of a private subnet. Check out this article.
  6. Redis does not support connection encryption currently. However, like they suggest, you could use Sniped to ensure your connections are encrypted.

Documentation and reference to check out:

  1. SpringSession
  2. RedisOperationsSessionRepository
  3. SessionRepository
Peter Kirby
  • 1,915
  • 1
  • 16
  • 29
  • 1
    Excellent answer! The only thing I struggled with was how to ensure that the custom serializer is always applied when using SpringBoot and spring-session-data-redis which configures most of the involved components magically behind the scenes. I ended up with a Configuration Class that extends RedisHttpSessionConfiguration and applies the CrypticRedisSerializer Instance in a PostConstruct method. Now both directions work, thanks for the samples. – Till Kuhn May 27 '19 at 15:28
1

Very good article on redis security from the creator or redis - http://antirez.com/news/96 it's pretty interesting read. Read the comments as well.

One thing I am curious is, does your "crucial" data has to be stored in session? If it's not super critical for performance you can just save it in your DB. I use redis in our product just for storing tokens along with basic user data, I have seen people dumping large data size as a session data, which is fine but i don't think it's a really good idea.

Puran
  • 984
  • 6
  • 15
  • I'd like to store "classic" things, like tokens, shopping carts, things for non registered users. – mat_boy Apr 10 '16 at 18:40
1

In my opinion you should avoid encrypting data in redis, otherwise it will be a performance overhead. So, you may want to put redis nodes in a protected zone(internal) where only the traffic from your application is allowed to reach. If its not possible then IPSec/Stunnel can be used to secure the communication.

By the way, storing the session data as HTTPSession attribute will be faster than retrieving it from Redis. But I believe you would have chosen redis probably because of the volume of the data.

Prashant Mishra
  • 330
  • 4
  • 15