yuusuke-roughの日記

Java,SpringBoot,趣味等

Spring Security 6.0.1にバージョンを変更した話

はじめに

いろいろ考え事があって本調子じゃない年末。少しだけ学習。

 

特定のURLの認可処理を実装していたところ、Spring Securityのドキュメントのバージョンが6.0.1である事に気づく。

WebConfigurerAdapter等が廃止され、起動もできなくなった。

 

今回は、上記問題の解決から始める。

内容

Spring Boot 3.0.0

Spring Security 6.0.1

 

JDKのバージョン変更

Spring Boot 3.0.0にはJava17が必要。

Java11からJava17へ切り替える。

Java 複数バージョン切り替え方法 - Qiita

 

 

②Spring BootのバージョンとSpring Security変更

pom.xmlにてSpring Boot 3.0.0、Spring Security 6.0.1に変更も...

 

dependencies.dependency.version' for org.thymeleaf.extras:thymeleaf-extras-springsecurity5:jar is missing.
    - プロジェクト・ビルド・エラー: 'dependencies.dependency.version' for org.thymeleaf.extras:thymeleaf-extras-springsecurity5:jar is 
     missing.

 

https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity6

Spring Securityのバージョンごとにthymeleaf-extras-springsecurityが対応しているのでthmeleaf-extras-springsecurityに変更。

 

③各ファイル修正

 

参考

Spring Security 5.7でセキュリティ設定の書き方が大幅に変わる件 - Qiita

SpringBoot 2.7 to 3.0 メモ - Qiita

Spring Boot 3.0 (Spring Framework6.0) へのアップデートで対応したこと - Qiita

 

1.importしているパッケージ名の変更

javax → jakarta

背景:JavaEEとJakartaEE、どう違うの? - Qiita

 

2.Spring Security変更点

 

AccessDecisionVoter、AccessDecisionManager、UnanimousBased、WebExpressionVoterが非推奨

WebSecurityConfigurerAdapterの廃止

 

Spring Security without the WebSecurityConfigurerAdapter

Authorize HttpServletRequests with AuthorizationFilter :: Spring Security

 

authorizeHttpRequestsに変更

Spring Securityのリファレンスにて下記のような改善が行われたとの事

  1. Uses the simplified AuthorizationManager API instead of metadata sources, config attributes, decision managers, and voters. This simplifies reuse and customization.

  2. Delays Authentication lookup. Instead of the authentication needing to be looked up for every request, it will only look it up in requests where an authorization decision requires authentication.

  3. Bean-based configuration support.

 

コードの書き方は下記

Spring Security - Lambda DSL

Spring Security: Upgrading the Deprecated WebSecurityConfigurerAdapter | Baeldung

AuthenticationManagerBuilderもUserDetailsManagerかUserDetailsServiceコンポーネントを定義するとある

 

....あれDAOAuthenticationProviderのBeanを設定して

@Bean
    public DaoAuthenticationProvider authProvider() {
        CustomAuthenticationProvider authProvider = new CustomAuthenticationProvider();
        authProvider.setUserDetailsService(userdetailservice);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

 

AuthenticationManagerBuilder使わなくていいの?

DAOAuthenticationProviderを勝手に追加してくれてる...?

...後日追記予定

 

修正し起動

Exception in thread "main" java.lang.UnsupportedClassVersionError: org/springframework/boot/SpringApplication has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0

 

java -versionとjavac -version共にJava 17だが...

 

コンソール上はAdoptOpenJDK¥jdk-11.0.11.9...になっていた。

STSの実行環境を確認すると上記設定と一致。

 

手順は実行の構成→該当のプロジェクト→JRE→環境→インストール済みのJRE

で、ロケーションを追加した。

 

⑤動くけど...

Application shutdown requested.

...

Stopping service [Tomcat]

...

The web application [ROOT] appears to have started a thread named [HikariPool-6 housekeeper] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:

java.base@17.0.5/jdk.internal.misc.Unsafe.park(Nativ Method)

java.base@17.0.5/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:252)
 java.base@17.0.5/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:1672)
 java.base@17.0.5/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1182)
 java.base@17.0.5/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:899)
 java.base@17.0.5/java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1062)
 java.base@17.0.5/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1122)
 java.base@17.0.5/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
 java.base@17.0.5/java.lang.Thread.run(Thread.java:833)

がでた。

 

sun.misc.Unsafe#parkって何? - スティルハウスの書庫の書庫

スレッドをブロック...触れた事のないお話...

 

HikariPool-6 housekeeperがメモリリークのような状態にある?

Spring Boot で HikariCP のコネクションリークを調べる - Qiita

 

「HikariPool but has failed to stop it.」 で検索するとTomcatの仕様で出力される・プロセス停止辞典でメモリ開放はできているとのため、ひとまず、この問題は置いておく。

 

 

感想

焦ったのは④と⑤。

Java17で設定しているのにエラーがでたので、実行環境かなと直感は働いた。

しかし、具体的な設定方法がわからず、STSの中を探し見つけたときは嬉しかった。

 

SpringSecurity自体は設定後に一発で動いたのでよし。

 

⑤については現状対処不要ですが、

HikariPoolについてやメモリリークが本当に起こった場合の特定と解決方法については、とても興味がある。

 

今、学習しなくてはいけない事を改めてまとめると...

①基本情報等の知識→「マスタリングTCP/IP 入門編」に絞って隙間時間に学習中

②DB→「達人に学ぶDB設計」でテーブル見直し完了・パフォーマンス等は現時点で検証不可のため保留

SQL→1月予定

JavaScriptフレームワーク→1月予定

⑤Git→プライベートリポジトリで活用中・誤った理解がないか「サル先生のGit入門」で都度補完

ExcelVBA→早くて1月

-------以下、趣味で必要な分野(習得期間未定)-------

⑦JavaGold

AWS

⑨SpringBoot

メモリリークやエラーに対する対策とトラブルシューティング

⑪HTML、CSS、BootStrap

-------余裕のある時-------

アプリ開発

アルゴリズム

 

退職したら、①~⑥最優先に切り替える。

今までの学習方針からは一転して、その目安は、「基本の理解を速く、手を動かして行う」。

理由としては、場数で身につく世界と思うので、場数をこなせる前提条件を整える事が最優先に思えた。そのために、速さを意識して学習する。

 

 

全然関係ない話

FFUSAのボスバトルBGMのメタルアレンジが疾走感あって好き。というか、ただただ懐かしい。

そういえば、妹夫婦からギターするように言われているけれど、二人と違ってバンド経験ないし、まず指が動くレベルまで到達していない。アコギで自分ペースにポロポロ弾いてるのが好きなレベル。

最近してなかったけれど、気持ちが沈む時はプログラミングに加えて、絵描いたり、ギター練習するのも良さそう。しかし、運動、数学、読書といい、気持ちが落ち着くのって学校の科目らしい。

 

ゲームは誘われてする分には楽しいから好き。けど、一人の場合はコツを掴んでしまうと、どのタイトルも時間に対する実力の伸び具合の天井が見えて、そのためにプレイする意味を見出せなくなる。感情的な楽しさ。

対して、プログラミングや数学は時間かかるけど、血のめぐるような達成感がある。

後者は岡潔の言う情緒というものに近いと思う。

道半ばなので、「近い何か」としか言えない。

数学かなり忘れちゃったけど、確率を勉強してて思う。

 

旧ハンターのセル画いいなと思いました。

重厚感というか暗さがある。「太陽は夜も輝く」とか。

Gitで2回目のプッシュ in Git Bash

はじめに

年末、資格取得と平行で忙しく、楽しく過ごしております。

さて、2回目のGitとなります。

 

今回は、ロールと権限を追加したプロジェクトをプライベート設定されているリモートリポジトリにプッシュしようと思います。

プッシュするブランチはmainです。

ReaDiscussionAuthorizationTestブランチは特定のURLにて認可処理を行う実装をした際に使用します。

 

記事の構成は、次回のプッシュをスムーズで行えるよう内容に流れを記載、ハマった事については別途記載しました。

 

内容

初めに、git statusを使用したら、fatal: not a git repository (or any of the parent directories): .git

→ローカルリポジトリのあるディレクトリに移動してなかった...

 

mysiteディレクトリに移動し、cd mysiteで該当ディレクトリに移動。

一応、git branch -aにてブランチを確認。

 

ファイルを追加してgit add ~でインデックスにファイルを登録

git commit -m "..."でコメントを追加しコミット

 

今回はmainにプッシュするので、pushのパラメータは省き、git push でOK。

 

 

ハマった事

初めに、前回上流ブランチを作成したので、そのままgit push origin ReaDiscussionAuthorizationTestを実行したらプッシュが終わ...らなかった。

Everything up-to-date

...なのに、更新されない。

 

解決

git log --statで確認すると

 コミットが(HEAD -> main)のみとなっている。

あれ(HEAD -> main , origin/main)じゃないんだ...となりました。

 

現在選択しているディレクトリはmysite、ブランチはmain。

HEADは今、作業しているブランチの最新のコミットを指す。

main、ReaDiscussionAuthorizationTestはローカルリポジトリのブランチ。

origin/main、origin/ReaDiscussionAuthorizationTestがリモートリポジトリのブランチ。

HEADはローカルリポジトリにあるブランチmainにある。

 

mainでadd、コミット、プッシュしたのだから...

元々変更のないReaDiscussionAuthorizationTestをプッシュしても意味がない...

そして、HEAD -> main は単にコミットの履歴なのでプッシュと関係はない。

原因の特定ができました。そして、一番初めのプッシュの話に戻ります。

 

 

--余談--

git statusをホームディレクトリで使用すると

[Q&A] コマンド$git statusを入力 エンターした後、理解できない文字列が出てきます - Qiita

とでる。

git initの取り消し方 - Qiita

間違ってgit init してしまった場合は、追加された.gitを削除できるとの事

 

感想

 

見苦しい過ちが多かったです。

ワーキングツリーやリポジトリーの概念がごっちゃになっていたり、一連のコマンドの理解が浅かったと思います。

その整理ができたのは良かったです。

 

気を付ける事は3点

・今、どこで作業しているのか

・そのコマンドは、どこに何をするものなのか

・そのコマンドで、他に影響はないか

 

全然関係のない話

 

亡き祖父の昭和史10巻ほどを持ち帰っていたので、ふと、太平洋戦争後期から読んでいました。

写真付きでアッツ島ガダルカナルといった聞いた事のあるエピソードがより詳細に書かれているので、興味深いです。また、戦地のみならず、政治も含めた一連の歴史を事細かに記載しているため、近代史そのものという感じがします。

この白黒写真に写るボロボロの軍服を着ていて、笑顔で国からの手紙を読むキスカ守備兵達の顔が、現代人と全く同じことに不思議な感覚を覚えます。

 

ディスカバリーチャンネルで戦車の歴史や戦艦の砲撃の弾道解説といった第二次世界大戦特集を観ていた15の夏をふと思い出しました。

近代史の他、日本史は戦国や幕末あたり、世界史は全般的に好きなので、ちょっと寝る前に読もうと思いました。

Role実装 in Spring Security

はじめに

余談からになりますが、「達人に学ぶDB設計」を再読しており、実装が遅れました。

改善としては、既読管理テーブルと参加者テーブルはひとつにできそうだなと思いました。

また、パフォーマンスというのも全ての実装ができてからの話と思うので、あまり深追いはせず、インデックスについては考えるのみ留めました。

今のところ、インデックスを付与したいのは、ログ取得テーブルです。日時検索、チャット内容の曖昧検索だとカーディナリティが高くなりそうと考えました。

しかし、奥が深い。理解が及ばない範囲については、試験的に動かして過ちが判明すると思うので、楽しみにしておきたいです。

 

さて、今回のテーマの前に最終目標から。

最終目標は、Webセキュリティ式によって、各部屋の認可処理をSpring Security側で行う事です。

 

そこで今回は、二年前にも少し触れたRoleから認可の流れを、BaeldungとSpringドキュメントを元に触れていきます。前回と違うのは権限とロールの階層です。

目標は、ロールと権限それぞれで特定のURLやオブジェクトへのアクセスを制御する事です。

抜けている知識や疑問点についても逐一、記載します。

 

昨日の就職活動の面接にて、JPAに頼らずSQLに触れるという事は大事とアドバイス頂いて、ちょうど旬な話題でした。(全然関係ない話になりますが、雇用形態によって就職・転職活動と表現が変わるのかなと悩んで、就職活動と表記していましたが、普通に転職活動でした。)

 

設計

ADMIN:Delete

USER:Read,Write

とします。権限は今後の実装で、新しいロールと共に必要になるため動作確認兼ねて実装予定。その際に、USERのReadも修正予定。

 

内容

今回はこちらを参考に進める。

Spring Security - Roles and Privileges | Baeldung

 

ポイント

・ユーザー、ロール、権限の関係はそれぞれ多対多で構成する。

・ContextRefreshedEventを用いてApplicationContextが初期化、リフレッシュされた時にロールと権限をセットアップする。...※1

・UserDetailsService内でロールと権限を付与する。GrantedAuthorityに含める。...※2

・役割に階層を持たせる事で、複数の役割を付与しないで済む。RoleHierarchyで設定。...※3

 

 

テーブル設計

 

>As we can see, we're considering both the User <-> Role as well as the Role <-> Privilege relationships to be many-to-many bidirectional

 

多対多で双方向

spring-data-jpaを使った場合の単方向と双方向について - だらだらと思いついたこととか書くブログ

mappedByでも良さそう...

@JoinTable自体はJoinColumnsとInverseJoinColumnsでリレーションが一目でわかるので、User <-> Role <-> Privilege の多対多がわかりやすいというのがあるのだろうか(未解決)

 

※1 ContextRefreshedEventを使用した処理について

イベントについて理解が曖昧になっているため再度整理。

①ApplicationContextとは?

Spring Framework コアテクノロジー - リファレンス

SpringのDIコンテナの動作イメージ(雰囲気)を掴もう - Qiita

DIについては上記参照。

DIコンテナ を構成するのが、BeanFactoryとApplicationContext。ApplicationContextは構成フレームワークと基本機能の提供するBeanFactoryの上位互換。

Beanのインスタンス化、構成、組み立てをする。

 

②ここでのイベント処理とは?

ApplicationContextのイベント処理は、ApplicationListenerでApplicationEventを登録。

今回は、ContextRefreshedEventが登録されている。

これが、ApplicationContextが初期化された際に発行できる仕組み。

あとは、onApplicationEventにてイベント処理をするという流れ。

 

※2 GrantedAuthorityの仕組みついて(※3の認可処理メモ参照)

RoleVoterについてSpring Security Referenceにて

>ConfigAttribute が ROLE_ プレフィックスで始まるかどうかを投票します。ROLE_ プレフィックスで始まる 1 つ以上の ConfigAttributes と正確に等しい String 表現(getAuthority() メソッドから)を返す GrantedAuthority がある場合、アクセスを許可することに投票します。ROLE_ で始まる ConfigAttribute の完全一致がない場合、RoleVoter はアクセスを拒否するために投票します。ConfigAttribute が ROLE_ で始まらない場合、投票者は棄権します。

 

...ConfigAttributeって何だろう

Spring Security 使い方メモ 認証・認可 - Qiita

ConfigAttribute (spring-security-docs API) - Javadoc

セキュリティ設定が入っていて、これと照らし合わせて認可するとの事。

 

権限についてはどうかというと

上記Qiita記事にて、WebExpressionVoterについての記載が...

現在はWebExpressionAuthorizationManagerで、指定された式を評価し認可するとの事

その式がhasAuthority()。

 

つまり、ロールも権限もそれぞれのVoterがチェックするのでまとめて入れているという事がわかった。AccessDecisionManagerに引き渡すためにGrantedAuthorityを使用すると。

 

※3 設定したロールの階層について(未解決)

認可アーキテクチャ :: Spring Security - リファレンス

 

この階層についてはSpring Web Expressionsに含める必要があるので、WebSecurityExpressionHandlerにRoleHierarchyのインスタンスを追加する。

WebSecurityExpressionHandlerはWebSecurityConfigurationに含まれる。

WebSecurityConfigurationはFilterChainProxyを作成するWebSecurityをカスタマイズするためのクラスだから、そのままの意味で設定自体に組み込む(自由に定義できる)という解釈でいいのだろうか...(Spring Web Expressionsが検索してもでてこない....)

 

--余談--

ロールに関わる認可について整理

AccessDecisionManagerにてGrantedAuthorityを読み取るが、この内部のAccessDecisionManager→AccessDecisionVoter→RoleVoterで認可を行う。

 

上記を呼び出すAuthorizationManagerはRoleHierarchyVoterを呼び出す。

 

Collection<? extends GrantedAuthority>について

[Java] ジェネリクス 境界ワイルドカード型のご紹介 - Qiita

境界ワイルドカードにすると、OAuth2UserAuthorityにも使えて便利。

OAuth2...試してみたい。

 

感想

ドキュメントに書かれているので、何が何を呼び出して認可しているというのを学習するには良い機会になりました。

今回の最終目標にも十分近づけたのではないかと思います。

ただ、もっと深掘りできる部分は多々あったので、時間の確保ができ次第、学習の修正はしていきたいです。

 

イベント処理を辿るとDIの話に、DIの話からBeanのスコープ等もっと深みのある話に...時間が足りなさ過ぎて触れられず、以前にカスタムイベント実装した時の復習みたいになってしまったのが悔しい。

 

SpringのDIコンテナの動作イメージ(雰囲気)を掴もう - Qiita

>「Beanのスコープ」「Beanの優先順位」「初期化処理/破棄処理」「AOP(Proxy)」「プロファイル」などの仕組みに関する知識

 

設計の分野になるのだろうか、到達点としては、すごく憧れがある。そして、とにかく深い。

このような分野をチームでコミュニケーション取って、理解し実装していく過程を想像すると、とても楽しいだろうなと思いました。

 

全然関係のない話

気付くと年末です。

2021年、2022年はムエタイのアマチュア大会の出場や就業先を通したコンクールの参加で、人間的に成長できた年でありました。

2023年は、どのような形であれ、技術と成果に重きを置いた年にしたいと思います。

Gitに触れてみた日記

はじめに

就職活動を始めた。

その中で頂いたアドバイスとして、私に必要な点は3点

①チーム開発を想定したツール等の知識(Git等のバージョン管理)

②他フレームワークの知識(JavaScriptフレームワーク等)

③チーム開発を想定した関連部門とコミュニケーションを円滑に取るための知識(基本情報処理等)

 

元々、自前の認可処理を廃止し、Spring Securityの認可処理をカスタマイズする事にする予定だったので 、直近で手をつけることにしたのは、①となった。

予定では、現在、プログラミングと並行している資格取得が終わり次第、③。

基本機能の実装が完了次第、②となる。

②については、細かいアニメーション等も検討してみたいので、時間のある時にライブラリが豊富なものを探したい。

 

内容

参考にしたサイト

【Windows】Gitの環境構築をしよう! | プログラミングの入門なら基礎から学べるProgate[プロゲート]

 

リモートリポジトリにプッシュする|サル先生のGit入門【プロジェクト管理ツールBacklog】

 

GitBashを用いてプライベートのリモートリポジトリに現コードをプッシュする事を目的とする。

 

ひとまず、progateを元にhtmlファイルのアップロードはできた。

しかし、自身のコードがpushできない事にハマった。

 

状況:リモートリポジトリへReadiscussionをpushしようとしてもできない。

 

ローカルリポジトのbranch (branch -aで表示。このAはALLのA)
ReaDiscussionAuthorizationTest(ReaDiscussionのコード)
main(htmlファイルは削除したため無)
remotes/origin/main ※リモート追跡ブランチ = ローカルリポジトリmainの上流ブランチ


ワークツリー(ReaDiscussionのコード)

git push origin ReaDiscussionAuthorizationTestを実行も
src refspec ReaDiscussionAuthorizationTest does not match any
failed to push some refs to 'GithubのURL'
との事


pushについて
https://git-scm.com/docs/git-push

リポジトリー等の引数で指定しなかった場合は、デフォルトのoriginにpushされる
コマンドラインや構成で何にpushしたのか特定できない時は、上流ブランチで一致するか確認する。ローカルブランチと一致する同じ名前がない場合は中止する。

 

-u
--set-upstream
For every branch that is up to date or successfully pushed, add upstream (tracking) reference, used by argument-less git-pull[1] and other commands. For more information, see branch.<name>.merge in git-config[1].

 

つまり、
git push -u origin ReaDiscussionAuthorizationTest
だとローカルリポジトリReaDiscussionAuthorizationTestの上流ブランチ remotes/origin/ReaDiscussionAuthorizationTestが生成されるからプッシュできる。

 

mainにもコードがコミットされたので履歴を確認。

git logを打つと...

commit 何々 (HEAD -> main, origin/main, origin/ReaDiscussionAuthorizationTest, ReaDiscussionAuthorizationTest)

 

HEADとは?

GitのHEADとは何者なのか - Qiita

>つまりコミットの場所を記憶しているのがブランチ、ブランチの場所を記憶しているのがHEAD

 

との事。

git reflogを打ち、内容を確認すると...

2b95aef (HEAD -> main, origin/main, origin/ReaDiscussionAuthorizationTest, ReaDiscussionAuthorizationTest) HEAD@{0}: merge origin/ReaDiscussionAuthorizationTest: Fast-forward
eafd40e HEAD@{1}: checkout: moving from ReaDiscussionAuthorizationTest to main
2b95aef (HEAD -> main, origin/main, origin/ReaDiscussionAuthorizationTest, ReaDiscussionAuthorizationTest) HEAD@{2}: checkout: moving from ReaDiscussionAuthorizationTest to ReaDiscussionAuthorizationTest ...

 

HEAD@{~}については公式ドキュメントより

>HEAD@{2} means "where HEAD used to be two moves ago"

 

今後使用するかもしれないのでメモ

Gitを使ってやらかした時、git reflogさえ使えればわりかしなんとかなる - Qiita

 

fast-forwardについての問題点については課題としたい。

 

感想

Githubでコードをアップロードする事はしていたが、Gitから行ったり、その仕組みを学習するのは初めてだった。

ひとまず、心置きなく認可処理を書ける状況にはなったのでこのまま学習を進めていきたい。

 

DaoAuthenticationProviderのテスト in JUnit

はじめに

今年は機能追加に時間を費やし、テストについては2023年2月頃に予定をしておりましたが、浅く触れ始める事を目的にJUnitを触れる事にしました。

感想としては、「もっと早く触れておけばよかった」に尽きます。

過去、OTP実装に当たってUsernamePasswordAuthenticationTokenを使用した際に、引数のオブジェクトにユーザーオブジェクトを割り当てるという間違った実装をしていた事がありました。(こちらは最初に作成されたAuthenticationのPrincipalをそのまま追加する事で解決しました。)

その際に、二要素認証のアカウントのみで他のパスワードリセットやアカウント削除機能ができなくなりましたが、どの機能に影響がでているかというのを全て手動で行ったのです。

二要素認証なのでID、Password入力→スマホのGoogleAuthenticationでOTPを確認、入力→各機能を試すといった具合です。

これが、事前にパスワードリセット、アカウント削除と各テストを用意しておけば時間などかからなかったわけです...。

 

という事で、初めてのテストは認証から始めました。

 

内容

まず、「①OTPを所持しているAuthenticationをSecurityContextが持っている②OTPの秘密鍵含めたユーザー情報は既にリポジトリーに保存されている」のが、実際の環境に近いかなと思ったので@WithSecurityContextにて下記を作成

 

参考

Spring Security 18. テスト - リファレンス

 

 

WithMockCustomUserSecurityContextFactory.java

 

public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser>{

    @Autowired
    SiteUserRepository siteUserRepository;
    
    private UserDetailsServiceImple userDetailsServiceImpl;
    
    @Autowired
    private PasswordEncoder passwordEncoder;

 

@Override
    public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
        
        
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        
        MockHttpServletRequest request = new MockHttpServletRequest();    
        String secretKeyForOTP = "TestOTPCode";
        
        setOTPToHttpServletRequest(secretKeyForOTP, request);
        registerUserRepository(customUser, secretKeyForOTP);
        
        //SecurityContextで使用するAuthenticationの準備
        UserDetails principal = userDetailsServiceImpl.loadUserByUsername(customUser.Mail());
        UsernamePasswordAuthenticationToken testAuth = new UsernamePasswordAuthenticationToken(principal, customUser.password());
        addDetailToAuthentication(testAuth, request);
        
        context.setAuthentication(testAuth);
        return context;
        
    }
    
    
    @Autowired
    public WithMockCustomUserSecurityContextFactory(UserDetailsServiceImple userDetailsService) {
        this.userDetailsServiceImpl = userDetailsService;
    }
    
    private void registerUserRepository(WithMockCustomUser customUser, String secretKeyForOTP) {
        SiteUser testUser = new SiteUser();
        testUser.setUsername(customUser.username());
        testUser.setMail(customUser.Mail());
        testUser.setPassword(passwordEncoder.encode(customUser.password()));
        testUser.setSecret(secretKeyForOTP);
        testUser.setEnabled(true);
        siteUserRepository.save(testUser);
    }
    
    private void setOTPToHttpServletRequest(String secretKeyForOTP, MockHttpServletRequest request) {
        
        Totp totp = new Totp(secretKeyForOTP);
        String currentOTP = totp.now();
        request.setParameter("code", currentOTP);
    }

    private void addDetailToAuthentication(UsernamePasswordAuthenticationToken testAuth,MockHttpServletRequest request ) {

        CustomWebAuthenticationDetailsSource addDetails = new CustomWebAuthenticationDetailsSource();
        testAuth.setDetails(addDetails.buildDetails(request));
        
    }

}

 

使用するユーザー情報についてはこちら

WithMockCustomUser.java

 

@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser {
    String username() default "掲載のため削除";
    String password() default "掲載のため削除";
    String Mail() default "掲載のため削除";
}

 

続いて、テストを行うクラス

@SpringBootTest
@Transactional
class CustomAuthenticationProviderTest {

    @Autowired
    SiteUserRepository siteUserRepository;
    

    @Autowired
    CustomAuthenticationProvider customAuthenticationProvider;
    
    @Autowired
    UserDetailsServiceImple userDetailsServiceImpl;
    
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Test
    @WithMockCustomUser
    void whenOTPUserExists_expectToGetAuthenticationObject() {
    
        Authentication authentication = (Authentication)SecurityContextHolder.getContext().getAuthentication();
        
        customAuthenticationProvider.setUserDetailsService(userDetailsServiceImpl);
        customAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        Authentication authenticatedUser =  customAuthenticationProvider.authenticate(authentication);
        
        assertEquals(authentication.getName(), authenticatedUser.getName());
        
    }

}

 

余談

途中で苦しんだ事:Authenticationに直接Detailをセットする方法がないため検索したところ、TestingAuthenticationTokenを確認するもDaoAuthenticationProviderにてonly usernamepasswordauthenticationtoken is supportedのお叱りを受ける。

このUsernamePasswordAuthenticationtokenにもsetDetails()があるとの事で解決。

 

あと、忘れちゃいけないDaoAuthenticationProviderへのUserDetailsServiceとPasswrodEncoderのセット(いつも忘れます)。

 

感想

テストを通して、SpringSecurity の理解がまた一つ深まったように思います。

OTP実装やテストにしても新しい取り組みをきっかけに理解が深まるというのはとても楽しいです。

logout処理に悩んだ話 in Spring Security

はじめに

Spring Security にてログアウト処理を行うときは下記のようにlogout()を設定するだけでよかった。

ログアウトの処理 :: Spring Security - リファレンス

 

今回は何かしらの処理をした後にログアウトするという実装。

 

ハマッタ事

上記のログアウト処理は、POSTでログアウトのUrlにリクエストを送って処理される。

なので、コントローラ内で何かしらの処理をした後にPOSTでリダイレクトする事はできないのでハマったという次第。

 

解決?

HttpServletRequst内にてlogout()がある。

 

サーブレット API の統合 :: Spring Security - リファレンス

通常、これは、SecurityContextHolder がクリアされ、HttpSession が無効化され、「自分を記憶」認証がクリーンアップされることを意味します

 

また、CookieのRemember-Meについては別途、削除した。

Spring-Boot で Cookie の付与・取得・削除 - Qiita

 

疑問点

・HttpSessionとRemeber-Meの削除だけでいいのだろうか?

・Spring SecurityではGETによるLogoutはできないのか

・リダイレクトでログアウトする方法はないのか?

循環参照 in Spring Boot

はじめに

中間テーブル実装に伴い、アカウント削除に必要な機能を加えてました。

元々、アカウント側とルーム側で分けてサービス層を作っており、ルーム側はアカウント側をインタフェースインジェクションで参照しておりました。

 

そのため、アカウント側からルーム側を参照するようにしたところ

The dependencies of some of the beans in the application context form a cycle:

 …というエラーでました。

 

内容

 

interface CallUserService

UserService 

interface CallDiscussionService

DiscussionService

 

にてUserServiceがCallDiscussionServiceを、DiscussionServiceがCallUserServiceをインジェクションしているという構成だった。

 

ダメなのはわかったけれど、DIについてやはり分からないので調べた。

 

循環参照。
https://qiita.com/daker2016/items/598a86e478334c1193e3
コンストラクターでInjectionの場合、循環参照は解決できなくて、代わりに、SetterのInjectionで変更が必要となります。


違いは何だろう。
今まで使用してきた@Autowiredとはインスタンス化ではないのか?
そもそもDIとは?

https://qiita.com/ritukiii/items/de30b2d944109521298f
>「Dependency」(依存性と訳していた)は、「オブジェクト」です。
つまり、DIとは、「依存性の注入」じゃなくて、「オブジェクトの注入」だった訳ですね。

インスタンスをnewする事の問題点の話もあった。
https://qiita.com/haseesah/items/32ad604c15328e91fb75
>「あるモジュールが別のモジュールの内部動作によって変化したり依存したりする = 依存関係にあるモジュールの仕様を変更した場合、モジュールの内容が壊れる可能性が大きい」
実際にJava言語などで依存性が高い構成とは
・あるクラスのメソッド内で別のクラスのインスタンスをnewして生成している
・あるクラスのメソッドの引数として他のクラスのインスタンスを使用している
といったものが挙げられます。

https://qiita.com/kazuki43zoo/items/7a0e96573e930ac934ed

「DIの考え方を取り入れてプログラムを書く」にて、
オブジェクトを用いたインスタンス生成を行っている。
BクラスのメソッドをAクラスから呼びだせるようにしている。

>AクラスにDIの考え方を取り入れたことで、Aクラスの中でBクラスのオブジェクトを生成する処理がなくなりました。このような構成にすることで、AクラスはBクラスに加えてBクラスの子クラスのオブジェクトを使って処理を実行することができるようになります。

Wikiでは
>DIは制御の反転の一種で、オブジェクトの作成と利用について関心の分離を行い、疎結合なプログラムを実現することを目的としている。

そして、3つのインジェクションがあるとの事。

>インタフェース注入
注入用のインタフェースを定義して注入を行う方法
setter 注入
setter メソッドを定義して注入を行う方法
コンストラクタ注入
コンストラクタを定義して注入を行う方法

 

もし、この両方を使用する場合は別の層を作る必要があるのだろうか。

わかりません。