Spring Bootによるウェブアプリ開発: [補足01] ユーザ認証(2)

前回の続き。

アカウントの情報がハードコードだと管理が面倒なので、データベースを使おう。その際、保存するパスワードはハッシュ化しよう。

作成・変更したファイル

今回使うページを次に示す。

パス アクセス制限 備考
/suppl01/admin/ 管理人ロールをもつ登録ユーザ 管理人用トップページ
/suppl01/admin/add_user 管理人ロールをもつ登録ユーザ ユーザ登録フォーム

アカウント情報を格納するテーブル

40.1 User Schemaにあるようにテーブルを作ると、簡単にアカウント情報として利用することができる(もちろん設定をすれば他の構成でも使えるが)。今回は、これを参考に次のSQL命令でテーブルを作ることにする。

40.1 User Schemaからの変更点は以下の通り。

  • passwordカラムは、ハッシュ化したパスワードを保存する為にサイズを100に変更
  • すでに存在している場合は作成しないようにするために「IF NOT EXISTS」を追加

このSQL命令をH2コンソールで実行してもいいし、JdbcTemplateで実行してもいいのだが、ファイルに記述したSQL命令をアプリの起動時に実行する方法(78.3 Initialize a database using Spring JDBC)がある。
今回はこの方法を使うことにし、src/main/resources/schema.sqlを作成する。

WebSecurityConfigurerAdapterを継承した設定用クラス

参考: 5.6.2 JDBC Authentication

  • 16行目: 設定時にJdbcTemplate(かDataSourcedの)インスタンスが必要なので、引数として「JdbcTemplate jdbcTemplate』を追加し、DIコンテナから取得する。
  • 21行目: パスワードのエンコーダとして使うBCryptPasswordEncoderをインスタンス化する。
  • 22行目: JDBCを使った認証で、どのデータベースを使うか(jdbcTemplate.getDataSource())とどのパスワードエンコーダを使うか(passwordEncoder)を指定する。
  • 29行目: 「/suppl01/admin/**」へのアクセスへは管理者ロールを持つアカウントがアクセスできることを指定する。

コントローラ

  • 86行目: SecurityConfiguration.javaと同様にパスワードエンコーダをインスタンス化する。
  • 87ページ: パスワードエンコーダのencodeメソッドを使ってパスワードをエンコードしたものを保存する。
  • 88ページ: ロールをauthoritiesテーブルに保存する。usersテーブルの情報だけではダメなことに注意する。

テンプレート

add_user.html

  • 14行目: ロールは「ROLE_」をつけることに注意する。

動作確認

ハードコードしたadminでログインし、一般ユーザ、管理者ユーザを作成し、それらのユーザで正しくアクセスできること等を確かめてみよう。

Spring Bootによるウェブアプリ開発: [補足01] ユーザ認証(1)

フォームからユーザ名とパスワードを入力してログインする機能(ユーザ認証)を実現しよう!

作成・変更したファイル

今回使うページを次に示す。

パス アクセス制限 備考
/suppl01/ 誰でも トップページ
/suppl01/login 誰でも ログインフォーム
/suppl01/loginProcess 誰でも ログイン処理を行う(ログインフォームからのPOST先)
/suppl01/logout 誰でも ログアウト処理を行う(ログアウトフォームからのPOST先)
/suppl01/secret/ 登録ユーザ ログイン後に遷移するページ
/suppl01/secret/info 登録ユーザ アカウントの情報を表示するページ

Spring Securityの有効化とBasic認証の無効化

Spring Securityの機能を加えよう。
build.gradleのdependenciesに次の1行を加える。

これでSpring Securityの機能が有効になる。デフォルトでBasic認証の機能が有効になるので、
パスワードとユーザ名を入れないとページが見えなくなる。

Basic認証

今回はBasic認証は使わないので無効にしよう。無効にするにはapplication.propertiesに次の1行を追加する。

WebSecurityConfigurerAdapterを継承した設定用クラス

WebSecurityConfigurerAdapterクラスを継承したクラスを作ることで、ユーザ認証の設定を行うことができる。

  • 9行目: このクラスが設定用のクラスであることを示す。これに加え「@EnableWebSecurity」を記述するという解説が様々なところでなされているが、この設定については82.1 Switch off the Spring Boot security configurationを参照すること。
  • 14〜16行目: ログインできるアカウントを設定する。今回はハードコーディングする(次回、データベースに変更する)。
  • 21〜22行目: ユーザ認証を要求するページとしないページを設定する。
  • 24〜26行目: ログインに関する設定をする。ログインフォームのURLと、ログインフォームの中でどのパラメータ名(input要素のname属性)を使うか、ログインを処理するURL(ログインフォームのactionで指定するパス)、成功した時に遷移するURL、ログイン失敗した時に遷移するURLを指定する。
  • 28〜29行目: ログアウトに関する設定を行う。ログアウトを処理するURL(ログアウトフォームのactionで指定するパス)、ログアウトが成功した時に遷移するURLを指定する。

コントローラ

  • ログインを処理する「/suppl01/loginProcess」とログアウトを処理する「/suppl01/logout」はコントローラで定義しない(Spring Secutiryの機能でアクセスできるようになるため)。
  • 42行目: ユーザ情報を取得するには、@AuthenticationPrincipalアノテーションをつけたUserDetails型を引数にする。

テンプレート(解説が必要なもの)

login.html

  • 10〜12行目: ログインに失敗した場合は「/suppl01/login?error」に遷移する(SecurityConfiguration.javaを確認せよ)。つまり「error」というパラメータが付加される。「param.containsKey(‘error’)」でerrorというパラメータがあるかチェックできる(参考: request/session 属性などに対するウェブコンテキストネームスペース
  • 13行目: methodはpostにする。
  • 13行目: actionはh:action属性で指定する(単なるaction属性じゃダメ)。action属性は、th:actionでリンクURL式を使う。このようにすることによって、自動的にCSRF対策用のパラメータが付加される。
  • 13行目: URLはSecurityConfiguration.javaのloginProcessingUrlで指定したURLにする。
  • 14〜15行目: name属性はSecurityConfiguration.javaで指定した文字列を設定する。

secret/{index,info}.htmlのログアウトフォーム

  • ログインの場合と同様に、methodはpostとし、actionはth:action属性で指定する。URLはSecurityConfiguration.javaのlogoutUrlで指定したURLにする。
  • 単なるリンクにしたい場合は、logoutRequestMatcherメソッドを使った形にする(調べてみよう)。

問題点

基本的なユーザー認証の仕組みを見てきたが、このままでは不便(ユーザ情報がハードコードで管理が面倒)だし問題がある(パスワードが平文で管理されている)。次回では、ユーザ情報をデータベースに保存し、パスワードをハッシュ化して保存する方法を紹介する。

Spring Bootによるウェブアプリ開発: 起動方法(サービスの公開)

これまでアプリの開発における起動方法として、NetBeansのメニューから実行という方法を使ってきた。
この方法は開発時にはいいが、IDEのないPCにコピーしての実行や、サーバでの実行には向かない。

gradlewを使ってbootRunタスクを実行する。

これまでNetBeansからのGradleのタスクを実行してきたが、IDEを使わずにコマンドラインからも実行できる(というかこれが本来の使い方)。

ターミナル(コマンドプロンプト)で、プロジェクトのディレクトリをカレントディレクトリとし、

./gradlew bootRun

と実行する。gradlewに実行権限がなければ事前に「chmod +x gradlew」としておく。Windowsの場合は、

gradlew.bat bootRun

とする(未確認)。

FAT JARを作る。

クラスファイルやリソース等をJAR(Java Archive)という形式にまとめることができる。もともと、JARファイルは自分の作ったプログラムをまとめるもので、ライブラリ等は別に準備して実行する、という使い方が普通であった。最近は、依存するライブラリ(サーブレットコンテナ)等を全て含めて1つのJarファイルで実行ができるようにした「FAT JAR」を利用することも増えている。Spring BootではこのFAT JARも簡単に作ることができる。

./gradlew build

作られたJARファイルは、build/libに置かれる(例えば、webapp-0.0.1-SNAPSHOT.jar)。

これを実行するには、そのファイルを適当なディレクトリにコピーし、そのディレクトリをカレントディレクトリとして、次のように実行する。

java -jar webapp-0.0.1-SNAPSHOT.jar

Windowsならばダブルクリックで起動できる場合もある。しかし、標準出力に出力されるメッセージが読めないので、上記コマンドで実行するのがおすすめである。

FAT JARと同じところにstaticディレクトリをおけば、静的ファイルも扱うことができる。

作ったアプリは公開しよう

残念ながら、Javaで作ったアプリを公開できるサービスは少ない。レンタルサーバでは使えるのはPHPのみというサービスが多い。現実的には、VPSを借りJavaをインストールして実行する、ということになるだろう。

VPSを借りるという以外では、Javaが実行できるPaaSを使う方法がある(これも対応サービスは少ないが)。例えば、HEROKUならばDeploying Spring Boot Applications to Herokuというドキュメントも用意されている。条件によっては無料で使うこともできる。

Spring Bootによるウェブアプリ開発: オンラインフォトアルバム(アプリ開発例)

ポイント:

  • ファイルをアップロードする
  • フォームで日付を扱う

ソースコード

静的コンテンツ

詳しくは27.1.5 Static Contentを参照せよ。

ファイルをそのままサーバから送信するにはsrcと同じ階層に「static」というフォルダを作り、そこのファイルをおく。

static

例えば、
static/lecture12/ThumbsUp.jpgというファイルは、
http://localhost:18080/lecture12/ThumbsUp.jpgでアクセスすることができる。

また、src/main/resources/staticというフォルダも用意されており、こちらに置いたファイルにもアクセスすることができる。ただし、このフォルダの場合は、アプリ起動後に追加したものは表示できない(起動時にファイルを含めたJARファイルを作っているから)。

コントローラからこのフォルダを操作する場合そのpathを知る必要があるが、その方法の1つとしてResourceHttpRequestHandlerから得る方法がある(コントローラの53〜63行目)。ただし、この方法が標準的な方法かどうかはわからない。

画像のアップロード

  • テンプレート(add.html)
    • 11行目: ファイルを送る場合はform要素に「enctype=”multipart/form-data”」という属性をつける。
    • 16行目: 「type=”file”」とするとファイルアップロード用のフォームができる。
  • コントローラ(Lecture12Controller.java)
    • 99行目: ファイルはMultipartFile型で受け取ることができる。
    • 101〜108行目: 受け取ったファイルは、サーバ上のテンポラリ領域に置かれるファイルなので、transferToメソッドを使って実際に使う場所にファイルを移す。移す先をFileクラスのインスタンスで指定する。このtransferToメソッドは検査例外(教科書P.66)が発生する可能性があるので、例外処理を行う。

なお、フォームで送ることのできるデータのサイズには制限がある。これを変更するには以下の2行をapplications.propertiesに加える。これは、リクエスト全体のサイズ(デフォルトは10MB)を100MBまで、リクエストに含まれる1ファイルのサイズ(デフォルトは1MB)を10MBまでに変更する設定である。

今回は画像のアップロード時にサムネイルを生成することする。
画像のスケーリングには、imgscalrを使うが、特にこれがおすすめというわけではない。また、ファイル名から拡張子を取得するのは単純な文字列処理ではあるが、今回はApache Commons IOを使うことにする。これらを使うためにbuild.gradleに次を追加する。

サムネイル生成のプログラムについては今回の主題ではないので解説は省略する。

画像の表示

  • テンプレート(index.htmlとview.html)
    • img要素(タグ)を使う
    • th:srcでsrc属性を動的に生成する
    • th:hrefと同様に@構文を使う
    • alt属性はつけよう。

日付の扱い

  • テンプレート(add.html、index.html、view.html)
    • フォームによる入力は「type=”date”」とすると対応ブラウザなら日付ピッカー等の適切なUIで入力することができる。
    • 出力する時にフォーマットするには、「th:text=”${#dates.format(photo.date, ‘yyyy年MM月dd日’)}”」等とする(Using Thymeleaf(Expression Utility Objects 日付))
  • コントローラ(Lecture12Controller.java)
    • 100行目: Date型で受け取ることができる。「@DateTimeFormat(pattern = “yyyy-MM-dd”)」でブラウザが送ってくる文字列のパターン(フォーマット)を指定する。