質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Spring Security

Spring Securityは、Springのサブプロジェクトの一つでWebアプリケーションに必要な機能を追加します。正規ユーザーであるかを確認するための「認証機能」と、ユーザーのアクセスを制御する「認可機能」を簡単に追加することが可能です。

Hibernate

HibernateとはJava言語のobject-relational mapping (ORM)ライブラリであり、Object/Relational Mappingよりはるか多くの方法でアプリケーションをPOJOで機能付けることができます。

セッション

Sessionはクライアントがサーバに送ったすべてのリクエストのことを指します。

ログイン

ログインは、ユーザーがコンピューターシステムにアクセスするプロセスの事を呼びます。

Spring Boot

Spring Bootは、Javaのフレームワークの一つ。Springプロジェクトが提供する様々なフレームワークを統合した、アプリケーションを高速で開発するために設計されたフレームワークです。

Q&A

解決済

1回答

13899閲覧

spring bootで同一ユーザの多重ログインを禁止したい

waito

総合スコア23

Spring Security

Spring Securityは、Springのサブプロジェクトの一つでWebアプリケーションに必要な機能を追加します。正規ユーザーであるかを確認するための「認証機能」と、ユーザーのアクセスを制御する「認可機能」を簡単に追加することが可能です。

Hibernate

HibernateとはJava言語のobject-relational mapping (ORM)ライブラリであり、Object/Relational Mappingよりはるか多くの方法でアプリケーションをPOJOで機能付けることができます。

セッション

Sessionはクライアントがサーバに送ったすべてのリクエストのことを指します。

ログイン

ログインは、ユーザーがコンピューターシステムにアクセスするプロセスの事を呼びます。

Spring Boot

Spring Bootは、Javaのフレームワークの一つ。Springプロジェクトが提供する様々なフレームワークを統合した、アプリケーションを高速で開発するために設計されたフレームワークです。

0グッド

1クリップ

投稿2020/06/16 02:47

編集2020/06/16 21:42

前提

Javaの学習のため、Spring Bootを使用してWebアプリケーションを作成しています。
同一ユーザの多重ログインを禁止したく、webで色々と調べたのですが、上手く出来ず困っています。
※2つのブラウザから同一ユーザ名とパスワードでログイン出来てしまう状態。

環境

Java 8
Spring Boot 2.3.0
JPA
H2 Database
Thymeleaf
BootStrap 4.5.0

試したこと

まず、WebSecurityConfigurerAdapterを継承したクラスで、
以下のように最大セッション数を1にしました。

Java

1@EnableWebSecurity 2public class SecurityConfig extends WebSecurityConfigurerAdapter { 3 (省略) 4 @Override 5 protected void configure(HttpSecurity http) throws Exception { 6 (省略) 7 http.sessionManagement().maximumSessions(1); 8 (省略) 9}

その上でセッション作成を検知するために、
AbstractSecurityWebApplicationInitializerを継承したクラスで、
enableHttpSessionEventPublisher()がtrueを返すようにオーバーライドしました。

Java

1package com.example; 2 3import org.springframework.core.annotation.Order; 4import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; 5 6@Order(1) 7public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { 8 9 public SecurityWebApplicationInitializer() { 10 super(SecurityConfig.class); 11 } 12 13 protected boolean enableHttpSessionEventPublisher() { 14 return true; 15 } 16 17}

問題

上記のとおり実装しましたが、
2つのブラウザから同一ユーザ名とパスワードでログイン出来てしまう状態です。
そこで、デバッグしたり、カバレッジを確認したりしたところ、
新規に作成したSecurityWebApplicationInitializerクラスが呼ばれていないことが分かりました。
(正確に書くと実装したコンストラクタやメソッドが呼び出されていない状態)

サーブレット初期化時にAbstractSecurityWebApplicationInitializerを継承したクラスがあれば自動でフックされるという記事があったので、
上記の実装で呼び出されると思っていましたが、そうでもないようです。

何か気づくことがあればアドバイスをお願い致します。

追記

問題のプロジェクトとは別に、
多重ログイン禁止処理を確かめるためのプロジェクトを新規に作成して試してみました。

すると、WebSecurityConfigurerAdapterを継承したクラスに、
上記と同様、最大セッション数を1とするコードを書くと2重ログインを禁止出来ました。

そこから少しずつ、問題のプロジェクトに近づけていくと、
ログインに使用しているUSERテーブルのNAMEカラムを外部キーにもつMUTTERテーブルを作成し、
USERテーブルと関連性をもたせたタイミングで2重ログインが禁止できなくなりました。

ここからは予想なのですが、この記事によると、
多重ログインチェックを機能させるためには、UserDetailsを実装したクラスの中で、
equalsとhashCodeメソッドのオーバーライドを適切に行うことが必要らしく、
それが出来ていないのではないかと考えています。

その観点から考えたのですが、結局何が問題か分からず、アドバイスをお願い致します。
なお、DB操作にはJPAを利用しています。

[Userエンティティクラス]

Java

1package sample.spring.domain; 2 3import java.io.Serializable; 4import java.util.List; 5 6import javax.persistence.CascadeType; 7import javax.persistence.Column; 8import javax.persistence.Entity; 9import javax.persistence.FetchType; 10import javax.persistence.GeneratedValue; 11import javax.persistence.GenerationType; 12import javax.persistence.Id; 13import javax.persistence.OneToMany; 14import javax.persistence.Table; 15 16import com.fasterxml.jackson.annotation.JsonIgnore; 17 18import lombok.AllArgsConstructor; 19import lombok.Data; 20import lombok.NoArgsConstructor; 21import lombok.ToString; 22 23@Entity 24@Table(name = "USER") 25@Data 26@NoArgsConstructor 27@AllArgsConstructor 28@ToString(exclude = "mutters") 29public class User implements Serializable { 30 private static final long serialVersionUID = 1L; 31 32 @Id 33 @GeneratedValue(strategy = GenerationType.IDENTITY) 34 @Column(name = "USER_ID") 35 private Integer Id; 36 37 @Column(unique = true, nullable = false, name = "NAME", length = 64) 38 private String name; 39 40 @Column(nullable = false, name = "PASS", length = 80) 41 @JsonIgnore 42 private String pass; 43 44 // 以下をコメントアウトすると2重ログインが禁止出来る 45 @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user") 46 private List<Mutter> mutters; 47 48}

[UserDetailsを実装したUserクラスを継承したクラス]

Java

1package sample.spring.service; 2 3import java.util.Collection; 4 5import org.springframework.security.core.GrantedAuthority; 6 7import sample.spring.domain.User; 8 9import lombok.Data; 10import lombok.EqualsAndHashCode; 11 12@Data 13@EqualsAndHashCode(callSuper = true) 14public class LoginUserDetails extends org.springframework.security.core.userdetails.User { 15 private static final long serialVersionUID = 1L; 16 private final User user; 17 18 public LoginUserDetails(User user, Collection<GrantedAuthority> authorities) { 19 super(user.getName(), user.getPass(), authorities); 20 this.user = user; 21 } 22 23 /* lombokを使わずに実装してみた 24 * 25 * public User getUser() { 26 * return user; 27 * } 28 * 29 * @Override 30 * public int hashCode() { 31 * return user.getName().hashCode(); 32 * } 33 * 34 * @Override 35 * public boolean equals(Object rhs) { 36 * if (rhs instanceof User) { 37 * return user.getName().equals(((LoginUserDetails) rhs).user.getName()); } 38 * return false; 39 * } 40 * 41 * @Override 42 * public String toString(){ 43 * StringBuilder sb = new StringBuilder(); 44 * sb.append("Username: ").append(this.user.getName()).append("; "); 45 * sb.append("Password: [PROTECTED]; "); 46 * 47 * return sb.toString(); 48 * } 49 */ 50 51}

その他のクラスはGitHubにソースを配置したので、
必要であればご確認をお願い致します。
ウェブアプリのユーザ名とパスワードはそれぞれuser1とdemoです。

https://github.com/uekiGityuto/test-session-control

補足

上記で省略していた問題のあるプロジェクトで使用しているSecurityConfigの全量です。

Java

1package com.example; 2 3import org.springframework.beans.factory.annotation.Autowired; 4import org.springframework.context.annotation.Bean; 5import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 6import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7import org.springframework.security.config.annotation.web.builders.WebSecurity; 8import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 9import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 10import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 11import org.springframework.security.crypto.password.PasswordEncoder; 12 13import com.example.service.CustomAuthenticationFailurehandler; 14import com.example.service.LoginUserDetailsService; 15 16//アプリ起動時に読み込まれるコンフィグファイル 17@EnableWebSecurity 18public class SecurityConfig extends WebSecurityConfigurerAdapter { 19 @Autowired 20 LoginUserDetailsService userDetailsService; 21 22 // アクセスフィルター(AuthenticationFilter)のカスタマイズ 23 @Override 24 public void configure(WebSecurity web) throws Exception { 25 web.ignoring().antMatchers("/webjars/**", "/css/**"); 26 } 27 28 // アクセスフィルター(AuthenticationFilter)のカスタマイズ 29 @Override 30 protected void configure(HttpSecurity http) throws Exception { 31 // ログインページを指定 32 // ログインページへのアクセスは全員許可する 33 http.formLogin().loginPage("/index").loginProcessingUrl("/authenticate").usernameParameter("name") 34 .passwordParameter("pass").defaultSuccessUrl("/main", true) 35 // failureHandlerを呼ばない場合は認証エラー時に"/index?error"にリダイレクトする 36 .failureHandler(new CustomAuthenticationFailurehandler()).permitAll(); 37 38 // ユーザ登録ページへのアクセスは全員許可する。 39 // それ以外は認証が必要とする 40 // "/management/"以下はROLE_ADMINのユーザのみ認可する 41 http.authorizeRequests().antMatchers("/index**").permitAll().antMatchers("/user/registration").permitAll() 42 .antMatchers("/user/registerResult").permitAll().antMatchers("/user/gotoTop").permitAll() 43 .antMatchers("/management/**").hasRole("ADMIN")// 'ROLE_'はつけない 44 .anyRequest().authenticated(); 45 46 // ログアウト後に遷移するページを指定 47 http.logout().logoutSuccessUrl("/index"); 48 49 // 二重ログインを禁止 50 http.sessionManagement().maximumSessions(1); 51 } 52 53 // パスワードのエンコード方式を指定 54 @Bean 55 PasswordEncoder passwordEncoder() { 56 return new BCryptPasswordEncoder(); 57 } 58 59 // 認証処理(AuthenticationProvider)のカスタマイズ 60 @Autowired 61 void configureAuthenticationManager(AuthenticationManagerBuilder auth) throws Exception { 62 auth.userDetailsService(userDetailsService)// DaoAuthenticationProviderが使用するUserDetailsServiceを指定 63 .passwordEncoder(passwordEncoder());// DaoAuthenticationProviderが使用するPasswordEncoderを指定 64 } 65 66}

以上、宜しくお願い致します。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

rubytomato

2020/06/16 04:06

アプリケーションの動作について2点確認させてください。 1点目 > 2つのブラウザから同一ユーザ名とパスワードでログイン出来てしまう状態です。 とのことですが、仮に2つのブラウザをブラウザAとブラウザBとした場合 ブラウザAでログイン → ブラウザBでログイン → ブラウザAでの操作はどうなりますか?問題なく認証が必要なページへ遷移することができていますか? 2点目 > サーブレット初期化時にAbstractSecurityWebApplicationInitializerを継承したクラスがあれば自動でフックされるという記事があった その情報の出所を教えてください。
waito

2020/06/16 04:22

rubytomato様 ありがとうございます。回答致します。 1点目 >ブラウザAでログイン → ブラウザBでログイン → ブラウザAでの操作はどうなりますか?問題なく認証>が必要なページへ遷移することができていますか? はい。認証が必要なページに遷移出来ています。 また、ブラウザBも認証が必要なページに遷移できます。 2点目 以下の記事です。ページ内でAbstractSecurityWebApplicationInitializerで検索すると見つかります。 http://myms0623.blog.fc2.com/blog-entry-6.html 上記の記事は古いので公式のリファレンスも確認しました。 サーブレット初期化時に自動でフックされると記載されているわけではありませんが、 呼び出すための特別な処理を実装しているようには見えませんでした。 https://spring.pleiades.io/spring-security/site/docs/5.1.8.BUILD-SNAPSHOT/reference/html/jc.html#hello-web-security-java-configuration 以上、宜しくお願い致します。
rubytomato

2020/06/16 08:22

ご確認ありがとうございます。 > はい。認証が必要なページに遷移出来ています。 > また、ブラウザBも認証が必要なページに遷移できます。 ということですが、この挙動はおかしいように思います。 maximumSessions(1) とした場合の期待する挙動は、 ブラウザAでログイン → ブラウザBでログイン → ブラウザAで操作 → ブラウザAの操作はエラー → ブラウザBで操作 → ブラウザBの操作は成功 つまり上限を超えた場合古いセッションから破棄されていくのがデフォルトの挙動です。 なので、セキュリティコンフィグレーションが正しく反映されていないように思います。 このあたりのことは下記のページが詳しいのでご覧ください。 Spring Security 使い方メモ セッション管理 https://qiita.com/opengl-8080/items/ad9159910501d1989876 > サーブレット初期化時に自動でフックされると記載されているわけではありませんが、 > 呼び出すための特別な処理を実装しているようには見えませんでした。 Spring BootとSpring Securityを組み合わせる場合は、AbstractSecurityWebApplicationInitializerを利用する必要はないように思います。 なので、一旦SecurityWebApplicationInitializerクラスは無効にして、再度動作確認を行ってみてください。 可能であれば動作確認できるだけのミニマムなプロジェクトを共有してください。そうするともう少し踏み込んで調べることができると思います。
waito

2020/06/16 17:59

rubytomato様 アドバイスありがとうございます! > Spring BootとSpring Securityを組み合わせる場合は、AbstractSecurityWebApplicationInitializerを> 利用する必要はないように思います。 > なので、一旦SecurityWebApplicationInitializerクラスは無効にして、再度動作確認を行ってみてください。 →変わらず2重ログイン出来てしまう状態です、、、 > 可能であれば動作確認できるだけのミニマムなプロジェクトを共有してください。そうするともう少し踏み込んで調べることができると思います。 →ありがとうございます!  ミニマムなプロジェクトを作成し試してみました。  試した結果は質問本文に追記しました。  作成したミニマムなプロジェクトは以下です。  https://github.com/uekiGityuto/test-session-control 以上、宜しくお願い致します。
guest

回答1

0

ベストアンサー

最小構成のプロジェクトの提供ありがとうございました。
GitHubのリポジトリをもとに動作確認した結果を回答致します。

公開して頂いたプロジェクトをフォークして正常動作するようになったプロジェクトをrubytomato
/
test-session-control
で公開していますのでご確認ください。
(※1週間ほどで削除致しますのでご了承ください)

原因

直接の原因はLoginUserDetails.javaのlombokのアノテーションにありました。

java

1@Data 2@EqualsAndHashCode(callSuper = true) 3public class LoginUserDetails extends org.springframework.security.core.userdetails.User { 4 5 private static final long serialVersionUID = 1L; 6 private final User user; 7 8 public LoginUserDetails(User user, Collection<GrantedAuthority> authorities) { 9 super(user.getName(), user.getPass(), authorities); 10 this.user = user; 11 } 12 13}

以下のように2つのアノテーションを除去することで期待する動作となります。

java

1public class LoginUserDetails extends org.springframework.security.core.userdetails.User { 2 3 private static final long serialVersionUID = 1L; 4 private final User user; 5 6 public LoginUserDetails(User user, Collection<GrantedAuthority> authorities) { 7 super(user.getName(), user.getPass(), authorities); 8 this.user = user; 9 } 10 11}

こちらのコメントは

多重ログインチェックを機能させるためには、UserDetailsを実装したクラスの中で、
equalsとhashCodeメソッドのオーバーライドを適切に行うことが必要らしく、
それが出来ていないのではないかと考えています。

参考にされた記事の

equalsとhashCodeメソッドのオーバーライドも適切に行う必要があります。このメソッドを適切に実装していないとセッション管理の多重ログインチェックが機能しません。

を読んでのことだと思いますが、その記事では以下のようにUserDetailsインタフェース、CredentialsContainerインタフェースを実装する方法を取っています。このような場合はequalsとhashCodeメソッドのオーバーライドも適切に行う必要があります。

java

1public class SimpleLoginUser implements UserDetails, CredentialsContainer { 2 3 // 省略 4 5}

当該のプロジェクトはorg.springframework.security.core.userdetails.Userを継承しているので不要です。このクラスはSpring Securityが用意しているリファレンスで必要な実装は既に行われています。

java

1public class LoginUserDetails extends org.springframework.security.core.userdetails.User { 2 3 // 省略 4 5}

セキュリティコンフィグレーション

MySpringSecurityConfigsessionManagement()が下記のようになっていると、一番新しいセッションのみ有効となり、それ以前のセッションは無効になります。

java

1.sessionManagement() 2 .maximumSessions(1) 3 //.maxSessionsPreventsLogin(true) 4 .sessionRegistry(sessionRegistry())

下記のように.maxSessionsPreventsLogin(true)のコメントを有効にすると、有効なセッションがある限り、次のログインそれ自体ができなくなります。

java

1.sessionManagement() 2 .maximumSessions(1) 3 .maxSessionsPreventsLogin(true) 4 .sessionRegistry(sessionRegistry())

.sessionRegistry(sessionRegistry())SEC-2855: SessionManagementConfigurer does not allow SessionRegistry to receive eventsの回避策です。
このコードがないとログアウト後もセッションが残り続け、再ログインができなくなります。

java

1// Work around https://jira.spring.io/browse/SEC-2855 2@Bean 3public SessionRegistry sessionRegistry() { 4 SessionRegistry sessionRegistry = new SessionRegistryImpl(); 5 return sessionRegistry; 6} 7 8// Register HttpSessionEventPublisher 9@Bean 10public static ServletListenerRegistrationBean httpSessionEventPublisher() { 11 return new ServletListenerRegistrationBean(new HttpSessionEventPublisher()); 12} 13

ある条件下で動作する件について

ログインに使用しているUSERテーブルのNAMEカラムを外部キーにもつMUTTERテーブルを作成し、

USERテーブルと関連性をもたせたタイミングで2重ログインが禁止できなくなりました。

こちらについてはよくわかりません。lombokのアノテーションが影響しているのかもしれませんが、改修後ではコメントアウトしなくても正常動作します。

java

1public class User implements Serializable { 2 3 // 省略 4 5 // 以下をコメントアウトすると2重ログインが禁止出来る 6 @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user") 7 private List<Mutter> mutters; 8 9}

2次的な問題点

ここからはJPAに関する問題なので読み飛ばしてもかまいません。

上記の修正でログインの多重制御ができたので、以下のようにmuttersにデータを登録して参照できるか確認したところ、データ表示のタイミングでエラーが起きました。

sql

1INSERT INTO USER (NAME, PASS) VALUES ('user1', 'ce5f8d0c5790bf82e9b253d362feb51ba02853301ae24149b260bd30acb00f1b2a0d8b18bbff97a9'); 2INSERT INTO USER (NAME, PASS) VALUES ('user2', 'ce5f8d0c5790bf82e9b253d362feb51ba02853301ae24149b260bd30acb00f1b2a0d8b18bbff97a9'); 3 4INSERT INTO MUTTER (TIMESTAMP, TEXT, USER_NAME) VALUES (CURRENT_TIMESTAMP(), 'user1 aaa', 'user1'); 5INSERT INTO MUTTER (TIMESTAMP, TEXT, USER_NAME) VALUES (CURRENT_TIMESTAMP(), 'user1 bbb', 'user1'); 6INSERT INTO MUTTER (TIMESTAMP, TEXT, USER_NAME) VALUES (CURRENT_TIMESTAMP(), 'user1 ccc', 'user1'); 7 8INSERT INTO MUTTER (TIMESTAMP, TEXT, USER_NAME) VALUES (CURRENT_TIMESTAMP(), 'user2 AAA', 'user2');

この原因はUserクラスとMutterクラスのリレーションシップの張り方にあり、下記のrepositoryでUserを検索しても、それに紐づくMutterは直接参照されるまで検索されません。

java

1public interface UserRepository extends JpaRepository<User, Integer> { 2 @Query("SELECT x FROM User x WHERE x.name = :name") 3 public Optional<User> findByName(@Param("name") String name); 4}

この問題は下記のようにJPQLでJOIN FETCHを行うようにすることで解決できます。

java

1public interface UserRepository extends JpaRepository<User, Integer> { 2 @Query("SELECT x FROM User x LEFT JOIN FETCH x.mutters WHERE x.name = :name") 3 Optional<User> findByName(@Param("name") String name); 4}

動作確認

参考までにログインの挙動をgifに記録しました。
左側がChorome、右側がFirefoxです。

セキュリティコンフィグレーションが以下の場合

java

1.sessionManagement() 2 .maximumSessions(1) 3 .sessionRegistry(sessionRegistry())

イメージ説明

セキュリティコンフィグレーションが以下の場合

java

1.sessionManagement() 2 .maximumSessions(1) 3 .maxSessionsPreventsLogin(true) 4 .sessionRegistry(sessionRegistry())

イメージ説明

追記

Spring Frameworkのプロジェクトの中で特に理解するのが難しいのがSpring Securityです。
私もわからないことだらけなので詳しく説明できない部分もありますがご了承ください。

原因はsuperクラスのequalsとhashCodeメソッドを使用できていなかったことでしょうか?
もしそうであるならば、

lombokの@Dataアノテーションを付与するとアクセサメソッドが追加され、さらにhashCodeequalstoStringメソッドがオーバーライドされます。
このhashCodeequalsメソッドが不適切なコードでオーバーライドされるために期待する動作にならなかったという理解です。

元々、@EqualsAndHashCode(callSuper = true)と書いていたので、
super classのequalsとhashCodeメソッドを利用していると思っていたのですが、
私の理解が違うのでしょうか。

@EqualsAndHashCode(callSuper = true)equalstoStringメソッドがオーバーライドされますが、スーパークラスのメソッドを利用する(呼ぶ)という動きではないようです。

sessionRegistry(sessionRegistry())がないと、
ログアウト後もセッションが残り続け、再ログインができなくなると教えて頂きましたが、
ここが良く分かりませんでした。

挙動を確認するのが一番だと思いますので、ソースコードを以下のように修正して試してみてください。
セキュリティコンフィグレーションのセッション制御の部分と追加したメソッドをコメントアウトします。

java

1.sessionManagement() 2 .maximumSessions(1) 3 .maxSessionsPreventsLogin(true) 4 // .sessionRegistry(sessionRegistry())

java

1/* 2 // Work around https://jira.spring.io/browse/SEC-2855 3 @Bean 4 public SessionRegistry sessionRegistry() { 5 SessionRegistry sessionRegistry = new SessionRegistryImpl(); 6 return sessionRegistry; 7 } 8 9 // Register HttpSessionEventPublisher 10 @Bean 11 public static ServletListenerRegistrationBean httpSessionEventPublisher() { 12 return new ServletListenerRegistrationBean(new HttpSessionEventPublisher()); 13 } 14*/ 15

修正が終わったらアプリケーションを起動し、ログイン → ログアウト → ログインを行ってみてください。
2度目のログインができないと思います。(ブラウザを立ち上げなおしてもログインできません)
これはサーバー側でセッションが有効なままだということだと思います。

この挙動を回避するために上記でコメントアウトした部分が必要になります。

紹介して頂いたページを参照して、sessionRegistryがBeanとして公開されないので、
上記のコーディングをしているのはなんとなく理解できたのですが、
sessionRegistryを公開しないとログアウト時にセッションを破棄出来ないのでしょうか。

springは特別な実装をしなくても、ログアウト時にセッションを破棄する仕様だと思うのですが、
多重ログインを禁止しているとこの仕様が無効になってしまうのでしょうか。

このあたりの挙動は私自身もよくわかりません。
ちなみにログアウト時にセッションを破棄する処理はセキュリティコンフィグレーションの以下の部分で行いますが、
この部分だけあっても上記の2度目のログインはできません。

java

1.logout() 2 .logoutSuccessUrl("/index") 3 .deleteCookies("JSESSIONID") 4 .invalidateHttpSession(true)

rubytomato様のgithubにあるセキュリティコンフィグレーションの最後に、
httpSessionEventPublisher()メソッドを定義していると思いますが、
これは具体的に何をする処理なのでしょうか。

こちらもよくわかりません。
コード自体はSpring Security logout and Maximum sessionsを参考しました。
sessionRegistry()httpSessionEventPublisher()の両方で機能するらしく、どちらか欠けても期待する動作にはなりません。

私が答えられるのは以上になりますが、おそらくこれでは納得できない部分もあると思います。
その場合は改めて質問を立ててください。より詳しい回答者からもっとよい回答が得られると思います。
一番いいのはstackoverflow(英語版の方)で質問することです。もしかするとSpring Securityの開発者の方から回答が貰えるかもしれません。

投稿2020/06/17 03:11

編集2020/06/17 14:16
rubytomato

総合スコア1752

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

waito

2020/06/17 07:25 編集

非常に丁寧で分かりやすい回答ありがとうございます! 元々のプロジェクトでもLoginUserDetails.javaのlombokのアノテーションを削ったら多重ログイン制御できました! 以下、頂いた回答について追加で質問させて下さい。 ・ 原因はsuperクラスのequalsとhashCodeメソッドを使用できていなかったことでしょうか? もしそうであるならば、 元々、@EqualsAndHashCode(callSuper = true)と書いていたので、 super classのequalsとhashCodeメソッドを利用していると思っていたのですが、 私の理解が違うのでしょうか。 ・ セキュリティコンフィグレーションのアドバイスもして頂き、大変ありがとうございます。 セッションは難しいですね。 sessionRegistry(sessionRegistry())がないと、 ログアウト後もセッションが残り続け、再ログインができなくなると教えて頂きましたが、 ここが良く分かりませんでした。 紹介して頂いたページを参照して、sessionRegistryがBeanとして公開されないので、 上記のコーディングをしているのはなんとなく理解できたのですが、 sessionRegistryを公開しないとログアウト時にセッションを破棄出来ないのでしょうか。 springは特別な実装をしなくても、ログアウト時にセッションを破棄する仕様だと思うのですが、 多重ログインを禁止しているとこの仕様が無効になってしまうのでしょうか。 ・ rubytomato様のgithubにあるセキュリティコンフィグレーションの最後に、 httpSessionEventPublisher()メソッドを定義していると思いますが、 これは具体的に何をする処理なのでしょうか。 公式のリファレンス(https://spring.pleiades.io/spring-security/site/docs/5.4.0-M1/api/)に、 期限切れのセッションをクリーンアップするために実装すると書いてあったので、 maximumSessionsの制約で期限切れになったセッションは破棄されず、 確実に破棄するためにこのメソッドを定義しているとも思ったのですが、 HttpSessionEventPublisherはセッションの生成時・破棄をフックして稼働する処理なので、 それも違うと思い。(セッション破棄をトリガーにしたセッション破棄処理は無意味なのでありえない) [公式リファレンスから引用] SessionManagementConfigurer.maximumSessions(int) を使用する場合、期限切れのセッションが確実にクリーンアップされるように、アプリケーションの HttpSessionEventPublisher を構成することを忘れないでください。 ・ JPAについてもありがとうございます。 自分でも試してみます。 長々と質問してしまい申し訳ございません。 以上、宜しくお願い致します。
rubytomato

2020/06/17 14:16

追記しましたのでご確認ください。
waito

2020/06/18 03:56 編集

夜遅くにありがとうございます。 大変勉強になりました。 細かな疑問点は経験を積みながら理解していこうと思います。 一点だけ、気になったのでコメントします。 sessionRegistry(sessionRegistry())の挙動確認のところで、 httpSessionEventPublisher()があれば、sessionRegistry()がなくとも、 「ログイン→ログアウト→ログイン」出来ました。 ですので、sessionRegistry()はいらないのかもしれません。 以上です。色々とありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問