18.1K Views
April 17, 25
スライド概要
数あるSpringプロダクトの中でも、特に難しいと言われるSpring Security。
トラブルシュートしたりカスタマイズしたりするには、アーキテクチャの理解が不可欠です。
このセッションでは、Spring Securityのアーキテクチャを図解で分かりやすく解説します。
併せて、認証のカスタマイズ方法や、コードリーディングのコツを解説します。
Java、Spring、IntelliJ IDEA
ハイレベルSpring Security アーキテクチャ、カスタマイズ、 コードリーディング JJUGナイトセミナー 2025年4月17日 株式会社クレディセゾン 多田真敏 1
このセッションについて ◆数あるSpringプロダクトの中でも、特に難しいと言われる Spring Security。 ◆トラブルシュートしたりカスタマイズしたりするには、 アーキテクチャの理解が不可欠です。 ◆このセッションでは、Spring Securityのアーキテクチャを 図解で分かりやすく解説します。 ◆併せて、認証のカスタマイズ方法や、コードリーディングの コツを解説します。 2
必要な前提知識 ◆このセッションを理解するには、以下の前提知識が必要です ◆サーブレットとサーブレットフィルター ◆Spring DI・Spring MVC・Spring Securityの利用経験 ◆認証と認可の違い 3
自己紹介 ◆多田真敏(@suke_masa) ◆JJUG・JSUGスタッフ ◆カード会社で 社内システムの内製化+AWS化 ◆Java 21 / Spring Boot / IntelliJ / Mac / Terraform / GitHub / Slack ... ◆OSSドキュメントの和訳 ◆Thymeleaf・Resilience4j ◆6/7 CCCでスポンサーセッションします 4
対象バージョン ◆Spring Boot 3.4 ◆Spring Security 6.4 バージョンが異なる場合、 この資料の説明が正しくない 可能性があります ◆Spring MVC 6.2 5
目次 ① Spring Securityとは ② Spring Securityのアーキテクチャ① Filter Chain ③ Spring Securityのアーキテクチャ② 認証情報の構造 ④ Spring Securityのアーキテクチャ③ 認証のフロー ⑤ Spring Securityのアーキテクチャ④ 認可のフロー ⑥ 認証のカスタマイズ ⑦ コードリーディングのコツ 6
目次 ① Spring Securityとは ② Spring Securityのアーキテクチャ① Filter Chain ③ Spring Securityのアーキテクチャ② 認証情報の構造 ④ Spring Securityのアーキテクチャ③ 認証のフロー ⑤ Spring Securityのアーキテクチャ④ 認可のフロー ⑥ 認証のカスタマイズ ⑦ コードリーディングのコツ 7
Spring Securityとは ◆Springプロダクトの1つで、主に認証・認可などの セキュリティ機能を持つフレームワーク ◆認証 = ユーザーが誰かを特定する ◆認可 = ユーザーの処理に対して許可・拒否を判定する ◆Spring MVC向けとSpring WebFlux向けの両方の機能を持つ ◆今日はSpring MVC向けの話 8
Spring Securityの誕生 ◆2004年にAcegi Securityとして誕生 ◆作者はBen Alex氏 ◆Spring Securityのソースコードを読むと 今でも "@author Ben Alex" と書いてある部分が多い ◆2008年にSpring Security 2.0としてアップデートリリース 9
年表 年 2008 2009 2015 2017 バージョン 2.0 3.0 4.0 5.0 2022 6.0 2025 7.0 主要なアップデート (よく知らない) (よく知らない) (よく知らない) リアクティブ対応、OAuth対応、 徐々にアーキテクチャの変更 アーキテクチャ変更により 非推奨化されたクラスの削除 例年通りなら11月リリース 10
Spring Securityのリリースサイクル ◆半年に1回リリース(5月と11月) ◆新機能の追加や旧機能の非推奨化のスピードも速い 11
ざっくりとした使い方 ◆pom.xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Security関連のライブラリをまとめたStarterライブラリ --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> 12
ざっくりとした使い方 ◆SecurityConfig.java @Configuration // Java Configクラスであることを示す @EnableWebSecurity // Webセキュリティ機能を有効化する public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); // BCryptアルゴリズムでパスワードをハッシュ化 } // 次ページへ 13
ざっくりとした使い方
◆SecurityConfig.java(続き)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception {
http.formLogin(login -> login
.loginPage("/login") // /login がログイン画面
.failureUrl("/login?error") // ログイン失敗時は /login?error にリダイレクト
.successForwardUrl("/") // ログイン成功時は / にリダイレクト
).logout(logout -> logout
.logoutUrl("/logout") // /logout にPOSTするとログアウト
.logoutSuccessUrl("/login") // ログアウト成功時は /login にリダイレクト
).authorizeHttpRequests(authz -> authz
.requestMatchers("/").authenticated() // / は認証が必要
.requestMatchers("/login").permitAll() // /login は誰でもアクセス可能
.requestMatchers("/admin").hasRole("ADMIN") // /admin は ADMIN 権限
);
return http.build();
}}
14
パッと見、使い方は簡単だけど・・・ ◆どうやってこの機能を実現しているのか? →次章以降で解説します 15
昔と使い方がだいぶ変わってるので注意 ◆Spring Security 5.4〜6.0にかけて、APIやアーキテクチャの 大幅な変更が行われている ◆変更点をまとめたブログ ◆https://zenn.dev/masatoshi_tada/articles/9f9b67768eb484 16
目次 ① Spring Securityとは ② Spring Securityのアーキテクチャ① Filter Chain ③ Spring Securityのアーキテクチャ② 主要クラスの構造 ④ Spring Securityのアーキテクチャ③ 認証のフロー ⑤ Spring Securityのアーキテクチャ④ 認可のフロー ⑥ 認証のカスタマイズ ⑦ コードリーディングのコツ 17
Filter Chainとは ◆Spring Securityの各セキュリティ機能は、 いくつものサーブレットフィルターで実現されている ◆それらのサーブレットフィルターの集まり=Filter Chain 18
[重要] リクエスト処理の流れ その他の Filter Delegating FilterProxy その他の Filter この形が Filter 実装クラス Dispatcher Servlet ブラウザ Filter Chain Controller FilterChain Proxy Security Filter 1 Security Filter 2 … Security Filter N 19
Filter Chain内の主なフィルター フィルター名 説明 SecurityContextHolder Filter SecurityContext(後述)をスレッドローカルにコピーする HeaderWriterFilter セキュリティ用の各種レスポンスヘッダーを付加する CsrfFilter CSRF対策を行う LogoutFilter ログアウトを行う UsernamePassword AuthenticationFilter ユーザー名とパスワードに基づくフォーム認証を行う ExceptionTranslation Filter 認証認可例外をハンドリングする AuthorizationFilter 認可処理を行う この順に 実行 20
独自のセキュリティフィルターの追加 @EnableWebSecurity @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.addFilterAfter( new CustomSecurityFilter(), // 自作したセキュリティフィルター BasicAuthenticationFilter.class // どのセキュリティフィルターの後に追加するか ); return http.build(); } } 21
よくある間違い ◆アクセスログ出力など、セキュリティに関係ないフィルターに http.addFilterAfter()を使わない! // これはダメ http.addFilterAfter(new AccessLogFilter(), BasicAuthenticationFilter.class); 22
間違ったフィルター追加 その他の Filter その他の Filter Delegating FilterProxy Dispatcher Servlet Controller Filter Chain Security Filter 1 ここに追加されてしまう! → セキュリティ例外時に 実行されないなど問題あり FilterChain Proxy Security Filter 2 AccessLog Filter … Security Filter N 23
正しいフィルター追加
◆セキュリティに関係ないフィルターは
FilterRegistrationBeanをBean定義する!
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<?> accessLogFilter() {
FilterRegistrationBean<AccessLogFilter> frb =
new FilterRegistrationBean<>();
frb.setFilter(new AccessLogFilter());
frb.addUrlPatterns("/*");
frb.setOrder(-200); // フィルターの順番
return frb;
}
}
24
正しいフィルター追加 ここに 追加される! その他の Filter AccessLog Filter Filter Chain Security Filter 1 Delegating FilterProxy Dispatcher Servlet FilterChain Proxy Security Filter 2 … その他の Filter Security Filter N Controller 25
DelegatingFilterProxyの順番 ◆application.propertiesの spring.security.filter.orderで変更可能(デフォルトは-100) その他の Filter Filter Chain Security Filter 1 Delegating FilterProxy その他の Filter FilterChain Proxy Security Filter 2 Dispatcher Servlet この中での順番 … Security Filter N 26
目次 ① Spring Securityとは ② Spring Securityのアーキテクチャ① Filter Chain ③ Spring Securityのアーキテクチャ② 認証情報の構造 ④ Spring Securityのアーキテクチャ③ 認証のフロー ⑤ Spring Securityのアーキテクチャ④ 認可のフロー ⑥ 認証のカスタマイズ ⑦ コードリーディングのコツ 27
認証情報の構造 HttpSession SecurityContext Authentication UserDetails getAuthentication() getPrincipal() ユーザー名・パスワードなど 28
SecurityContextとは ◆HttpSessionに保存される認証情報 ◆SecurityContextはインタフェース →実装クラスはSecurityContextImpl 29
SecurityContextのコピー ◆毎リクエストごとに、SecurityContextは HttpSessionからスレッドローカルにコピーされる ◆1つのHTTPリクエストは1つのスレッドで処理される →スレッドローカルに保存すれば、リクエスト処理のどこからでも SecurityContextを取得できる ◆コピーを行っているのはSecurityContextHolderFilter ◆レスポンスを返し終わったら、スレッドローカル内のコピーは 削除される 30
Authenticationとは ◆SecurityContextに保持される認証情報 ◆Authenticationはインタフェース → 認証方法ごとに実装クラスが用意されている <<interface>> Authentication 実装 Abstract Authentication Token 継承 UsernamePassword AuthenticationToken フォーム認証 AbstractOAuth2Token AuthenticationToken OAuth2 Anonymous AuthenticationToken 未ログイン時 31
UserDetailsとは ◆ユーザー名・パスワード・ロールなどの情報を持つ ◆UserDetailsはインタフェース → 実装クラスは2パターン ① 自分で作る(フォーム認証やBasic認証など) ② 認証方法ごとに用意されたものを使う(OAuth2など) 32
認証情報の取得 // スレッドローカルからSecurityContectを取得 SecurityContext securityContext = SecurityContextHolder.getContext(); // SecurityContextからAuthenticationを取得 Authentication authentication = securityContext.getAuthentication(); // AuthenticationからUserDetailsを取得(キャストが必要) UserDetails userDetails = (UserDetails) authentication.getPrincipal(); // コントローラーメソッドの引数で取得 @GetMapping("/foo") public String foo(Authentication authentication) { ... } // @AuthenticationPrincipalを付加すればUserDetailsを取得できる @GetMapping("/bar") public String bar(@AuthenticationPrincipal UserDetails userDetails) { ... } 未ログイン時は戻り値が String型なので注意 未ログイン時は nullになるので注意 33
目次 ① Spring Securityとは ② Spring Securityのアーキテクチャ① Filter Chain ③ Spring Securityのアーキテクチャ② 認証情報の構造 ④ Spring Securityのアーキテクチャ③ 認証のフロー ⑤ Spring Securityのアーキテクチャ④ 認可のフロー ⑥ 認証のカスタマイズ ⑦ コードリーディングのコツ 34
認証処理のフロー UsernamePassword AuthenticationFilter 認証処理を依頼 → 1つでもOKならば 認証OK Authentication Provider 呼び出し Authentication Manager Authentication Provider Authentication Provider 35
AuthenticationManager ◆AuthenticationManagerはインタフェース → 実装クラスはProviderManager(ややこしい) ◆複数のAuthenticationProviderに認証処理を移譲し、 1つでもOKが返ってくれば認証OKとする 36
AuthenticationProvider ◆認証処理を行うインタフェース ◆認証方法に応じて実装クラスがたくさんある ◆DB、LDAP、OAuth2、その他もろもろ https://spring.pleiades.io/springsecurity/site/docs/current/api/org/springframework/security/authentication/AuthenticationProvider.html 37
DaoAuthenticationProvider ◆よく使われるAuthenticationProvider実装クラス ◆UserDetailsServiceを利用して、 DBなどからユーザー情報を検索して認証を行う ◆自分でUserDetailsService実装クラスを作成してBean定義すれば、 それを使ってくれる ②検索 ①呼び出し DaoAuthentication Provider UserDetailsService ④検索結果を保持する UserDetails DB ③検索結果 38
目次 ① Spring Securityとは ② Spring Securityのアーキテクチャ① Filter Chain ③ Spring Securityのアーキテクチャ② 認証情報の構造 ④ Spring Securityのアーキテクチャ③ 認証のフロー ⑤ Spring Securityのアーキテクチャ④ 認可のフロー ⑥ 認証のカスタマイズ ⑦ コードリーディングのコツ 39
認可処理のフロー AuthorizationFilter Authorization Manager 呼び出し RequestMatcherDelegating AuthorizationManager Authorization Manager ※これもAuthorizationManager 実装クラス URLなどで適切な AuthtorizationManagerを判断 → 認可処理を移譲 Authorization Manager 40
AuthorizationManager ◆認可処理を行うインタフェース ◆認可方法に応じて実装クラスがたくさんある ◆認証されていればOKと判断、ロールで判断、その他もろもろ https://spring.pleiades.io/springsecurity/site/docs/current/api/org/springframework/security/authorization/AuthorizationManager.html 41
目次 ① Spring Securityとは ② Spring Securityのアーキテクチャ① Filter Chain ③ Spring Securityのアーキテクチャ② 認証情報の構造 ④ Spring Securityのアーキテクチャ③ 認証のフロー ⑤ Spring Securityのアーキテクチャ④ 認可のフロー ⑥ 認証のカスタマイズ ⑦ コードリーディングのコツ 42
カスタマイズしたいのは認証処理が多い ◆様々な認証処理 認可処理はあんまり カスタマイズしないはず A) DBからユーザーを検索する B) 独自の認証システムにアクセスする C) OAuth2・OpenID Connectを利用する D) ロードバランサーで認証済みの状態でアプリにアクセスする 43
A) DBからユーザーを検索する ◆UserDetails実装クラスを作成する public record LoginUserDetails( LoginUser loginUser, // これは自作のユーザー情報クラス Collection<? extends GrantedAuthority> authorities) implements UserDetails { ... } ◆UserDetailsService実装クラスを作成してBean定義する @Component public class LoginUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws ... { // DBからユーザーを検索してUserDetailsを返す }} 44
B) 独自の認証システムにアクセスする ◆AuthenticationProvider実装クラスを作成して Bean定義する @Component public class CustomAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 認証システムにアクセスして、OKなら新たなAuthenticationを返す } @Override public boolean supports(Class<?> aClass) { // aClassが期待するAuthentication実装クラスならばtrueを返す } } 45
C) OAuth2を利用する ◆書ききれないので以下の資料参照 https://www.docswell.com/s/MasatoshiTada/KL8JQ5-oauth2-and-springsecurity51-basic 46
D) ロードバランサーで認証済みの状態で アプリにアクセスする ◆例: AWS ALBとMS Entra IDを連携させる Entra ID ③認証 ②認証依頼 ALB ④アクセス トークン アプリ ①アクセス ⑤アクセス トークン 47
D) ロードバランサーで認証済みの状態で アプリにアクセスする ◆AbstractPreAuthenticatedProcessingFilterを継承する public class CustomPreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter { public CustomPreAuthenticatedProcessingFilter(AuthenticationManager am) { // このフィルターの認証に失敗したら、後続のフィルターチェインは実行しない setContinueFilterChainOnUnsuccessfulAuthentication(false); // これが無いとNullPointerExceptionが発生する setAuthenticationManager(am); } @Override protected Object getPreAuthenticatedPrincipal(HttpServletRequest req) { // リクエストヘッダーからJWTアクセストークンを取り出し、ユーザー名を抽出して返す } @Override protected Object getPreAuthenticatedCredentials(HttpServletRequest req) { return ""; // パスワードが不要な場合は空文字を返す } } 48
D) ロードバランサーで認証済みの状態で アプリにアクセスする ◆AuthenticationUserDetailsServiceを実装してBean定義する @Component public class CommonUserDetailsService implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> { @Override public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken auth) throws UsernameNotFoundException { String username = (String) auth.getPrincipal(); // JWTから抽出したユーザー名 // DBからユーザー情報を検索して返すなどする } } 49
D) ロードバランサーで認証済みの状態で アプリにアクセスする ◆作成したフィルターを登録する @EnableWebSecurity @Configuration public class SecurityConfig { @Autowired AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> uds; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.addFilter(new CustomPreAuthenticatedProcessingFilter(authenticationManager())) ... } private AuthenticationManager authenticationManager() { PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider(); provider.setPreAuthenticatedUserDetailsService(uds); provider.setThrowExceptionWhenTokenRejected(true); return new ProviderManager(List.of(provider)); } } 50
目次 ① Spring Securityとは ② Spring Securityのアーキテクチャ① Filter Chain ③ Spring Securityのアーキテクチャ② 認証情報の構造 ④ Spring Securityのアーキテクチャ③ 認証のフロー ⑤ Spring Securityのアーキテクチャ④ 認可のフロー ⑥ 認証のカスタマイズ ⑦ コードリーディングのコツ 51
Spring Securityにコードリーディングは必須 ◆想定通りに動かないなどのトラブルが多い ◆ドキュメントに書かれていない振る舞いが多い ◆数あるSpringプロダクトの中で、 多田が一番コードリーディングしているのがSpring Security 52
処理の起点から読む ◆認証処理ならUsernamePasswordAuthenticationFilter ◆認可処理ならAuthorizationFilter ◆読む前にアーキテクチャを理解しておくことが重要 53
デバッガを利用する ◆処理の流れを追いたい箇所にブレークポイントを設定する ◆ステップオーバー、ステップインで1行ずつ追う 54
呼び出し元を確認する ◆メソッドがどこから呼び出されているのかを確認する ◆IntelliJでは[使用箇所の検索] 55
図を書きながら読む ◆コードリーディングを進めていくと、少し前に読んだ内容を 忘れてしまう → 忘れてもいいように、クラス図やシーケンス図を書いておく ◆PlantUML・Mermaid・手書きなど、手段は何でもOK 56
最後に 57
読んでおくといい書籍 ◆Spring Securityを含むSpringの基礎をしっかり固める 58
読んでおくといい書籍 ◆Spring Securityを使いこなすには、サーブレットやHTTP、 Webセキュリティを理解することが大事 59
最新情報の収集手段 ◆公式ドキュメントを読む ◆英語: https://docs.spring.io/spring-security/reference/index.html ◆日本語: https://spring.pleiades.io/spring-security/reference/ ◆公式ブログを読む ◆https://spring.io/blog ◆最新のリリース情報やTIPSなどが紹介されている ◆Xでフォローする ◆Spring全般の公式アカウント: @springcentral ◆Spring Security公式アカウント: @SpringSecurity 60
ご清聴ありがとうございました! ◆よいSpringライフを! 61