SpringBootでログイン機能作ってみた

Java

今回もセキュリティ関連です!

前回は「spring-boot-starter-security」が提供してくれるBASIC認証をやってみたので、今回は、自分でログイン機能を作ってデータベース認証をやってみたいと思います!

何が違うかというと、ログインを許可するユーザとパスワードを設定ファイルではなく、DBに持たせることができるので、汎用性が高くなります。

BASIC認証だと、みんな同じユーザIDとパスワードを使うことになりますし、セキュリティはガバガバですね

プロトタイプなんかで、かつIPアドレスが絞れないような環境でクライアント認証させたいようなシステムだと結構このBASIC認証は簡単に実装できるので威力を発揮するんじゃないかなーと思ってます。

では!早速作っちゃいましょう!

手順は以下の通りです。

1:dependency の追加

2:JavaConfig関連の作成

3:認証処理クラスを作成

4:DBアクセス関連処理の作成

5:リクエストハンドラーの作成

6:テンプレート側の作成

ちなみに、今回はDBにH2を使用してデータベースアクセスにはMyBatisを使います

1:dependency の追加

早速、pom.xmlを修正します!

特段、取り上げるものは何もないですね。

2:JavaConfig関連の作成

こやつが新参者で、何をしているかというとSpringフレームワークがもつ機能の設定をいじってるって感じです。

ソースはこんな感じ

@Configurationアノテーションでこのクラスは設定情報を記述しているんですよ!とSpringに教えています。

続く@EnableWebSecurityってのは、SpringSecurityが提供しているConfigurationクラスをインポートし、SpringSecurityを利用するために必要となるコンポーネントのBean定義を自動で行われるようにするものです

で、UserServiceっていうサービスクラスをDIしている。このクラスについては後述する

そのあとのこいつ

こいつは、入力されたパスワードをエンコードするクラスでSpring側のライブラリ。

@Beanアノテーションをつけて、DIコンテナにこのエンコードクラスを登録してます。

続くこいつ。

こいつは、スーパークラスの「WebSecurityConfigurerAdapter」が持ってるメソッドで、最初のauthorizeRequests().anyRequest().authenticated()ってとこはよーわからん!笑

おろらく、すべてのリクエストに対して認証されているかチェックしまっせって感じ!

かな!わからん!また調べときいます。

で、次のformLogin().loginPage(“/login”) ってとこは、formLoginメソッドを呼び出すことで、フォーミ認証を有効にしており、FormLoginConfigurerのインスタンスが返ってきます。このインスタンスには、フォーム認証で使用するコンポーネントの動作をカスタマイズするためのメソッドが定義されており、その1つがloginPageメソッドです。このメソッドでフォーム認証に使用するフォームが存在する認証画面は「/login」ってパスやでってことを指定しています。

で、次のloginProcessingUrlメソッドが引数に指定した「/sign_in」ってパスにリクエストがあったら、「username」と「password」のパラメータを使って認証をかけます!成功したらsuccessForwardUrlメソッドの引数に指定している「/hello」ってパスにフォワードして、

失敗したらfailureUrlで指定している「/login」ってパスにerrorっていうパラメータつけて返します。

続く、permitAllメソッドは、すべてのユーザに対してログインフォームへのアクセス権を付与するためのメソッドです。

で、次のlogout().logoutUrl(“/logout”).logoutSuccessUrl(“/login?logout”)てのが

ログアウト用のパスは「/logout」でここにリクエストが来たらセッション破棄など認証情報を初期化して「/login」ってパスにlogoutっていうパラメータをつけて返します。

ちなみに、SpringSecurityでは、以下のような流れでログアウト処理を行う

・クライアントは、ログアウト処理を行うためのパスにリクエストを送信する。ここでいう「/logout」のこと

・LogoutFilterは、LogoutHandlerのメソッドを呼び出してログアウト処理を行う

・LogoutFilterは、LogoutSuccessHandlerのメソッドを呼び出して画面遷移を行う。

.logoutメソッドを呼び出すことで、ログアウト機能が有効になり、LogoutConfigurerのインスタンスが返されます。

そして。。。

このクラスの最後の部分

こいつは、configureってメソッドをDIしてて認証処理をごにょごにょしてます。笑

このuserDetailsServiceってのもSpring側のライブラリで、このクラスを継承した独自クラスを作成する必要があります。後述!

次は、本サイトにアクセスがあった場合にlogin画面を返す必要があるのでその設定です

例に倣って、@Configurationアノテーションです。

addViewControllersの引数で取得したクラスのaddViewControllerメソッドを呼びます。この引数にはパスを指定し、続くsetViewNameにhtmlファイル名を書きます。

以上!

3:認証処理クラスを作成

次は先ほどから後述すると先延ばしにしてきたUserServiceクラスです

はい、こいつのメソッドはDBへデータを更新するため@Transactionalアノテーションをクラス全体につけます

で、実際にMyBatisを使ってデータアクセスをするDAOクラスをDIしています。こいつは後述

注目すべきは、「UserDetailsService」っていうSpring側のライブラリクラスを実装していて、loadUserByUsernameメソッドをオーバーライドしています。

UserDetailsServiceは資格情報とユーザの状態をデータストアから取得するためのインターフェースで、以下のメソッドが定義されている。

オーバーライドしたメソッドですね!

このメソッドの最初でMyBatisを使ってユーザ名と一致するデータを取得しエンティティクラスであるLoginUserクラスに情報を格納して返します。

データベースに情報が見つからなかったら例外を吐きます。この結果がWebConfigSecurityクラスの

このfailureUrlに流れていくイメージ!

続くこいつが、DBに作成する権限テーブル関連の処理になっている

今回は権限テーブルとしてUSERテーブルを作ります!このテーブルにユーザ名とパスワードを登録しておくことで、認証が成功するという仕組みです!

最後の部分はエンコードクラスをインスタンス化してDBから取得したパスワードをエンコーディングさせ、ユーザ名、パスワード、権限リストをそれぞれフィールドに持ったUserクラスをUserDetailsインターフェースに格納して返します。

BCryptPasswordEncoderは、BCryptアルゴリズムを使用してパスワードのハッシュ化およびパスワードの照合を行う実装クラスで、ソルトには16バイトの乱数が使用され、デフォルトでは、1024回ストレッチングを行っているみたい。

※ソルト:パスワードに追加する文字列のこと。パスワードにソルトを追加して実際のパスワードより桁数を長くすると、レインボークラックなどのパスワード解析を困難にすることができる。

※ストレッチング:ハッシュ値の計算を繰り返し行うこと。ストレッチングを多く行いパスワード解析に必要になる時間を増やすと、パスワードの総当たり攻撃などによるパスワード解析を困難にすることができる。この回数は多いほど、強度はますがサーバへの負荷は高くなる。

ちなみに、SpringSecurityには、BCrypt以外にStandardPasswordEncoderとNoOpPasswordEncoderがあり、どれもPasswordEncoderの実装クラスです。

StandardPasswordEncoderは、SHA-256アルゴリズムを使用してパスワードのハッシュ化および照合を行う。

NoOpPasswordEncoderは、ハッシュ化をしません。テスト用のクラスとして用意されているので、実際のアプリケーションで使用することはないですね!

ちなみに、このUserクラスとUserDetailsインターフェース両方ともSpring側のライブラリです。

UseDetailsは、認証処理で必要となる資格情報(ユーザIDとパスワード)とユーザの状態を提供するためのインターフェースで、以下のメソッドが用意されている

で、SpringSecurityでは、UserDetailsの実装クラスとしてUserクラスを提供している

4:DBアクセス関連処理の作成

さて、DBアクセスです

DBアクセス処理を担うDAOクラスからみていきましょう!

データ永続化関連なので@Repositoryアノテーションをつけていて、LoginMapperインターフェースをDIしています。

findUserメソッドで引数に受け取った(フォームから送られてきたもの)ユーザ名を使用して、データを取得します!

LoginMappeerインターフェースがこれ

何の変哲もないクラスですね。あ、@Mapperアノテーション関連の説明は「SpringBootでMyBatis使ってみた」記事で解説しているのでよかったらみてください。

マッピングファイルがこれ

いいですね。user_name(引数で受け取ったもの)を使ってデータを検索していますl

取得したデータを格納するエンティティクラスはこれ

idとユーザ名、パスワードをフィールドにもつクラスです。

最後に、データベースにテーブルとデータを初期登録しておきます。

schema.sqlにテーブル作成文を書き、resources直下に保存します。

次に、USERテーブルに認証を許可するユーザ名とパスワードを登録するdata.sqlを同じくresources直下に保存します

以上で、データベース関連の処理は終わりです。

5:リクエストハンドラーの作成

では!リクエストハンドラを作ります

WebConfigSecurityクラスにて認証が成功したら「/hello」にフォワードするため、それを受け取るハンドラーが必要になります

ちなみに、このリクエストハンドラーが呼ばれるということは認証が成功しているということになります。

SpringSecurityのデフォルト実装では、認証済みのユーザの認証情報は、セッションに格納される。セッションに格納された認証情報はリクエストごとにSecurityContextPersistenceFilterクラスによってSecurityContextHolderというクラスに格納され、同一スレッド内であれば、どこからでもアクセスすることができます。

Authentication auth = SecurityContextHolder.getContext().getAuthentication();

この部分で、SecurityContextHolderから認証情報を取得してきます。

6:テンプレート側の作成

最後のテンプレートです。「login.html」「hello.html」の2つ

login.html

body内1つ目と2つ目のdivタグは、loginパスの後ろにlogoutのパラメータがあれば、1つ目のdivタグを表示し、errorのパラメータがあれば、2つ目のdivタグを表示するというものです。タグ内のtextには、messages.propertiesで設定した内容を出力せています。

各パラメータについては、WebSecurityConfigクラスで設定していましたね!

これです。?に続くものがパラメータになります。

続く、formタグのリクエスト先が「/sign_in」パスで、このパスは、WebSecurityConfigでのloginProsessingUrlで指定していたパスですね!

ここにリクエストが来ると、認証処理が走ります。

hello.html

この画面が認証処理が無事に成功した場合に表示される画面です。

問題ないですね!

ログアウトボタンを押せば、ログアウト処理が走ってログイン画面に戻るって感じです!

では、動かしてみましょう!

ログイン画面を表示し、data.sqlで登録しておいたユーザ名とパスワードでアクセスします。

無事にログインができました!では、ログアウトしてみます!

OKですね!

最後に、DBに登録していないユーザ名とパスワードでアクセスしてみます!

無事に弾かれました!

以上で、SpringBootでログイン機能作ってみたでした! 

ちなみに、ですね。

今回の記事は、ほぼほぼこの人の完コピになってしまってます。

参考サイト:https://takaxtech.com/2019/05/29/article311/

この人、尊敬です!私もまだまだ精進していきたいと思います!

BYE!

コメント

タイトルとURLをコピーしました