狠狠撸

狠狠撸Share a Scribd company logo
1
今こそ知りたいSpring Security
2020/12/17
日本Springユーザ会
土岐 孝平
自己紹介
? 土岐 孝平
? 合同会社 現場指向
? Webアプリ「メモラキー」の運営
? システム開発の支援
? 研修の講師
? 書籍の執筆
2
[改訂新版]Spring入門https://www.memorarchy.com
想定する聴講者
? なんとなく使ってはいるが、裏で何が起こってるのか
わからず、もやもやしている
? 少し触ったけど、難しそうなので自前にしようか悩ん
でいる
3
Spring 厂别肠耻谤颈迟测に触ったことがある方が前提です
本セッションの目的
? Spring Securityが裏で何をやっているかを知って、
問題が起きたときのデバッグや、カスタマイズ方法
の調査を円滑にする
? キーとなる以下の2つを抑える
– Security Filter Chain
– Security Context
4
網羅的な機能の説明や、設定ノウハウの説明は行いません
Security Filter Chainとは?
? Spring Securityが提供するFilter※の繋がり
? Servlet Filterの仕組みを使って処理を挟み込む
? 各Filterは、認証、URLパスの認可、CSRF防止などの役割
を持つ
5
※ 個々のFilterは
ServletのFilterイン
タフェースを実装し
ているが、Servletコ
ンテナに登録されて
いる訳ではない
Filter1
FilterX
DispatcherServlet
Delegating
FilterProxy
ブラウザ
??
?
???
Controller
??
?
ServletのFilter Chain
★Security Filter
Chain
Filter2
Spring MVCのサーブ
レット
主なFilter
6
クラス名 役割
CsrfFilter CSRFトークンの発行やチェックを行う
HeaderWriterFilter セキュリティ関連のレスポンスヘッダを
設定する
BasicAuthenticationFilter Basic認証を行う
UsernamePasswordAuthenticationFilter フォーム認証を行う
ExceptionTranslationFilter 後続の処理で発生した認証?認可の例
外をキャッチしてハンドリング(エラー画
面に遷移など)する
FilterSecurityInterceptor URLのパスの認可を行う
SecurityContextPersistenceFilter 「Security Context」を用意して、HTTP
セッションやThreadLocalに格納する
次ページで説明
Security Context
? 認証したユーザ情報(IDや権限など)を格納するオブジェクト
? HTTPセッションに保持されるため、リクエストを跨ってユー
ザ情報を参照できる
? ThreadLocal(スレッド毎にデータを管理してくれる)にも保持
されるため、リクエスト(スレッド)の処理内の任意のタイミング
でユーザ情報を参照可能
– Controllerの引数で受けとる
– 画面にユーザ情報を表示するときに参照
– staticメソッドでプログラムの任意の箇所で参照
? SecurityContextHolder.getContext()
– 認可時(URLのパス、メソッド、画面内の表示非表示)に参照
7
ThreadLocal
ThreadLocalとSecurity Contextのイメージ
8
Aさん
Bさん
Bさんの
スレッド
Bさんの
Security Context
Aさんのリクエスト(スレッド)の処理
Bさんのリクエスト(スレッド)の処理
Aさんの
スレッド
Aさんの
Security Context
Security Filter Chainの作成
? WebSecurityConfigurerAdapter を継承して
@EnableWebSecurityを付けたJavaConfigのクラス
(@Configurationを付けたクラス)を作成する
? @EnableWebSecurityがインポートしている
WebSecurityConfigurationクラスの中で、Security Filter
Chainのオブジェクトが作成される
? 標準的なFilterが自動で登録されるので、メソッドをオーバー
ライドして個別の設定を行う 9
@Configuration@Configuration
@EnableWebSecurity
public class SampleSecurityConfig extends WebSecurityConfigurerAdapter {
}
個別の設定のサンプル
10
@Configuration
@EnableWebSecurity
public class SampleSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.mvcMatchers("/admin/**").hasAuthority("ADMIN")
.anyRequest().authenticated().and()
.httpBasic();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder
= PasswordEncoderFactories.createDelegatingPasswordEncoder();
auth
.inMemoryAuthentication()
.withUser("foo").password(encoder.encode("passfoo")).authorities("ADMIN").and()
.withUser("bar").password(encoder.encode("passbar")).authorities("USER");
}
}
「/admin」 で始まるパスは「ADMIN」権限が
必要、それ以外は認証されてればOK
Basic認証を有効にする
「foo」ユーザは「ADMIN」権限、「bar」ユーザは
「USER」権限という認証情報をメモリ上に保管
出来上がった主なオブジェクト
11
Authentication
Manager
UserDetailsService
AccessDecision
Manager
Dispatcher
Servlet
Delegating
FilterProxy
ブラウザ
??
?
??
?
Controller
ServletのFilter Chain
★Security Filter Chain
ID?パスワードのチェックを
行う。認証情報は
UserDetailsServieから取得
権限のチェックを行う
認証情報を取得。今回はメ
モリ上から取得
URLのパスの認可を行う
BasicAuthenticationFilter
ExceptionTranslationFilter
SecurityContextPersistenceFilter
FilterSecurityInterceptor
Basic認証を行う
Security Filter Chainの動作イメージ
? 認可が必要なURLに初回アクセスしたとき
– ※主なFilterに絞っています
12
BasicAuthenticationFilter
ExceptionTranslationFilter
SecurityContextPersistenceFilter
FilterSecurityInterceptor
DispatcherServlet
リクエスト(/admin/foo) レスポンス
HTTPセッションにSecurity Context
がないため、新しく用意する。
用意したSecurity Contextを、
ThreadLocalに入れる
Authorization ID
Context
AuthorizationヘッダからユーザID?
パスワードを取得して認証する。
OKならユーザ情報をSecurity
Contextに入れる
スルー
URLパスに設定された権限と、
Security Contextのユーザ情報の権
限を比較する。OKなら後続の処理
を呼び出す
スルー
例外が投げられた訳
ではないのでスルー
スルー
ユーザ情報入りの
Security ContextをHTTP
セッションに入れる
フォーム認証のサンプル
13
@Configuration
@EnableWebSecurity
public class SampleSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.mvcMatchers("/admin/**").hasAuthority("ADMIN")
.anyRequest().authenticated().and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/login")
.usernameParameter("username").passwordParameter("password")
.failureUrl("/login?error");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
??? 省略
}
}
フォーム認証を有効にする
ログイン画面のパス。未認証のユーザ
を自動的に遷移させる
認証リクエストのパス。POST前提
認証リクエストに乗せるユーザIDとパス
ワードのパラメータ名ログイン失敗時に遷移
するパス
ログイン画面のサンプル
14
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<form th:action="@{/login}" method="post">
<input type="text" name="username" />
<input type="text" name="password" />
<input type="submit" value="送信" />
</form>
</body>
</html>
POSTメソッドを指定
※テンプレートエンジンのThymeleafを使用
Security Filter Chainの動作イメージ1/3
? 未認証のユーザが、認可が必要なURLにアクセス
15
UsernamePasswordAuthenticationFilter
ExceptionTranslationFilter
SecurityContextPersistenceFilter
FilterSecurityInterceptor
DispatcherServlet
ログイン画面にリダイレクトHTTPセッションにSecurity Context
がないため、新しく用意する。
用意したSecurity Contextを、
ThreadLocalに入れる
認証のリクエストでは無いので
スルー
リクエスト(/admin/foo)
スルー
未認証なので、
AccessDeniedExceptionをスロー
AccessDeniedExceptionをキャッチ
して、ログイン画面にリダイレクト
するレスポンスを設定。その際、
リクエストの内容をHTTPセッショ
ンに格納する(ログイン後に自動
で遷移するため)
スルー
空のSecurity Contextを
HTTPセッションに入れる。
Security Filter Chainの動作イメージ2/3
? 認証リクエストを送信
16
UsernamePasswordAuthenticationFilter
ExceptionTranslationFilter
SecurityContextPersistenceFilter
FilterSecurityInterceptor
/admin/fooにリダイレクトリクエスト(POSTの/login)
HTTPセッションから空のSecurity
Contextを取得して、ThreadLocal
に入れる。
認証リクエストだと判断して、リク
エストパラメータのID?パスワード
で認証する。認証したユーザ情
報をSecurity Contextに入れる。
HTTPセッションに格納しておいた
リクエストにリダイレクトするレス
ポンスを設定。
DispatcherServlet
ユーザ情報が入った
Security ContextをHTTP
セッションに入れる。
Security Filter Chainの動作イメージ3/3
? /admin/fooにアクセス
17
SecurityContextPersistenceFilter
FilterSecurityInterceptor
レスポンスリクエスト(/admin/foo)
HTTPセッションからSecurity
Context(ユーザ情報入り)を取得
して、ThreadLocalに入れる。
DispatcherServlet
ユーザ情報が入った
Security ContextをHTTP
セッションに入れる。
UsernamePasswordAuthenticationFilter
ExceptionTranslationFilter
認証のリクエストでは無いので
スルー
スルー
URLパスに設定された権限と、
Security Contextのユーザ情報の
権限を比較する。OKなら後続の処
理を呼び出す
スルー
例外が投げられた訳
ではないのでスルー
スルー
Security Filter Chainのログ 1/2
? どのFilterが、どの順番で動いたかをログに出力す
ることが可能
– デバッグに便利
? 設定方法
– ロガーの「org.springframework.security」をDEBUGレベルに
する
– application.propertiesでの設定例
18
logging.level.org.springframework.security=DEBUG
Security Filter Chainのログ 2/2
? 出力例
– 未認証のユーザが、認可が必要なURLにアクセスしたと
きのログ(抜粋)
19
/admin/foo at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
/admin/foo at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
No HttpSession currently exists
No SecurityContext was available from the HttpSession: null. A new one will be created.
/admin/foo at position 3 of 12 in additional filter chain; firing Filter: 'HeaderWriterFilter'
/admin/foo at position 4 of 12 in additional filter chain; firing Filter: 'CsrfFilter'
/admin/foo at position 5 of 12 in additional filter chain; firing Filter: 'LogoutFilter'
Request 'GET /admin/foo' doesn't match 'POST /logout'
/admin/foo at position 6 of 12 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
Request 'GET /admin/foo' doesn't match 'POST /login'
/admin/foo at position 7 of 12 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
saved request doesn't match
/admin/foo at position 8 of 12 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
/admin/foo at position 9 of 12 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
/admin/foo at position 10 of 12 in additional filter chain; firing Filter: 'SessionManagementFilter'
/admin/foo at position 11 of 12 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
/admin/foo at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
Secure object: FilterInvocation: URL: /admin/foo; Attributes: [hasAuthority('ADMIN')]
Voter: org.springframework.security.web.access.expression.WebExpressionVoter@7d30e6, returned: -1
o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
Filterの数と処理
準が表示われる
特別な処理が行われた
らログにでる
さいごに
? Spring Securityの裏の動きのイメージが沸いて、デ
バッグやカスタマイズの作業がし易くなれば幸いで
す。
? おすすめのサイト
– Spring Securityのマニュアルの「The Big Picture」の章
? https://docs.spring.io/spring-
security/site/docs/current/reference/html5/#servlet-
architecture
20
21
ご清聴ありがとうございました
22
ライセンスについて
? JSUGマスコットアイコン(本スライド左下)が残されている場合に限り、本作品(またそれを元にした派生
作品)の複製?頒布?表示?上演を認めます。
? 非商用目的に限り、本作品(またそれを元にした派生作品)の複製?頒布?表示?上演を認めます。
? 本作品のライセンスを遵守する限り、派生作品を颁布することを许可します。

More Related Content

Spring fest2020 spring-security

  • 2. 自己紹介 ? 土岐 孝平 ? 合同会社 現場指向 ? Webアプリ「メモラキー」の運営 ? システム開発の支援 ? 研修の講師 ? 書籍の執筆 2 [改訂新版]Spring入門https://www.memorarchy.com
  • 4. 本セッションの目的 ? Spring Securityが裏で何をやっているかを知って、 問題が起きたときのデバッグや、カスタマイズ方法 の調査を円滑にする ? キーとなる以下の2つを抑える – Security Filter Chain – Security Context 4 網羅的な機能の説明や、設定ノウハウの説明は行いません
  • 5. Security Filter Chainとは? ? Spring Securityが提供するFilter※の繋がり ? Servlet Filterの仕組みを使って処理を挟み込む ? 各Filterは、認証、URLパスの認可、CSRF防止などの役割 を持つ 5 ※ 個々のFilterは ServletのFilterイン タフェースを実装し ているが、Servletコ ンテナに登録されて いる訳ではない Filter1 FilterX DispatcherServlet Delegating FilterProxy ブラウザ ?? ? ??? Controller ?? ? ServletのFilter Chain ★Security Filter Chain Filter2 Spring MVCのサーブ レット
  • 6. 主なFilter 6 クラス名 役割 CsrfFilter CSRFトークンの発行やチェックを行う HeaderWriterFilter セキュリティ関連のレスポンスヘッダを 設定する BasicAuthenticationFilter Basic認証を行う UsernamePasswordAuthenticationFilter フォーム認証を行う ExceptionTranslationFilter 後続の処理で発生した認証?認可の例 外をキャッチしてハンドリング(エラー画 面に遷移など)する FilterSecurityInterceptor URLのパスの認可を行う SecurityContextPersistenceFilter 「Security Context」を用意して、HTTP セッションやThreadLocalに格納する 次ページで説明
  • 7. Security Context ? 認証したユーザ情報(IDや権限など)を格納するオブジェクト ? HTTPセッションに保持されるため、リクエストを跨ってユー ザ情報を参照できる ? ThreadLocal(スレッド毎にデータを管理してくれる)にも保持 されるため、リクエスト(スレッド)の処理内の任意のタイミング でユーザ情報を参照可能 – Controllerの引数で受けとる – 画面にユーザ情報を表示するときに参照 – staticメソッドでプログラムの任意の箇所で参照 ? SecurityContextHolder.getContext() – 認可時(URLのパス、メソッド、画面内の表示非表示)に参照 7
  • 9. Security Filter Chainの作成 ? WebSecurityConfigurerAdapter を継承して @EnableWebSecurityを付けたJavaConfigのクラス (@Configurationを付けたクラス)を作成する ? @EnableWebSecurityがインポートしている WebSecurityConfigurationクラスの中で、Security Filter Chainのオブジェクトが作成される ? 標準的なFilterが自動で登録されるので、メソッドをオーバー ライドして個別の設定を行う 9 @Configuration@Configuration @EnableWebSecurity public class SampleSecurityConfig extends WebSecurityConfigurerAdapter { }
  • 10. 個別の設定のサンプル 10 @Configuration @EnableWebSecurity public class SampleSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .mvcMatchers("/admin/**").hasAuthority("ADMIN") .anyRequest().authenticated().and() .httpBasic(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); auth .inMemoryAuthentication() .withUser("foo").password(encoder.encode("passfoo")).authorities("ADMIN").and() .withUser("bar").password(encoder.encode("passbar")).authorities("USER"); } } 「/admin」 で始まるパスは「ADMIN」権限が 必要、それ以外は認証されてればOK Basic認証を有効にする 「foo」ユーザは「ADMIN」権限、「bar」ユーザは 「USER」権限という認証情報をメモリ上に保管
  • 11. 出来上がった主なオブジェクト 11 Authentication Manager UserDetailsService AccessDecision Manager Dispatcher Servlet Delegating FilterProxy ブラウザ ?? ? ?? ? Controller ServletのFilter Chain ★Security Filter Chain ID?パスワードのチェックを 行う。認証情報は UserDetailsServieから取得 権限のチェックを行う 認証情報を取得。今回はメ モリ上から取得 URLのパスの認可を行う BasicAuthenticationFilter ExceptionTranslationFilter SecurityContextPersistenceFilter FilterSecurityInterceptor Basic認証を行う
  • 12. Security Filter Chainの動作イメージ ? 認可が必要なURLに初回アクセスしたとき – ※主なFilterに絞っています 12 BasicAuthenticationFilter ExceptionTranslationFilter SecurityContextPersistenceFilter FilterSecurityInterceptor DispatcherServlet リクエスト(/admin/foo) レスポンス HTTPセッションにSecurity Context がないため、新しく用意する。 用意したSecurity Contextを、 ThreadLocalに入れる Authorization ID Context AuthorizationヘッダからユーザID? パスワードを取得して認証する。 OKならユーザ情報をSecurity Contextに入れる スルー URLパスに設定された権限と、 Security Contextのユーザ情報の権 限を比較する。OKなら後続の処理 を呼び出す スルー 例外が投げられた訳 ではないのでスルー スルー ユーザ情報入りの Security ContextをHTTP セッションに入れる
  • 13. フォーム認証のサンプル 13 @Configuration @EnableWebSecurity public class SampleSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .mvcMatchers("/admin/**").hasAuthority("ADMIN") .anyRequest().authenticated().and() .formLogin() .loginPage("/login").permitAll() .loginProcessingUrl("/login") .usernameParameter("username").passwordParameter("password") .failureUrl("/login?error"); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { ??? 省略 } } フォーム認証を有効にする ログイン画面のパス。未認証のユーザ を自動的に遷移させる 認証リクエストのパス。POST前提 認証リクエストに乗せるユーザIDとパス ワードのパラメータ名ログイン失敗時に遷移 するパス
  • 14. ログイン画面のサンプル 14 <!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <body> <form th:action="@{/login}" method="post"> <input type="text" name="username" /> <input type="text" name="password" /> <input type="submit" value="送信" /> </form> </body> </html> POSTメソッドを指定 ※テンプレートエンジンのThymeleafを使用
  • 15. Security Filter Chainの動作イメージ1/3 ? 未認証のユーザが、認可が必要なURLにアクセス 15 UsernamePasswordAuthenticationFilter ExceptionTranslationFilter SecurityContextPersistenceFilter FilterSecurityInterceptor DispatcherServlet ログイン画面にリダイレクトHTTPセッションにSecurity Context がないため、新しく用意する。 用意したSecurity Contextを、 ThreadLocalに入れる 認証のリクエストでは無いので スルー リクエスト(/admin/foo) スルー 未認証なので、 AccessDeniedExceptionをスロー AccessDeniedExceptionをキャッチ して、ログイン画面にリダイレクト するレスポンスを設定。その際、 リクエストの内容をHTTPセッショ ンに格納する(ログイン後に自動 で遷移するため) スルー 空のSecurity Contextを HTTPセッションに入れる。
  • 16. Security Filter Chainの動作イメージ2/3 ? 認証リクエストを送信 16 UsernamePasswordAuthenticationFilter ExceptionTranslationFilter SecurityContextPersistenceFilter FilterSecurityInterceptor /admin/fooにリダイレクトリクエスト(POSTの/login) HTTPセッションから空のSecurity Contextを取得して、ThreadLocal に入れる。 認証リクエストだと判断して、リク エストパラメータのID?パスワード で認証する。認証したユーザ情 報をSecurity Contextに入れる。 HTTPセッションに格納しておいた リクエストにリダイレクトするレス ポンスを設定。 DispatcherServlet ユーザ情報が入った Security ContextをHTTP セッションに入れる。
  • 17. Security Filter Chainの動作イメージ3/3 ? /admin/fooにアクセス 17 SecurityContextPersistenceFilter FilterSecurityInterceptor レスポンスリクエスト(/admin/foo) HTTPセッションからSecurity Context(ユーザ情報入り)を取得 して、ThreadLocalに入れる。 DispatcherServlet ユーザ情報が入った Security ContextをHTTP セッションに入れる。 UsernamePasswordAuthenticationFilter ExceptionTranslationFilter 認証のリクエストでは無いので スルー スルー URLパスに設定された権限と、 Security Contextのユーザ情報の 権限を比較する。OKなら後続の処 理を呼び出す スルー 例外が投げられた訳 ではないのでスルー スルー
  • 18. Security Filter Chainのログ 1/2 ? どのFilterが、どの順番で動いたかをログに出力す ることが可能 – デバッグに便利 ? 設定方法 – ロガーの「org.springframework.security」をDEBUGレベルに する – application.propertiesでの設定例 18 logging.level.org.springframework.security=DEBUG
  • 19. Security Filter Chainのログ 2/2 ? 出力例 – 未認証のユーザが、認可が必要なURLにアクセスしたと きのログ(抜粋) 19 /admin/foo at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter' /admin/foo at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter' No HttpSession currently exists No SecurityContext was available from the HttpSession: null. A new one will be created. /admin/foo at position 3 of 12 in additional filter chain; firing Filter: 'HeaderWriterFilter' /admin/foo at position 4 of 12 in additional filter chain; firing Filter: 'CsrfFilter' /admin/foo at position 5 of 12 in additional filter chain; firing Filter: 'LogoutFilter' Request 'GET /admin/foo' doesn't match 'POST /logout' /admin/foo at position 6 of 12 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter' Request 'GET /admin/foo' doesn't match 'POST /login' /admin/foo at position 7 of 12 in additional filter chain; firing Filter: 'RequestCacheAwareFilter' saved request doesn't match /admin/foo at position 8 of 12 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter' /admin/foo at position 9 of 12 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter' /admin/foo at position 10 of 12 in additional filter chain; firing Filter: 'SessionManagementFilter' /admin/foo at position 11 of 12 in additional filter chain; firing Filter: 'ExceptionTranslationFilter' /admin/foo at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor' Secure object: FilterInvocation: URL: /admin/foo; Attributes: [hasAuthority('ADMIN')] Voter: org.springframework.security.web.access.expression.WebExpressionVoter@7d30e6, returned: -1 o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point Filterの数と処理 準が表示われる 特別な処理が行われた らログにでる
  • 20. さいごに ? Spring Securityの裏の動きのイメージが沸いて、デ バッグやカスタマイズの作業がし易くなれば幸いで す。 ? おすすめのサイト – Spring Securityのマニュアルの「The Big Picture」の章 ? https://docs.spring.io/spring- security/site/docs/current/reference/html5/#servlet- architecture 20