3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring Boot 3.2.3 × Redisでセッション管理する

Last updated at Posted at 2024-02-26

Redis

  • インメモリデータベース
    • メモリ上(RAM)にデータを保存する
    • メリット
      • 低レイテンシ
      • 高スループット
    • デメリット
      • RAMが高価
      • ストレージサイズに限界がある
  • NoSQL

Spring BootでRedisを用いたセッション管理をしてみる

ソースコードは以下。

Redisの準備

RedisはDockerで起動することにする。

compose.yml
version: '3'
services:
  redis:
    image: redis
    ports:
      - 6379:6379

compose.ymlからコンテナ起動し、docker compose exec redis bashでコンテナ内に入り、redis-cliを実行する。

$ docker compose up -d
$ docker compose exec redis bash
root@4229d9d7221f:/data# redis-cli
127.0.0.1:6379> 

set key valueでデータ登録し、keys *で登録されたデータ一覧を参照できる。

127.0.0.1:6379> set aaa bbb
OK
127.0.0.1:6379> keys *
1) "aaa"
127.0.0.1:6379> get aaa
"bbb"

後述するJavaコードでセッションをRedisに登録するが、セッションはシリアライズされるためgetでは参照できない。
代わりに、hgetallで取得する。

127.0.0.1:6379> get spring:session:sessions:e8aa9a26-1c93-4b22-9733-b364586ffa52
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> hgetall spring:session:sessions:e8aa9a26-1c93-4b22-9733-b364586ffa52
1) "creationTime"
2) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x8d\xe3[j3"
3) "maxInactiveInterval"
4) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\a\b"
5) "sessionAttr:hoge"
6) "\xac\xed\x00\x05t\x00\x04fuga"
7) "lastAccessedTime"
8) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x8d\xe3[j4"

Javaコード

特に凝ったことはしていない。

application.yml
spring:
    data:
        redis:
            host: localhost
            port: 6379
            password: null
build.gradle
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-redis'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.session:spring-session-data-redis'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Controller
package io.github.tttol.redis.controller;

import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RestController
@Slf4j
@RequestMapping("redis")
@RequiredArgsConstructor
public class RedisController {
    private final HttpSession session;

    @GetMapping
    public String hello() {
        return "Hello, Redis!";
    }    

    @SuppressWarnings("null")
    @GetMapping("simple/put")
    public String put() {
        var connectionFactory = new LettuceConnectionFactory();
		connectionFactory.afterPropertiesSet();

		var template = new RedisTemplate<String, String>();
		template.setConnectionFactory(connectionFactory);
		template.setDefaultSerializer(StringRedisSerializer.UTF_8);
		template.afterPropertiesSet();

		template.opsForValue().set("foo", "bar");

		log.info("Value at foo:" + template.opsForValue().get("foo"));

		connectionFactory.destroy();
        return "put foo=bar to redis";
    }    

    @GetMapping("session/put/{key}/{value}")
    public String putSession(@PathVariable String key, @PathVariable String value) {
        session.setAttribute(key, value);
        return "put %s=%s to session".formatted(key, value);
    }
}

putSessionメソッドをcurlで呼んでみる。

$ curl -v http://localhost:8080/redis/session/put/hoge/fuga
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
> GET /redis/session/put/hoge/fuga HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.5.0
> Accept: */*
> 
< HTTP/1.1 200 
< Set-Cookie: SESSION=ZThhYTlhMjYtMWM5My00YjIyLTk3MzMtYjM2NDU4NmZmYTUy; Path=/; HttpOnly; SameSite=Lax
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 24
< Date: Mon, 26 Feb 2024 03:00:36 GMT
< 
* Connection #0 to host localhost left intact
put hoge=fuga to session%                       

< Set-Cookie: SESSION=ZThhYTlhMjYtMWM5My00YjIyLTk3MzMtYjM2NDU4NmZmYTUy; より、セッション登録されてることがわかる。

Redisで確認してみる。

127.0.0.1:6379> keys spring*
1) "spring:session:sessions:e8aa9a26-1c93-4b22-9733-b364586ffa52"
127.0.0.1:6379> hgetall spring:session:sessions:e8aa9a26-1c93-4b22-9733-b364586ffa52
1) "creationTime"
2) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x8d\xe3[j3"
3) "maxInactiveInterval"
4) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\a\b"
5) "sessionAttr:hoge"
6) "\xac\xed\x00\x05t\x00\x04fuga"
7) "lastAccessedTime"
8) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x8d\xe3[j4"

セッションIDが一致しなくない?

レスポンスヘッダとRedisでセッションのIDが違うように見える。

  • SESSION=ZThhYTlhMjYtMWM5My00YjIyLTk3MzMtYjM2NDU4NmZmYTUy;
  • 1) "spring:session:sessions:e8aa9a26-1c93-4b22-9733-b364586ffa52"

これはSpirng SessionがRedisへ登録する際にIDをbase64エンコードしてるらしく、値が異なって見えているもよう。
デコードすると同じ値になる。

import base64

encoded_session_id = "ZThhYTlhMjYtMWM5My00YjIyLTk3MzMtYjM2NDU4NmZmYTUy"
decoded_session_id = base64.urlsafe_b64decode(encoded_session_id).decode("utf-8")

print(decoded_session_id)  # Output: e8aa9a26-1c93-4b22-9733-b364586ffa52

Spring Sessionの公式リファレンスからはその記載は見つけることができていないが、ソースコードを見るとたしかにCookieから値を取得する際にbase64エンコードしていた。

org/springframework/session/web/http/DefaultCookieSerializer.java
	private String getValue(CookieValue cookieValue) {
		String requestedCookieValue = cookieValue.getCookieValue();
		String actualCookieValue = requestedCookieValue;
		if (this.jvmRoute != null) {
			actualCookieValue = requestedCookieValue + this.jvmRoute;
		}
		if (this.useBase64Encoding) {
			actualCookieValue = base64Encode(actualCookieValue);
		}
		return actualCookieValue;
	}

参考

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?