44.9K Views
May 26, 22
スライド概要
Spring Securityの仕組みをコードリーディングしながら解説しています。
ただし、Spring Securityのバージョンがやや古いため、最新のバージョンとはやや異なる部分もありますので、ご注意ください。
Java、Spring、IntelliJ IDEA
#中央線Meetup これで怖くない!? コードリーディングで学ぶ Spring Security (株)カサレアル 多⽥真敏 2018年11⽉30⽇ 中央線Meetup #2 (C) CASAREAL, Inc. All rights reserved. 1
#中央線Meetup このセッションについて ▸ Spring Securityの認証・認可の仕組みを、 ソースコードをもとに解説します ▸ 中級者向け ▸ ライブラリのソースコードを読んだことがある⽅ ▸ 認証と認可の区別がつく⽅ ▸ サーブレットAPIが分かる⽅ 2 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup ⾃⼰紹介 ▸ 多⽥真敏(@suke_masa) ▸ 研修トレーナー@カサレアル ▸ Spring / Java EE / Microservices / Cloud Foundry ▸ Pivotal認定講師 ▸ ⽇本Springユーザ会スタッフ ▸ ⽇本GlassFishユーザー会運営メンバー 3 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup Thymeleaf 3のドキュメントを(ちょっと)和訳しました! https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf_ja.html 4 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup ⽬次 ① Spring Securityとは ② アーキテクチャをざっくり理解する ③ ソースコードを読んでより深く理解する ④ おまけ 5 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup ⽬次 ① Spring Securityとは ② アーキテクチャをざっくり理解する ③ ソースコードを読んでより深く理解する ④ おまけ 6 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup Spring Securityとは ▸ Springのサブプロジェクト ▸ 認証・認可を中⼼に、セキュリティにまつわる様々な 機能を提供する ▸ 何重ものサーブレットフィルターで 機能を実現している ▸ 5.0でリアクティブ対応機能を 新規に開発した 7 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup Spring Securityの歴史 年 説明 2003 Acegi Securityとして誕⽣ 2004 Apache Licenseで公開→Springに取り込まれる 2008 Spring Security 2.0としてリリース 2009 Spring Security 3.0 2015 Spring Security 4.0 2017 Spring Security 5.0 2018 Spring Security 5.1 ←今ココ Thanks to Wikipedia 8 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup Spring Securityは複雑? ▸ 数あるSpringプロダクトの中でもかなり複雑な ⽅だと思います(発表者の主観) ⼤まかにでも内部のアーキテクチャを理解すれば 怖くなくなる・・・かも? 9 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup ⽬次 ① Spring Securityとは ② アーキテクチャをざっくり理解する ③ ソースコードを読んでより深く理解する ④ おまけ 10 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup 今回は ▸ 従来のサーブレット版を前提に解説します ▸ リアクティブ版は今回はスコープ外 11 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup ざっくり分かるSpring Securityアーキテクチャ リクエスト ※本当はspringSecurityFilterChainが2つあるのですが、 時間短縮のため省略しています springSecurityFilterChain (フィルター) レスポンス Filter 1 Filter 2 Authentication Manager (認証) … サーブ レット Filter N AccessDecision Manager (認可) 12 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup フィルター群 ▸ application.propertiesで logging.level.org.springframework.security=debug とするとログに出⼒される ... DEBUG: DEBUG: DEBUG: DEBUG: DEBUG: DEBUG: DEBUG: DEBUG: DEBUG: DEBUG: DEBUG: DEBUG: ... /login /login /login /login /login /login /login /login /login /login /login /login at at at at at at at at at at at at position position position position position position position position position position position position 1 of 12 ...: 'WebAsyncManagerIntegrationFilter' 2 of 12 ...: 'SecurityContextPersistenceFilter' 3 of 12 ...: 'HeaderWriterFilter' 4 of 12 ...: 'CsrfFilter' 5 of 12 ...: 'LogoutFilter' 6 of 12 ...: 'UsernamePasswordAuthenticationFilter' 7 of 12 ...: 'RequestCacheAwareFilter' 8 of 12 ...: 'SecurityContextHolderAwareRequestFilter' 9 of 12 ...: 'AnonymousAuthenticationFilter' 10 of 12 ...: 'SessionManagementFilter' 11 of 12 ...: 'ExceptionTranslationFilter' 12 of 12 ...: 'FilterSecurityInterceptor' 13 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup 重要なフィルター抜粋 クラス名 説明 ① SecurityContext PersistenceFilter セッションに格納していた SecurityContextをThreadLocalに移す ② LogoutFilter ログアウト処理を⾏う ③ UsernamePassword フォーム認証を⾏う AuthenticationFilter ④ ExceptionTranslation 発⽣した例外を受け取ってエラー Filter 画⾯を表⽰する FilterSecurity アクセス制限を⾏う Interceptor ⑤ 14 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup ⽬次 ① Spring Securityとは ② アーキテクチャをざっくり理解する ③ ソースコードを読んでより深く理解する ④ おまけ 15 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup Spring Securityコードリーディングのポイント ▸ 各フィルターから読むとよい ▸ 今回は前述のフィルター5つに絞って紹介 16 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup ①SecurityContextPersistenceFilter(1/2) // デフォルト実装はHttpSessionSecurityContextRepository private SecurityContextRepository repo; HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response); // SecurityContextをHttpSessionから取り出す SecurityContext contextBeforeChainExecution = repo.loadContext(holder); try { // ThreadLocalにSecurityContextを保存する SecurityContextHolder.setContext(contextBeforeChainExecution); https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/ security/web/context/SecurityContextPersistenceFilter.java#L98 17 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup ①SecurityContextPersistenceFilter(2/2) // 次のフィルターに処理を委譲 chain.doFilter(holder.getRequest(), holder.getResponse()); } finally { // リクエスト処理後のSecurityContextを取得 SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext(); // ThreadLocalからSecurityContextを削除 SecurityContextHolder.clearContext(); // HttpSessionにSecurityContextを再保存 repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse()); } 18 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup なぜThreadLocalを使うのか? ▸ 実⾏中スレッドのどこからでも SecurityContext内のユーザー情報に アクセスできる! // SecurityContextをThreadLocalから取得 SecurityContext context = SecurityContextHolder.getContext(); // 認証情報を取得 Authentication auth = context.getAuthentication(); // ログイン中のユーザー情報を取得 UserDetails userDetails = (UserDetails) auth.getPrincipal(); 19 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup
②LogoutFilter(1/2)
// ログアウト処理を⾏う
private final LogoutHandler handler;
// ログアウト成功後の処理を⾏う
private final LogoutSuccessHandler logoutSuccessHandler;
public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler,
LogoutHandler... handlers) {
// 複数のログアウト処理を保持するハンドラー
this.handler = new CompositeLogoutHandler(handlers);
// デフォルトではログアウト成功後、指定URLにリダイレクトする
this.logoutSuccessHandler = logoutSuccessHandler;
// ログアウトURLを"/logout"に指定
setFilterProcessesUrl("/logout");
}
https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/
security/web/authentication/logout/LogoutFilter.java#L101
20
(C) CASAREAL, Inc. All rights reserved.
#中央線Meetup ②LogoutFilter(2/2) // ログアウトURLだったら実⾏ if (requiresLogout(request, response)) { // 認証情報を取得 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); // ログアウト処理 this.handler.logout(request, response, auth); // ログアウト成功後の処理 logoutSuccessHandler.onLogoutSuccess(request, response, auth); return; } chain.doFilter(request, response); 21 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup LogoutHandlerは何をしている? ▸ SecurityContextLogoutHandlerが必ず最後に実⾏される ▸ それ以外のLogoutHandlerは認証情報を利⽤可能 ▸ LogoutHandlerはJava Configで追加できる if (invalidateHttpSession) { HttpSession session = request.getSession(false); if (session != null) { session.invalidate(); } } if (clearAuthentication) { SecurityContext context = SecurityContextHolder.getContext(); context.setAuthentication(null); } // SecurityContextを空にする SecurityContextHolder.clearContext(); 22 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup ③UsernamePasswordAuthenticationFilter // リクエストパラメータからユーザー名を取得 String username = obtainUsername(request); // リクエストパラメータからパスワードを取得 String password = obtainPassword(request); // Authenticationオブジェクトを⽣成 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); setDetails(request, authRequest); // AuthenticationManagerで認証 return this.getAuthenticationManager().authenticate(authRequest); https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/ security/web/authentication/UsernamePasswordAuthenticationFilter.java#L75 23 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup AuthenticationManagerとは? ▸ 認証処理を⾏うインタフェース ▸ 実装クラスはProviderManager ▸ 実際には、AuthenticationProviderに 認証処理を委譲する 24 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup AuthenticationManagerの構造 <<interface>> Authentication Manager どれか1つで 認証成功すればOK * ProviderManager <<interface>> Authentication Provider 25 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup
④ExceptionTranslationFilter(1/3)
try {
chain.doFilter(request, response);
// 例外をキャッチ
} catch (Exception ex) {
// 親の例外までgetCause()で辿り、全例外を配列で取得
Throwable[] causeChain =
throwableAnalyzer.determineCauseChain(ex);
// 例外の配列から、認証例外を探す
RuntimeException exception =
(AuthenticationException) throwableAnalyzer
.getFirstThrowableOfType(
AuthenticationException.class, causeChain);
https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/
security/web/access/ExceptionTranslationFilter.java#L118
26
(C) CASAREAL, Inc. All rights reserved.
#中央線Meetup ④ExceptionTranslationFilter(2/3) if (exception == null) { // 認証例外が無ければ、認可例外を探す exception = (AccessDeniedException) throwableAnalyzer .getFirstThrowableOfType( AccessDeniedException.class, causeChain); } if (exception != null) { // 認証例外または認可例外を処理する handleSpringSecurityException( request, response, chain, exception); } else { // 例外をスローする } } 27 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup ④ExceptionTranslationFilter(3/3) private void handleSpringSecurityException(...) throws ... { if (exception instanceof AuthenticationException) { // ログイン画⾯にリダイレクト } else if (exception instanceof AccessDeniedException) { if (/* もし無名ユーザー or RememberMeだったら */) { // ログイン画⾯にリダイレクト } else { // 403画⾯にフォワード } } } 28 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup 例外処理は階層がとても深い ▸ 興味がある⽅はさらにソースコードを追ってみ てください! 29 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup
⑤FilterSecurityInterceptor(1/3)
// 前処理としてのセキュリティチェック
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
// 次のフィルターまたはサーブレットを実⾏
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
// 例外の有無に関わらず⾏うセキュリティチェック
super.finallyInvocation(token);
}
// 後処理としてのセキュリティチェック
super.afterInvocation(token, null);
https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/
security/web/access/intercept/FilterSecurityInterceptor.java#L124
30
(C) CASAREAL, Inc. All rights reserved.
#中央線Meetup
⑤FilterSecurityInterceptor(2/3)
protected InterceptorStatusToken beforeInvocation(Object object) {
// アクセスしたリソースに対するアクセス許可設定を取得
Collection<ConfigAttribute> attributes =
this.obtainSecurityMetadataSource().getAttributes(object);
if (SecurityContextHolder.getContext().getAuthentication()
== null) {
// 認証情報が無い旨の例外をスロー
}
// 認証処理 -> 認証情報を取得 or 認証例外をスロー
Authentication authenticated = authenticateIfRequired();
31
(C) CASAREAL, Inc. All rights reserved.
#中央線Meetup ⑤FilterSecurityInterceptor(3/3) } try { // 認可処理を⾏う(NGなら例外をスロー、OKなら何もしない) this.accessDecisionManager.decide( authenticated, object, attributes); } catch (AccessDeniedException accessDeniedException) { // 認可失敗イベントを発⽕ publishEvent(new AuthorizationFailureEvent(...)); // 例外を再スロー throw accessDeniedException; } // 他にもいろんな処理・・・ 32 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup Filterなの?Interceptorなの? <<interface>> javax.servlet. Filter <<abstract>> AbstractSecurity Interceptor <<interface>> org.aopalliance.intercept. MethodInterceptor FilterSecurity Interceptor MethodSecurity Interceptor フィルターとして実⾏される (URL単位の認可制御) AOPとして実⾏される (メソッドへの認可制御) 33 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup AccessDecisionManagerの構造 <<interface>> AccessDecision Manager Affirmative Based (1票でもあればOK) 意思決定者 Consensus Based (多数決) Unanimous Based (全会⼀致) * <<interface>> AccessDecision Voter 投票者 34 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup ⽬次 ① Spring Securityとは ② アーキテクチャをざっくり理解する ③ ソースコードを読んでより深く理解する ④ おまけ 35 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup kawasimaさんのQiita https://qiita.com/kawasima/items/8dd7eda743f2fdcad78e (C) CASAREAL, Inc. All rights reserved. 36
#中央線Meetup kawasimaさんのQiita https://qiita.com/kawasima/items/8dd7eda743f2fdcad78e (C) CASAREAL, Inc. All rights reserved. 37
#中央線Meetup kawasimaさんのQiita https://qiita.com/kawasima/items/8dd7eda743f2fdcad78e (C) CASAREAL, Inc. All rights reserved. 38
#中央線Meetup kawasimaさんのQiita _⼈⼈⼈⼈⼈⼈⼈⼈⼈⼈⼈⼈⼈_ > 割愛させていただきます <  ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^ ̄ https://qiita.com/kawasima/items/8dd7eda743f2fdcad78e (C) CASAREAL, Inc. All rights reserved. 39
#中央線Meetup しゃーない、 ⾃分で作るか・・・ 😭 40 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup 使⽤技術 @kawasima @suke_masa ▸ Ninja Framework ▸ Spring MVC ▸ FreeMarker ▸ Thymeleaf ▸ JPA ▸ JPA (w/Spring Boot) ▸ JSR 250 (@RolesAllowed) ▸ Spring Security 41 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup 基本⽅針 ▸ オペレーション=URL・メソッド ▸ 各オペレーションにパーミッションを指定 42 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup URL単位のアクセス許可 @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { ... @Override protected void configure(HttpSecurity http) throws Exception { ... http.authorizeRequests() .mvcMatchers("/signup").permitAll() .mvcMatchers(GET, "/issues/").hasAuthority("readIssue") .mvcMatchers(GET, "/issue/new").hasAuthority("writeIssue") .mvcMatchers(POST, "/issues/").hasAuthority("writeIssue") .mvcMatchers("/users").hasAuthority("manageUser") .anyRequest().authenticated(); ... URLパターンごとに } パーミッションを指定 ... } 43 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup
ビジネスロジックのアクセス許可
@Service
public class IssueServiceImpl implements IssueService {
...
@PreAuthorize("hasAuthority('readIssue')")
@Transactional(readOnly = true)
public List<Issue> findAll() {
AOPでパーミッションが
AOPで実⾏直前に
return issueRepository.findAll();
パーミッションをチェック
チェックされる
}
@PreAuthorize("hasAuthority('writeIssue')")
@Transactional
public void register(Issue issue, String account) {
issueRepository.register(issue, account);
}
}
44
(C) CASAREAL, Inc. All rights reserved.
#中央線Meetup
ビュー層での権限による出し分け
<html xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
...
パーミッションがある場合のみ
<a href="#" class="header item">
リンクを表⽰
RBAC Example
(thymeleaf-extras-springsecurity5の機能)
</a>
<a th:href="@{/}" sec:authorize="isAuthenticated()">
Home
</a>
<a th:href="@{/issues/}"
sec:authorize="isAuthenticated() and hasAuthority('readIssue')">
Issue
</a>
<a th:href="@{/users/}"
sec:authorize="isAuthenticated() and hasAuthority('manageUser')">
Users
</a>
...
45
(C) CASAREAL, Inc. All rights reserved.
#中央線Meetup ソースコード ▸ https://github.com/MasatoshiTada/rbacexample-springsecurity 46 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup 今⽇のまとめ ▸ Spring Security、なかなか複雑ですね ▸ でもコードリーディングって楽しいですね! ▸ 興味があればAuthenticationManagerや AccessDecisionManagerも読むと⾯⽩いです ▸ RBACサンプルを作ったのでぜひ⾒てください! 47 (C) CASAREAL, Inc. All rights reserved.
#中央線Meetup Enjoy Spring Security!! ▸ ご清聴ありがとうございました! 48 (C) CASAREAL, Inc. All rights reserved.