1. 概要

Spring Session には、サーバーに格納されているHTTPセッションの制限からセッション管理を解放するという単純な目標があります。

このソリューションにより、単一のコンテナーに縛られることなく、クラウド内のサービス間でセッションデータを簡単に共有できます(つまり、 Tomcat)。 さらに、同じブラウザで複数のセッションをサポートし、ヘッダーでセッションを送信します。

この記事では、 Spring Session を使用して、Webアプリの認証情報を管理します。 Spring Session はJDBC、Gemfire、またはMongoDBを使用してデータを永続化できますが、Redisを使用します。

Redis の概要については、thisの記事をご覧ください。

2. シンプルなプロジェクト

最初に、後でセッション例のベースとして使用する単純なSpring Bootプロジェクトを作成しましょう。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.2</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

このアプリケーションはSpringBoot で実行され、親pomは各エントリのバージョンを提供します。 各依存関係の最新バージョンは、 spring-boot-starter-security spring-boot-starter-web spring-boot-starter-test[にあります。 X158X]。

application.propertiesにRedisサーバーのいくつかの構成プロパティも追加しましょう。

spring.redis.host=localhost
spring.redis.port=6379

3. スプリングブート構成

Spring Boot、の場合、次の依存関係を追加するだけで十分であり、残りは自動構成で処理されます。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

ここでバージョンを設定するためにブート親pomを使用しているため、これらは他の依存関係で機能することが保証されています。 各依存関係の最新バージョンは、 spring-boot-starter-data-redis spring-sessionにあります。

4. 標準のSpringConfig(ブートなし)

spring-session をSpring Bootなしで、つまりプレーンSpringだけで統合および構成する方法も見てみましょう。

4.1. 依存関係

まず、 spring-session を標準のSpringプロジェクトに追加する場合は、以下を明示的に定義する必要があります。

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session</artifactId>
    <version>1.2.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.5.0.RELEASE</version>
</dependency>

これらのモジュールの最新バージョンは、 spring-session spring-data-redisにあります。

4.2. Springセッション構成

次に、 SpringSessionの構成クラスを追加しましょう。

@Configuration
@EnableRedisHttpSession
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
    @Bean
    public JedisConnectionFactory connectionFactory() {
        return new JedisConnectionFactory();
    }
}

@EnableRedisHttpSessionAbstractHttpSessionApplicationInitializerの拡張機能は、すべてのセキュリティインフラストラクチャの前にフィルターを作成して接続し、アクティブなセッションを探し、に格納されている値からセキュリティコンテキストにデータを入力します。 Redis

ここで、コントローラーとセキュリティ構成を使用してこのアプリケーションを完成させましょう。

5. アプリケーション構成

メインのアプリケーションファイルに移動し、コントローラーを追加します。

@RestController
public class SessionController {
    @RequestMapping("/")
    public String helloAdmin() {
        return "hello admin";
    }
}

これにより、テストするエンドポイントが得られます。

次に、セキュリティ構成クラスを追加します。

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {
        UserDetails user = User.withUsername("admin")
            .password(passwordEncoder.encode("password"))
            .roles("ADMIN")
            .build();
        return new InMemoryUserDetailsManager(user);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.httpBasic()
            .and()
            .authorizeRequests()
            .antMatchers("/")
            .hasRole("ADMIN")
            .anyRequest()
            .authenticated();
        return http.build();
    }

    @Bean 
    public PasswordEncoder passwordEncoder() { 
        return new BCryptPasswordEncoder(); 
    } 
}

これにより、基本認証でエンドポイントが保護され、テストするユーザーが設定されます。

6. テスト

最後に、すべてをテストしましょう。ここでは、2つのことを実行できるようにする簡単なテストを定義します。

  • ライブWebアプリケーションを消費する
  • Redisと話す

まず、設定を行いましょう。

public class SessionControllerTest {

    private Jedis jedis;
    private TestRestTemplate testRestTemplate;
    private TestRestTemplate testRestTemplateWithAuth;
    private String testUrl = "http://localhost:8080/";

    @Before
    public void clearRedisData() {
        testRestTemplate = new TestRestTemplate();
        testRestTemplateWithAuth = new TestRestTemplate("admin", "password", null);

        jedis = new Jedis("localhost", 6379);
        jedis.flushAll();
    }
}

HTTPクライアントとRedisクライアントの両方のクライアントをどのように設定しているかに注目してください。 もちろん、この時点でサーバー(およびRedis)が稼働しているはずです。これにより、これらのテストを介してサーバーと通信できるようになります。

Redisが空であることをテストすることから始めましょう。

@Test
public void testRedisIsEmpty() {
    Set<String> result = jedis.keys("*");
    assertEquals(0, result.size());
}

次に、セキュリティが認証されていない要求に対して401を返すことをテストします。

@Test
public void testUnauthenticatedCantAccess() {
    ResponseEntity<String> result = testRestTemplate.getForEntity(testUrl, String.class);
    assertEquals(HttpStatus.UNAUTHORIZED, result.getStatusCode());
}

次に、 SpringSessionが認証トークンを管理していることをテストします。

@Test
public void testRedisControlsSession() {
    ResponseEntity<String> result = testRestTemplateWithAuth.getForEntity(testUrl, String.class);
    assertEquals("hello admin", result.getBody()); //login worked

    Set<String> redisResult = jedis.keys("*");
    assertTrue(redisResult.size() > 0); //redis is populated with session data

    String sessionCookie = result.getHeaders().get("Set-Cookie").get(0).split(";")[0];
    HttpHeaders headers = new HttpHeaders();
    headers.add("Cookie", sessionCookie);
    HttpEntity<String> httpEntity = new HttpEntity<>(headers);

    result = testRestTemplate.exchange(testUrl, HttpMethod.GET, httpEntity, String.class);
    assertEquals("hello admin", result.getBody()); //access with session works worked

    jedis.flushAll(); //clear all keys in redis

    result = testRestTemplate.exchange(testUrl, HttpMethod.GET, httpEntity, String.class);
    assertEquals(HttpStatus.UNAUTHORIZED, result.getStatusCode());
    //access denied after sessions are removed in redis
}

まず、テストでは、管理者認証資格情報を使用してリクエストが成功したことを確認します。

次に、応答ヘッダーからセッション値を抽出し、それを2番目のリクエストの認証として使用します。 それを検証してから、Redisのすべてのデータをクリアします。

最後に、セッションCookieを使用して別のリクエストを行い、ログアウトしていることを確認します。 これは、 SpringSessionがセッションを管理していることを確認します。

7. 結論

Spring Session は、HTTPセッションを管理するための強力なツールです。 セッションストレージが構成クラスといくつかのMaven依存関係に単純化されたため、複数のアプリケーションを同じ Redis インスタンスに接続し、認証情報を共有できるようになりました。

いつものように、すべての例はGithubから入手できます。