ElastiCache for ValkeyによるHTTPセッション管理

office3 AWS
office3

Amazon ElastiCache for ValkeyをHTTPセッション管理に利用する方法と設計時の考慮点についての記事です。アプリケーションサーバーにはSpring Bootを利用し、SpringSession機能を利用します。

Elasti Cache for Valkeyの概要

Valkeyとは

Redisをフォークして作成されたインメモリデータベースです。Valkeyのフォーク元となったRedisはオープンソースとして開発されてきましたが、2024年3月にライセンスが変更され、クラウドベンダなどによる商用サービスが制限されることになりました。
このライセンス変更を受けAWS、Google Cloud、Oracleなどの主要ベンダーがLinux Foundation傘下でRedisをフォークし、オープンソースとして「Valkey」の開発、運用を行っています。

今後Redis OSSの利用は難しくなることからAWSも積極的にサポートを行っており、料金はValkeyのほうが安く、パフォーマンスも優れていることからクラウド上ではValkeyの利用が進んでいくと思われます。ValkeyはRedisと互換性があるためRedisで利用していたクライアントなどもそのまま利用でき、SpringSessionでも対応されています。

オンデマンドとサーバレスの違い

Valkeyはオンデマンドとサーバレスの2タイプで提供されています。大きな違いは課金体系とスケーリングが自動かどうかです。
サーバーレス:
インフラ設計不要で自動スケールします。使用したデータ量、リクエスト量で課金。
オンデマンド:
ノードやシャード構成の設計が必要。スケールは手動。ノードの利用時間によって課金。リザーブドによる割引も受けれる。

サーバレスではシャードなどの設計は不要な反面、細かな制御は出来ません。料金については最小構成での利用や、小規模かつワークロードが変動しやすい場面では料金を抑えられる可能性が高く、運用も軽減出来るメリットが出やすいですが、長時間高いキャッシュ容量・リクエスト量を継続して使う用途では料金予測が難しい点に注意が必要です。

料金体系

オンデマンドの場合、コスト影響があるのは以下の要素です。ノードやシャードといった言葉がわかりにくいですが、料金体系はEC2に近く「キャッシュノードタイプ」(EC2でいうところのインスタンスタイプ)とノード数が大きなポイントです。

種類コストへの影響度概要
キャッシュノードタイプvCPU数、メモリサイズ、ネットワーク性能を決定。
シャード数(クラスターモード有効時)書き込み可能なプライマリノード数。
例:3シャード → 3プライマリ
レプリカ数プライマリノードのデータをリアルタイムで複製。
読み取り専用アクセスが可能。フェイルオーバー時には自動昇格してプライマリへ昇格。
メモリキャッシュノードタイプにより決定。
データ転送リージョン内でのデータ転送費用は無料
※同じリージョンの異なる AZ にある EC2 インスタンスと ElastiCache ノードの間でデータを転送する場合、標準的な Amazon EC2 のリージョナルデータ転送料金が適用されるが、EC2インスタンスへのデータ転送または EC2 インスタンスからのデータ転送に対してのみ課金
バックアップ安価なので特に大きな影響なし。

ノード数はシャード数とレプリカ数により決定されます。

ノード数 = シャード数 ×(1 + レプリカ数)

冗長化

ElastiCacheの単一ノードでのSLAは99.99%です。
Amazon ElastiCacheサービスレベルアグリーメント

冗長化を行いたい場合はリードレプリカを追加し、マルチAZを有効化します。リードレプリカを追加することで障害発生時にリードレプリカが自動的にプライマリノードに昇格します。

保存可能なデータ型

Valkeyに保存可能なデータ型で主要なものは以下の通りです。

データ型概要
String(文字列)最も基本的なデータ型。文字列は、テキスト、シリアル化されたオブジェクト、バイナリ配列などのバイトシーケンスを格納します。
List(リスト)順序付きの文字列要素のリスト。
Set(集合)重複を許さない要素の集合。集合演算(和・積・差)をサポート。
Sorted Set(ソート済み集合 / ZSet)各要素にスコアを持たせ、スコア順に並べられる集合。
Hash(ハッシュ)フィールド名と値(文字列)のペアを格納。
JSON(ElastiCache拡張)JSON ドキュメントをネイティブに格納・部分更新・検索可能(JSON.GET / JSON.SET など)。
Getting started with JSON for Valkey and Redis OSS – Amazon ElastiCache

上記以外にも保存可能なデータ型があります。詳細は次の公式ドキュメントを参照ください。

SpringSessionでの利用方法

SpringSessionからValkeyを利用するための必要な作業なステップは以下になります。

  • 依存ライブラリの追加(pom.xml)
  • SpringBootアプリケーション設定(application.yml)
  • 接続プールと読み取り分散の設定(Java設定クラス)
  • SpringBootアプリケーションへのIAM権限付与
  • HttpSessionを保存する構成を有効化(Java設定クラス)

依存ライブラリの追加(pom.xml)

以下のライブラリをpom.xmlの依存関係に追加します。
・spring-boot-starter-data-redis:SpringSession拡張です。HttpSession経由で透過的にVolkeyにデータを保存する機能を提供します。データのライフサイクルはHTTPセッションと同じになります。
・spring-session-data-redis:RedisTemplateなどを利用し、HttpSessionとは関係なくVolkeyにデータを保存する機能を提供します。Httpセッションに関係ない情報を保存したい場合などに利用します。
・spring-cloud-aws-starter-parameter-store:Spring公式の Spring Cloud AWSです。application.ymlからAWSのパラメータストアを参照する機能を提供します。
・jackson-databind:Jackson の中核ライブラリで、Javaオブジェクトと JSON の相互変換(シリアライズ/デシリアライズ)機能を提供します。

<dependencies>
    <!-- Spring Data Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- Spring Session -->
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>

    <dependency>
        <groupId>io.awspring.cloud</groupId>
        <artifactId>spring-cloud-aws-starter-parameter-store</artifactId>
    </dependency>

    <dependency>
       <groupId>com.fasterxml.jackson.core</groupId>
       <artifactId>jackson-databind</artifactId>
    </dependency>


</dependencies>

SpringBootアプリケーション設定

application.ymlにDB接続情報、クラスタエンドポイントなどを追加します。

spring:
  session:
    # Redis(Valkey)をセッションストアとして利用
    store-type: redis

    redis:
      # Redis上でセッションを格納するキーのプレフィックス
      # 複数アプリや環境で同一Redisを使う場合のキー衝突防止に有効
      namespace: myapp:session

      # Redisへの書き込みタイミング
      # - on-save   : セッション保存時に書き込み、リクエスト単位(性能重視)
      # - immediate : 属性変更ごとに即時書き込み(一貫性重視)
      flush-mode: on-save

      # Spring Sessionのセッション有効期限(TTL)設定
      # timeoutを明示的に指定しない場合、server.servlet.session.timeoutの値を継承します。
      # timeout: 30m

  data:
    redis:
      # Valkey (ElastiCache) クラスタ構成の接続設定
      cluster:
        nodes:
          - mycluster-001.abc123.clustercfg.apne1.cache.amazonaws.com:6379

      # Redis 認証情報はAWS Systems Manager Parameter Storeから取得
      # Spring Cloud AWSを利用して ${ssm:/path/to/password} の形式で参照可能
      password: ${ssm:/myapp/valkey/password}

接続プールと読み取り分散の設定

リードレプリカを利用しており、読み取り分散方法を変更したい場合はapplication.ymlでは設定出来ないため設定クラスを作成します。読み取り分散方法を変更する必要がない場合はクラス作成は不要なので、接続プール設定だけをapplication.ymlに設定すればOKです。

読み取り分散方法は Lettuce(Redisクライアント)の列挙型(io.lettuce.core.ReadFrom)で指定します。指定可能なモードは次の通りです。今回はキャッシュ目的ではなくセッション管理が目的なので「ReadFrom.MASTER」を指定します。(Valkeyのレプリカは非同期レプリケーションなのでセッション管理では不向き)

モード名説明
ReadFrom.MASTER常にマスター(書き込みノード)から読み取る。整合性重視。デフォルト設定。
ReadFrom.REPLICA常にレプリカ(読み取り専用ノード)から読み取る。性能重視。一貫性は保証されない。
ReadFrom.REPLICA_PREFERREDレプリカがあれば優先して読み取り。なければマスターから読み取る。
ReadFrom.NEARESTRTT(応答時間)が最も短いノードから読み取る。地理的・ネットワーク的に最適化。
ReadFrom.ANY利用可能な任意のノードから読み取る。整合性や性能の保証はない。非推奨。
package com.example.config;

import java.time.Duration;
import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

import io.lettuce.core.ReadFrom;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

@Configuration
public class RedisConfig {

    @Value("${spring.data.redis.cluster.nodes}")
    private List<String> clusterNodes;

    @Value("${spring.data.redis.password}")
    private String password;

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        // クラスタ構成の定義
        RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration(clusterNodes);
        clusterConfig.setPassword(RedisPassword.of(password));

        // 接続プール設定
        GenericObjectPoolConfig<?> poolConfig = new GenericObjectPoolConfig<>();
        poolConfig.setMaxTotal(2000);     // max-active
        poolConfig.setMaxIdle(500);       // max-idle
        poolConfig.setMinIdle(50);        // min-idle
        poolConfig.setMaxWait(Duration.ofMillis(5000)); // max-wait

        // クライアント設定(読み取り分散+タイムアウト)
        LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
            .poolConfig(poolConfig)
            .readFrom(ReadFrom.MASTER) // セッション用途なので整合性重視
            .commandTimeout(Duration.ofMillis(5000))
            .build();

        return new LettuceConnectionFactory(clusterConfig, clientConfig);
    }
}

SpringBootアプリケーションへのIAM権限付与

SpringBootアプリケーションにAWS Parameter Storeへのアクセス権限を付与します。

1. Parameter Storeの読み取りを許可するIAMポリシーを作成

{
  "Effect": "Allow",
  "Action": [
    "ssm:GetParameter"
  ],
  "Resource": "arn:aws:ssm:ap-northeast-1:xxx:parameter/myapp/redis/password"
}

2. IAM ロール作成(信頼ポリシーに OIDC を指定)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/XXXXXXXX"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.ap-northeast-1.amazonaws.com/id/XXXXXXXX:sub": "system:serviceaccount:default:my-app-sa"
        }
      }
    }
  ]
}

3. Kubernetes の ServiceAccount に IAM ロールを関連付け

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app-sa
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT_ID>:role/my-app-parameter-access-role

HttpSessionを保存する構成を有効化

@EnableRedisHttpSessionアノテーションを使用しValkeyにHttpSessionを保存する構成を有効化します。application.ymlでセッションタイムアウトを指定してますが、@EnableRedisHttpSessionを利用すると、yml側の設定が無視される問題が過去にあったようなので、ymlから設定を読み込み、本クラスでも設定するようにしています。

@Configuration
@EnableRedisHttpSession
public class SessionConfig {

    @Value("${server.servlet.session.timeout}")
    private Duration sessionTimeout;

    @Bean
    public RedisHttpSessionConfiguration redisHttpSessionConfiguration() {
        RedisHttpSessionConfiguration config = new RedisHttpSessionConfiguration();
        config.setMaxInactiveIntervalInSeconds((int) sessionTimeout.getSeconds());
        return config;
    }
}

Valkeyへのセッションデータ保存方法

いままで記載してきた設定を行うと次のようにHttpSession経由でValkeyにデータが保存出来るようになります。

@Service
public class AddressService {

    private static final String SESSION_KEY_ADDRESS = "userAddress";
    private static final String SESSION_KEY_ADDRESS_OBJECT = "userAddressObject";

    /**
     * 文字列型の住所をセッションに保存する。
     * 通常のセッションデータ保存と同じ実装でValkeyに保存される。
     * デフォルトでは内部的にStringオブジェクトが
     * Javaシリアライズされたバイナリとして格納される。
     */
    public void saveAddressToSession(HttpSession session, String address) {
        session.setAttribute(SESSION_KEY_ADDRESS, address);
    }

    /**
     * Javaオブジェクト型の住所情報をセッションに保存する。
     * デフォルトではJavaシリアライズされたバイナリとして格納される。
     */
    public void saveAddressObjectToSession(HttpSession session, AddressInfo addressInfo) {
        session.setAttribute(SESSION_KEY_ADDRESS_OBJECT, addressInfo);
    }
}

Valkeyに保存されるセッションデータの形式と互換性に関する注意点

Springで提供される主なシリアライザ

Spring Sessionを利用して、Valkey(Redis)をセッションストアとして構成する場合、HttpSession#setAttribute() によって保存されるデータは、Springの RedisSerializer によってシリアライズされ、Valkeyに格納されます。保存される形式は設定されたシリアライザに依存します。

Springで提供されている主なシリアライザは以下の通りです。デフォルトではJava標準シリアライズが利用され、バイナリ形式で保存されます。

保存形式RedisSerializer名提供元特徴
Java標準シリアライズ(バイナリ)JdkSerializationRedisSerializerSpring Data Redis内部的にはJavaの Serializableを利用。バイナリ形式でオブジェクトがそのまま保存される。
JSON(型情報付き)GenericJackson2JsonRedisSerializerSpring Data RedisJackson利用しJSON文字列として保存される。
(型情報含む)
JSON(型指定)Jackson2JsonRedisSerializer<T>Spring Data RedisJackson利用しJSON文字列として保存される。
(型情報は含まない)

Javaシリアライズ形式とJSON形式の比較

ValkeyへJavaオブジェクトをそのまま保存したい場合、保存方式は前述の通り以下2つの方法があります。
・「Java標準シリアライザ」
・「JSON形式シリアライザ(GenericJackson2JsonRedisSerializer など)」

どちらを方式を選択するか?はシステム要件によりますが、それぞれの特徴は以下のとおりです。パフォーマンスやデータサイズの差異はH/Wスペックで調整可能なのでどちらの形式も選択可能ですが、アプリケーションリリース時にセッションデータのクリアが許容されない場合は、Java標準のシリアライザでは対応出来ないためJSON形式を選択することになると思います。
※Java標準のシリアライザでもserialVersionUIDを変更せず、バージョンごとのコンバージョン実装などを行えば対応できなくも無いですが望ましい設計では無いと思います。

項目Java標準のシリアライザJSON形式シリアライザ
後方互換性/変更耐性
(ソース修正時)
低い。
serialVersionUIDが異なる場合は読み込み不可。
高い。
型やフィールドが増減しても柔軟に対応可能
可読性低い
(バイナリのため 中身が分かりにくい)
高い
(文字列として保存されるため中身の確認が容易)
他言語との互換性Java専用。他言語から直接読み取れない高い。他言語でも読み書き可能
データサイズJSON形式と比較し小さくなる傾向。バイナリ形式で効率的に格納可能。ただし、JSONでは保存不要なオブジェクトメタデータ(クラス情報や参照情報)を含むため、データによってはJSONより大きくなることもある文字列ベースのため、Java標準シリアライズと比較すると大きくなりやすい傾向。
パフォーマンスJSON形式と比較すると早い。パースする分シリアライズよりは遅い。
セキュリティ悪意のあるコードが埋め込まれる可能性あり。文字列として扱うため、悪意あるコードの埋め込みによるリスクは低い

JSON形式シリアライザの比較

Redisとの連携を簡素化するための Spring Data ファミリーの一部である「Spring Data Redis」ではJSON形式シリアライザとして「GenericJackson2JsonRedisSerializer」と「Jackson2JsonRedisSerializer」の2つのシリアライザを提供しています。機能の違いは以下のとおりです。

項目GenericJackson2JsonRedisSerializerJackson2JsonRedisSerializer
概要「汎用」JSONシリアライザ。クラス情報(型情報)を含めてJSON化。「特定型」JSONシリアライザ。クラス情報(型情報)を含まずデータのみをJSON化
指定方法new GenericJackson2JsonRedisSerializer()new Jackson2JsonRedisSerializer<>(MyClass.class)
型情報の扱いJSON内に型情報 (@class メタ情報) を埋め込み、復元時に自動で型を判断。型情報を埋め込まない。復元時はコンストラクタで指定したクラス型に変換。
多様なオブジェクトの保存 異なるクラスのオブジェクト混在OK固定クラス以外は復元不可(特定のデータ専用)
主な用途Spring Session、汎用キャッシュなど、値の型が可変、複数混在になるケースキャッシュ・メッセージなど、構造がシンプル、固定となるケース
JSON出力例json { "@class":"java.util.HashMap", "key":"value" }json { "key":"value" }
復元精度高い
(正確な型復元が可能)
限定的
(指定型にキャスト)
パフォーマンスやや低い
(型情報付与のオーバーヘッド)
高速
(シンプルなJSON変換)

Jackson2JsonRedisSerializer」はJSON化した際にデータをJSON化するだけになるため、シリアライズ後のデータはシンプルでパフォーマンスも高速ですが、型情報が失われるため、復元可能なオブジェクトは純粋なPOJOに限られるので、汎用的にHttpセッションデータを扱う場合は「GenericJackson2JsonRedisSerializer」を利用する形になると思います。

後方互換性、デシリアライズに関する注意点

GenericJackson2JsonRedisSerializer」を利用し、型情報と共にJSON化した場合にも注意する点があります。

フィールドの追加、変更、削除

以下のようにフィールドが追加や削除された場合、未定義のフィールドの扱いとなり、デフォルトでは無視(スキップ)されます。この際、フィールド初期化子の値が設定されるか、デフォルト値(0)が設定されるかは、プリミティブ型かどうかによって変わります。
プリミティブ型の場合はフィールド初期化子に関係なくデフォルト値が設定され、ラッパー型の場合は初期化子が適用されます。

// 保存時
public class User {
    private String name;
}

// 新バージョン
public class User {
    private String name;
    private int age = 3;       // ← 追加
    private Integer year = 10; // ← 追加
}

デフォルト動作は無視になりますが、明示的に無視設定するにはRedis用ObjectMapperに対して以下のように「FAIL_ON_UNKNOWN_PROPERTIES」の設定を行います。application.ymlなどでも動作設定は可能ですが、application.ymlで指定した場合は、Redis以外のObjectMapperにも適用されてしまう点に注意してください。

@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
    ObjectMapper redisMapper = new ObjectMapper();
    redisMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // Redis専用設定
    return new GenericJackson2JsonRedisSerializer(redisMapper);
}

セッションデータの場合、多くのケースではデフォルト無視の動作で事足りるケースが多いはずですが、以下のようにデシリアライズ動作を定義しカスタマイズすることも可能です。または「@JsonProperty(defaultValue = “10”)」のように指定することも可能です。

public class User {
    private String name;

    @JsonDeserialize(using = AgeDeserializer.class)
    private int age; // ← 任意のロジックで値を設定
}
public class AgeDeserializer extends JsonDeserializer<Integer> {
    @Override
    public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        if (p.getCurrentToken() == JsonToken.VALUE_NULL || p.getText().isEmpty()) {
            return 18; // ← 任意のデフォルト値
        }
        return p.getIntValue();
    }
}

パッケージの移動

パッケージ移動した場合、JSONに埋め込まれたクラスは存在しなくなるため旧パッケージと新パッケージのマッピング情報を定義する必要があります。以下サンプルです。

package com.example.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

/**
 * Redis専用のJackson設定クラス。
 * Valkey(Redis互換)に保存されたセッションJSONの互換性を保つため、
 * - 型情報付きJSON(@class)を安全に復元
 * - 旧パッケージから新パッケージへの型マッピングを定義
 * - 不明なフィールド(追加・削除)を無視して復元可能にする
 * 
 * この設定は Redis 用途(Spring Session RedisやRedisTemplate)に限定され、
 * Web API や Logging など他の用途には影響を与えません。
 */
@Configuration
public class RedisJacksonConfig {

    /**
     * Spring Session Redis で使用されるデフォルトの RedisSerializer Bean。
     * GenericJackson2JsonRedisSerializer にカスタム ObjectMapper を渡すことで、
     * セッション互換性と安全な型復元を実現する。
     *
     * @return RedisSerializer<Object> セッション用のJSONシリアライザ
     */
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        // Redis専用のObjectMapperを構築
        ObjectMapper redisMapper = new ObjectMapper();

        // 型情報付きJSON(@class)を安全に扱うための型制限
        BasicPolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
            .allowIfSubType("com.example.new") // 新パッケージのみ許可
            .build();

        // 型情報を埋め込む設定(@classフィールドを含む)
        redisMapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL);

        // 旧→新パッケージの型マッピングを定義
        SimpleModule typeMappingModule = new SimpleModule();
        typeMappingModule.addAbstractTypeMapping(
            com.example.old.User.class, // 旧パッケージの型
            com.example.new.User.class  // 新パッケージの型
        );
        redisMapper.registerModule(typeMappingModule);

        // 不明なフィールド(追加・削除)を無視して復元可能にする
        redisMapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        // Redisセッション専用のシリアライザを返す
        return new GenericJackson2JsonRedisSerializer(redisMapper);
    }
}

特殊な日付、時刻型(LocalDate, LocalDateTime, ZonedDateTimeなど)

java.util.Date, java.sql.TimestampなどはJacksonでデフォルト対応していますが、LocalDate, LocalDateTimeなどは対応していないため、追加設定が必要です。

これらはjacksonのJavaTimeModuleをObjectMapperに登録することで対応可能です。

ObjectMapper redisMapper = new ObjectMapper();

// 日付型対応
redisMapper.registerModule(new JavaTimeModule());

// ISO-8601形式で保存(タイムスタンプ無効化)
// オプション。UNIXタイムスタンプ(数値)ではなく、
// ISO-8601形式の文字列でシリアライズさせるための設定。
redisMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

セッションデータをJSONで保存する際の設計指針

  • 保存可能なクラスを限定する。例:String,DTOに限定する
  • 特にEntityは保存不可とする。
    – EntityはDBとのマッピングを担う「永続モデル」でありアプリ層と永続層の境界が崩れてしまう。
    – EntityにはIDや氏名など個人情報が含まれる可能性が高くセキュリティ的にも好ましくない
    – EntityはDB構造と密接に結びついているため柔軟なバージョン管理が難しい
    – Entityは多くのフィールド・関連オブジェクトを持つため肥大化しやすい
  • セキュリティ観点から復元可能なクラスは最低限のパッケージに制限する。※ObjectMapper#activateDefaultTyping() でJSON に型情報(@class)を埋め込み、復元時にその型情報を使ってインスタンス化できるようにするための設定を行う。対象パッケージは引数の「PolymorphicTypeValidator」で指定。

Redis用ObjectMapper設定例

今まで話してきた内容を反映したRedis用ObjectMapperの設定例です。

package com.example.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

/**
 * Redisセッション専用のObjectMapper構成。
 * 他の用途には影響を与えず、Valkeyセッションの安全な保存・復元を実現。
 * - 型情報付きJSON(@class)を有効化
 * - 抽象型の復元を許可する型制限を設定
 * - Java 8 日付型(LocalDateTimeなど)に対応
 * - ISO-8601形式で保存(タイムスタンプ無効化)
 * - 不明なフィールドを無視して互換性を維持
 */
@Configuration
public class RedisJacksonConfig {

    /**
     * Spring Session Redis で使用される RedisSerializer Bean。
     * Redis専用のObjectMapperを構成する。
     *
     * @return RedisSerializer<Object> Redisセッション専用のシリアライザ
     */
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        ObjectMapper redisMapper = new ObjectMapper();

        // 型制限:安全なパッケージのみ許可(DTOパッケージを明示)
        BasicPolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
            .allowIfSubType("com.example.dto") // ← DTOや抽象型のパッケージ
            .build();

        // 型情報付きJSON(@class)を有効化(抽象型復元に必要)
        redisMapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL);

        // Java 8 日付型対応(LocalDateTime, ZonedDateTime など)
        redisMapper.registerModule(new JavaTimeModule());

        // ISO-8601形式で保存(タイムスタンプ無効化)
        redisMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        // 不明なフィールドを無視(後方互換性のため)
        redisMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        // Redis専用のシリアライザを返却
        return new GenericJackson2JsonRedisSerializer(redisMapper);
    }
}

参考記事

以上

タイトルとURLをコピーしました