春のセッションのガイド
1. 概要
Spring Sessionには、サーバーに格納されているHTTPセッションの制限からセッション管理を解放するという単純な目標があります。
このソリューションにより、単一のコンテナに縛られることなく、クラウド内のサービス間でセッションデータを簡単に共有できます(つまり、 Tomcat) さらに、同じブラウザーで複数のセッションをサポートし、ヘッダーでセッションを送信します。
この記事では、Spring Sessionを使用してWebアプリの認証情報を管理します。 Spring Sessionは、JDBC、Gemfire、またはMongoDBを使用してデータを永続化できますが、Redisを使用します。
Redisの概要については、thisの記事をご覧ください。
2. シンプルなプロジェクト
最初に、後でセッション例のベースとして使用する単純なSpring Bootプロジェクトを作成しましょう。
org.springframework.boot
spring-boot-starter-parent
1.4.0.RELEASE
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
アプリケーションはSpring Bootで実行され、親pomは各エントリのバージョンを提供します。 各依存関係の最新バージョンは、spring-boot-starter-security、spring-boot-starter-web、spring-boot-starter-testにあります。
また、Redisサーバーの構成プロパティをapplication.propertiesに追加しましょう。
spring.redis.host=localhost
spring.redis.port=6379
3. スプリングブート構成
まず、Bootを使用してSpring Sessionを構成する方法を示しましょう。
注:セクション3および4を完了する必要はありません。 Spring Bootを使用してSpring Sessionを構成しているかどうかに応じて、1つを選択してください。
3.1. 依存関係
これらの依存関係をプロジェクトに追加します。
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.session
spring-session
ここではバージョンを設定するためにブート親pomを使用しているので、これらは他の依存関係で動作することが保証されています。 各依存関係の最新バージョンは、spring-boot-starter-data-redis、spring-sessionにあります。
3.2. Springセッション構成
次に、Spring Sessionの構成クラスを追加しましょう。
@Configuration
@EnableRedisHttpSession
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
}
4. 標準のSpringConfig(ブートなし)
また、Spring Bootを使用せずに(プレーンSpringのみを使用して)spring-sessionを統合および構成する方法も見てみましょう。
4.1. 依存関係
まず、標準のSpringプロジェクトにspring-sessionを追加する場合は、以下を明示的に定義する必要があります。
org.springframework.session
spring-session
1.2.2.RELEASE
org.springframework.data
spring-data-redis
1.5.0.RELEASE
これらのモジュールの最新バージョンはここにあります:spring-session、spring-data-redis
4.2. Springセッション構成
次に、Spring Sessionの構成クラスを追加しましょう。
@Configuration
@EnableRedisHttpSession
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
@Bean
public JedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory();
}
}
ご覧のとおり、違いはごくわずかです。JedisConnectionFactory Beanを明示的に定義する必要があります。Bootがそれを行います。
どちらのタイプでも、@EnableRedisHttpSessionとAbstractHttpSessionApplicationInitializerの拡張により、すべてのセキュリティインフラストラクチャの前にフィルタが作成および配線され、アクティブなセッションが検索され、Redisに格納されている値からセキュリティコンテキストが設定されます。
それでは、コントローラーとセキュリティ構成を使用してこのアプリケーションを完成させましょう。
5. アプリケーション構成
メインアプリケーションファイルに移動し、コントローラーを追加します。
@RestController
public class SessionController {
@RequestMapping("/")
public String helloAdmin() {
return "hello admin";
}
}
これにより、テストするエンドポイントが得られます。
次に、セキュリティ構成クラスを追加します。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password("password").roles("ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().and()
.authorizeRequests()
.antMatchers("/").hasRole("ADMIN")
.anyRequest().authenticated();
}
}
これにより、基本認証でエンドポイントが保護され、テストするユーザーが設定されます。
6. Test
最後に、すべてをテストしましょう。ここでは、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 result = jedis.keys("*");
assertEquals(0, result.size());
}
ここで、認証されていない要求に対してセキュリティが401を返すことをテストします。
@Test
public void testUnauthenticatedCantAccess() {
ResponseEntity result = testRestTemplate.getForEntity(testUrl, String.class);
assertEquals(HttpStatus.UNAUTHORIZED, result.getStatusCode());
}
次に、Spring Sessionが認証トークンを管理していることをテストします。
@Test
public void testRedisControlsSession() {
ResponseEntity result = testRestTemplateWithAuth.getForEntity(testUrl, String.class);
assertEquals("hello admin", result.getBody()); //login worked
Set 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 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を使用して別の要求を行い、ログアウトされたことを確認します。 これは、Spring Sessionがセッションを管理していることを確認します。
7. 結論
Spring Sessionは、HTTPセッションを管理するための強力なツールです。 セッションストレージが構成クラスといくつかのMaven依存関係に単純化されたため、複数のアプリケーションを同じRedisインスタンスに接続し、認証情報を共有できるようになりました。
いつものように、ソースコードover on Githubを見つけることができます。