Spring Session Data Redis でセッションリプリケーション方法

Tech > Java > SpringFramework

Spring Session Data Redis を利用したセッションのリプリケーション

AWS上のTomcatでSpringFrameworkを動作させた環境でRedisを利用したセッションの共有化を行う方法の備忘録。

AWS上でTomcatを利用する注意点としてマルチキャストが使えないため、Tomcatがもつネイティブなセッションクラスタリングが利用できないため、ログイン情報などセッション関連の情報がTomcatの再起動でお亡くなりなるケースだとメンテナンスで辛い。

その辺の回避策として、なるべくサービス継続できるようセッションの状態を維持するようにする。

インフラ構成

フロントへリバースプロキシを置き、2台のtomcatでセッションを共有する、認証情報はデータベースへ保存しログインを実行する

変更箇所

まず、前提としてSpring Session Data Redis を利用したセッションのリプリケーションSpringFramework5が必須らしい。

pom.xml

指定されているバージョンについては適当

Redis client driver lettuce

クライアントドライバーにlettuceを利用

<!-- https://mvnrepository.com/artifact/biz.paluch.redis/lettuce -->
<dependency>
    <groupId>biz.paluch.redis</groupId>
    <artifactId>lettuce</artifactId>
    <version>4.5.0.Final</version>
</dependency>

<!-- https://mvnrepository.com/artifact/io.lettuce/lettuce-core -->
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.0.2.RELEASE</version>
</dependency>

spring-session-data-redis

Redis用のセッションモジュール

<!-- https://mvnrepository.com/artifact/org.springframework.session/spring-session-data-redis -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
    <version>2.4.2</version>
</dependency>

web.xml

クッキーまわりの設定はRedis関連の設定から行うので必要最低限の設定

session-configセクション

SESSIONIDはクッキーから取得するよう変更

<session-config>
    <tracking-mode>COOKIE</tracking-mode>
</session-config>

filterセクション

TomcatのHttpSessionをSpringFrameworkのものでfilterにより差し替える設定を行う、 厳密にいうと HttpSession を利用するモジュールより前に書く必要があるらしいので、SpringSecurity 関連のものの前に記載しておく

    <filter>
        <filter-name>springSessionRepositoryFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSessionRepositoryFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>ERROR</dispatcher>        
    </filter-mapping>

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>
        	org.springframework.web.filter.DelegatingFilterProxy
    ...

context-paramセクション

外部のxml設定ファイルを読み込んでいる場合、session-context.xmlの記載を追加する (どこに書いてもいいと思うので、必要であれば実施)

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
    classpath:WEB-INF/spring/security-context.xml
    classpath:WEB-INF/spring/mybatis-context.xml
    classpath:WEB-INF/spring/session-context.xml
    </param-value>
</context-param>

session-context.xml

セッションの設定を行う

  • maxInactiveIntervalInSeconds
    • セッションのタイムアウトを秒で指定
  • RedisStandaloneConfiguration
    • この設定は単一のRedisインスタンス向け接続設定であり、クラスタリングに非対応
  • ConfigureRedisAction.NO_OP
    • Amazon ElastiCacheなどで接続エラーが発生するようなのですが未確認

IPやPORTは適切修正

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd">

    <context:annotation-config/>    
    <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <property name="maxInactiveIntervalInSeconds" value="1800"/>
    </bean>

    <bean id="redisConfig" class="org.springframework.data.redis.connection.RedisStandaloneConfiguration">
        <property name="hostName" value="192.168.0.53" />
        <property name="port" value="6379"/>
        <property name="database" value="1" />
    </bean>
    <bean class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
        <constructor-arg index="0" ref="redisConfig" />
    </bean>

    <!-- <util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/> -->
</beans>

security-context.xml

CSRFタグの取得をクッキーから行うように、TokenRepositoryを明示的に指定

<sec:csrf token-repository-ref="csrfTokenRepository" />

refを以下のBeanで設定

    <bean id="csrfTokenRepository" class="org.springframework.security.web.csrf.CookieCsrfTokenRepository" />

認証

認証については、AuthenticationManagerを利用した普通のフォーム認証のため、面倒くさいので割愛

Controllerの実装方法

SessionAttributeを利用した実装方法

クラス定義

typeを利用した記載方法、valueの利用方法も一般的なSpringFrameworkのやり方と同様

@Controller
@SessionAttributes(types = { SessParam.class })
public class PutSessionController {
...

セッションから取得

コントローラ内のハンドラ関数の引数で@ModelAttributeと名前でDIさせる

    ...
    @RequestMapping(value = "/putsess", method = RequestMethod.GET)
    public String showSess(@ModelAttribute("sessParam") SessParam sessParam, Model model) {
        logger.info("putss: get count = " + sessParam.getCount());
        model.addAttribute("sessParam", sessParam);
        return "putsess";
    }
    ...

セッションへ保存

セッションへ保存するインスタンスのsetterメソッドなどで情報を変更する、セッションストレージへの保存タイミングはレスポンスを返送したときがディフォルト(うる覚え)

    ...
    @RequestMapping(value = "/putsess", method = RequestMethod.POST)
    public String putSess(@ModelAttribute("sessParam") SessParam sessParam) {
        if(sessParam.getCount() == null) {
            sessParam.setCount(1);
        } else {
            sessParam.setCount(sessParam.getCount()+1);
        }
        logger.info("putss: inc count = " + sessParam.getCount());
        return "redirect:/putsess";
	}
    ...

セッションのリセット

sessionStatus.setComplete(); を利用してリセット

    ...
    @RequestMapping(value = "/putsess/reset", method = RequestMethod.POST)
    public String putSessReset(SessionStatus sessionStatus) {
        sessionStatus.setComplete();
        return "redirect:/putsess";
    }
    ...

動作時の注意点

tomcatの再起動とインスタンス復元時の注意点

Tomcatの再起動によってRedisとの接続が切れて再接続した場合、以下の書き方だと新しいオブジェクトのインスタンスが返ってくるので実際の動作で許容できない場合書き換える必要がある。

... Controller内のオブジェクト
...
    @ModelAttribute("sessParam")
    public SessParam setupSessParam() {
        return new SessParam();
    }
...

これを回避したい場合、以下のように修正する

@Controller
@SessionAttributes(types = { SessParam.class })
public class PutSessionController {

   	@Autowired
    protected HttpSession session;

    ...
    @ModelAttribute("sessParam")
    public SessParam setupSessParam(HttpServletRequest request) {
        SessParam obj = (SessParam)session.getAttribute("sessParam");
        if(obj == null) {
            return new SessParam();
        } else {
            return obj;
        }
    }
    ...

HttpSessionインスタンスをAutowiredして、そこからオブジェクトをRedis内に確認し無い場合だけ新しいインスタンスを生成して返す、こうすることによって、tomcatの再起動後も同じインスタンスを再生成できるようになる

sptest3はアプリケーションコンテキストとして以下のような書き方だとクッキーが正常に取得できず、ログイン認証が動かなかった

tomcatのHttpSession版 nginx設定の場合、以下のような設定で動いていたのが、Spring Session Data Redisに切り替えた際、Redisにデータがストアさせるが、ログイン後の認証情報が取得できないという事象に遭遇した

    proxy_cookie_path /sptest3 /;

原因は、レスポンスヘッダのCookie Pathが Path=//; のように出力されていたためで

    proxy_cookie_path ~*^/.* /; # --> proxy_cookie_path /sptest3/ /; 後に確認したところ、この書き方もNGでした

のようにスラッシュを追加し、Path=/; のように出力することで解決した、この設定については、nginxの他のProxy設定が違っていた可能性もあるので正確性にはかけていると思う

Posted on 2021-03-20 06:36:09

はじめまして

お茶の国静岡で、焼酎のお茶割なんか罰当たりで飲んだことはありません、常に一番搾りを嗜む静岡極東のBBQerです、最近まわりのエンジニアの方々がお料理を上手にやっている姿を恨めしそうに横目に見ながら、軟骨ピリ辛チクワを食べています、みなさんよろしく。

Posted

Amazon

tags

家系 日本酒池 広井酒店 やがら やっぱた 刺身 丸干し 鯵 東京マラソン fpm php82 servant thread spawn Rust Oracle Linux 8 microcode firmware linux openzfs zfs gitea 麒麟 真野鶴 金鶴 日本酒 docker oracle pod podman cli virtualbox VirtualBox epub mobi calibre mask lens ワンライナー php 海 redmine Linux Oracle Map OMap omap map 夏 BBQ カテゴリ管理 カテゴリ timestamp date oracle database string 麦焼酎 ダービー process 磨き蒸留 広井酒店、日本酒 芋焼酎 酒 焼酎 ゆるキャン 広井酒店、日本酒池 spring framework java persistent spring session session spring hdbc-odbc persistent-odbc odbc day utctime スィート レマンの森 elm初期化 elm バイク xlr80 esqueleto database xl2tpd strongswan vpn l2tp ipsec 正月 ゲーム grub nginx systemctl portage 豚骨 圧力鍋 yesod-auth-hashdb yesod-auth