Spring徹底入門
-
目次
- 第1章 Spring Frameworkとは
- 第2章 Spring Core(DI×AOP)
- 第3章 データアクセス(Tx、JDBC)
- 第4章 Spring MVC
- 第5章 Webアプリケーションの開発
- 第6章 RESTful Webサービスの開発
- 第7章 Spring MVC の応用
- 第8章 Spring Test
- 第9章 Spring Security
- 第10章 Spring Data JPA
- 第11章 Spring + MyBatis
- 第12章 Spring+Thymeleaf
- 第13章 Spring Boot
- 第14章 チュートリアル
-
形式:書籍
-
発売日:2016年07月20日
-
ISBN:9784798142470
-
価格:本体4,000円+税
-
仕様:B5変・744ページ
第1章 Spring Frameworkとは
-
Spring Frameworkの概要
- 「Spring Frameworkは、開発当初より古いインフラ環境上でも新しいインフラ環境上でも、最新のプログラミング思想で開発出来ることを設計思想にしている」
-
Spring Frameworkの歴史
- 2003年:Interface21 Framework → Spring Frameworkへと変更になった (Expert One-one-One: J2EE Design and Development)
- 2004年:EJBを使わずにSpringFramework1.0で開発する方法
- 2005年:SpringFramework1.2 の機能をカバーした書籍が刊行 SpringをStruts,Hibernateと組み合わせて使う(SSH)
- 2006年:SpringFramework2.0がリリース
- 2007年:SpringFramework2.5 アノテーションベースのDIやMVC
- 米国に拠点を移す SpringToolSuiteなど統合開発環境の提供が始まる
- 2009年:SpringFramework3.0がリリース JSR330に対応
- 2013年:SpringFramework4.0がリリース WebsocketやWebメソッドなどをサポート
- Pivotalという新しい会社にスピンオフ
- 2014年:昨今注目されているSpringBootやSpringIOPlatformプロジェクトが始まる
-
Springの各種プロジェクトについて
- SpringMVC: Webアプリケーションを開発するためのフレームワークであり、MVCパターンを利用している
- MVCパターンには、「アクションベースフレームワーク」と「コンポーネントベースフレームワーク」の2パターンある
- アクションベースフレームワーク
- リクエストによって実行する処理(アクション)を決定し、処理の結果としてレスポンスを返すフレームワーク
- コンポーネントベースフレームワーク
- リクエストやレスポンスを抽象化(隠蔽)し、画面を構成するコンポーネントをベースにWebアプリケーションを開発するフレームワーク
- JSFはコンポーネントベースのフレームワーク
- MEMO: JavaEE徹底入門読んで理解した!
- アクションベースフレームワーク
- 各種サードパーティとの連携機能もある
- Jackson
- ApacheTiles
- FreeMarker
- Rome
- JasperReports
- また、Thymeleafなど、サードパーティ自体がSpringMVCをサポートしているケースも有る
- MVCパターンには、「アクションベースフレームワーク」と「コンポーネントベースフレームワーク」の2パターンある
- SpringMVC: Webアプリケーションを開発するためのフレームワークであり、MVCパターンを利用している
-
Spring Security
- Authentication(認証)
- Authorization(認可)などのセキュリテイ要件を用意に実現するためのフレームワーク
- 非常に多くの認証方式に対応している
- Basic認証
- ダイジェスト認証
- X.509クライアント証明証
- LDAP
- OpenID
- 非常に多くの認証方式に対応している
-
Spring Data
- Spring Dataはリレーショナル・データベースやNoSQL、Key-Valueストアなど様々なデータストアへのデータアクセスを用意にするためのもの
- Spring Data Commons
- Spring Data JPA
- Spring Data MongoDB, Spring Data Redis, Spring Data Solr, etc…
- Spring Dataはリレーショナル・データベースやNoSQL、Key-Valueストアなど様々なデータストアへのデータアクセスを用意にするためのもの
-
Spring Batch
- バッチアプリケーション向けの軽量フレームワーク
- 大容量のデータ処理に必要な共通機能を提供している
- もともと、Accentureが開発したバッチアプリケーション向けのフレームワークをベースとしており、2008年にSpringBatchとしてバージョン1.0がリリース
-
Spring Integration
- Enterprise Integration Patterns(EIP)としてよく知られているさまざまなシステム間を連携させるアーキテクチャパターンに基づく開発をサポートするフレームワーク
- 同様の機能を実現するOSSとして、ApacheCamelが有名です。
-
Spring Cloud
- 分散環境でCloud Nativeなアプリケーションを開発するためのフレームワーク及びツール群
- SpringCloudConfig
- SpringCloudBus
- SpringCloudConnectors
- SpringCloudNetflix
- 分散環境でCloud Nativeなアプリケーションを開発するためのフレームワーク及びツール群
-
Spring tool Suite
- Eclipseベースの統合開発環境
-
Spring IO Platform
- 依存関係を管理するためのライブラリー
-
Spring Boot
- 最小限の設定でプロダクションレベルのSpringアプリケーションを容易に開発するためのSpringプロジェクト
-
JavaEEとの関係
- 差は縮まっている
- Springのほうが新しい技術を取り込むサイクルが早い
第2章 Spring Core(DI×AOP)
-
DIコンテナメリット
- インスタンスのスコープを制御できる
- インスタンスのライフサイクルを制御できる
- 共通機能を組み込める
- コンポーネント間が疎結合になるため、単体テストがしやすい
-
SpringFramework以外の有名なDIコンテナのフレームワーク
- CDI(Contexts&DependencyInjection) :JavaEE6で導入されたコンテキストに対応したDIの仕様
- Google Guice
- Dagger
-
ApplicationContextとBean定義
- SpringFrameworkではApplicationContextがDIコンテナの役割を担う
- DIコンテナに登録するコンポーネントのことを「Bean」
- Configurationのことを「Bean定義」
- DIコンテナからBeanを取得することを「ルックアップ」という
- SpringFrameworkではApplicationContextがDIコンテナの役割を担う
UserService userService = context.getBean(UserService.class);UserService userService = context.getBean("userService",UserService.class);UserService userService = (UserService)context.getBean("userService");-
代表的なBean定義の方法
- JavaベースConfiguration
- XMLベースConfiguration
- アノテーションベースConfiguration
-
それぞれ1つだけを使用して実装もできるが、通常は
- JavaベースConfigurationとアノテーションベースConfigurationの組み合わせ、または
- XMLベースConfigurationとアノテーションベースConfigurationの組み合わせを用いてBean定義を行う
-
Configuration方法
-
javaベース、XMLベース、アノテーションベースの説明
-
インジェクションの種類
- セッターインジェクション
- コンストラクタインジェクション
- フィールドインジェクション
-
オートワイヤリング
@Beanメソッドや<bean>要素で明示的にBean定義しなくても自動的にDIコンテナにインジェクションさせる仕組み- 解決方法は2つ
- 型によるもの(by Type)
- 名前によるもの(by Name)
-
型による解決
- デフォルトでインジェクションされる事が必須
- 対象の型を持つ
Beanが1つも登録されていないとorg.springframework.beans.factory.NoSuchBeanDefinitionExceptionが発生
required = false を指定するか
-
Spring4からは、
required = falseの代わりに、JavaSE8から導入された、java.util.Optionalを使用することが出来る- MEMO:
Optional<T>で実装できる!絶対こっちのほうがいい 関数型プログラミングの本で知った。
- MEMO:
-
名前による解決
- 同じ親クラスを持つ2つの実装クラスに
Bean定義をして、インジェクションする場合 @Qualifierで名前を指定してあげないといけない@Primaryを使えば、名前を指定しなかった時にインジェクションされるデフォルトを指定できる- 名前には、実装クラス名を記載するべきではない。呼び出し側で実装を特定してしまうとDIの意味がなくなる。用途名を指定するべき。
- 用途は文字列ではなく、アノテーションでも指定できる。
- 同じ親クラスを持つ2つの実装クラスに
-
用途を指定するためのアノテーション作成してみたい
-
独自アノテーションを作成してみる
-
名前によるオートワイヤリング
@Resourceを付与- フィールド名がBean名に一致するパターン
- プロパティ名がBean名に一致するパターン
-
コレクションやmap型によるオートワイヤリング
2.1.6 コンポーネントスキャン
-
デフォルトのコンポーネントスキャン
- 以下のアノテーションが付いたクラスがDIコンテナに登録される
@org.springfrwamework.stereotype.Component@org.springfrwamework.stereotype.Controller@org.springfrwamework.stereotype.Service@org.springfrwamework.stereotype.Repository@org.springfrwamework.context.annotation.Configuration@org.springfrwamework.web.bind.annotation.RestController@org.springfrwamework.web.bind.annotation.ControllerAdvice@javax.annotation.ManagedBean@javax.inject.Named
- 以下のアノテーションが付いたクラスがDIコンテナに登録される
-
コンポーネントスキャンは広範囲な程処理が遅くなるので不適切
@ComponentScan(basePackages = "com")@ComponentScan(basePackages = "com.example")
-
対象のアプリケーションのトップレベル、あるいはもう1階層下をスキャン対象にすべき
@ComponentScan(basePackages = "com.example.demo")@ComponentScan(basePackages = "com.example.demo.app")
-
value属性は basePackages属性の別名であり、どちらを使用しても構いません。この属性を省略した場合、コンフィグレーションクラスと同じパッケージ配下をスキャンすることに注意する事
-
スキャン対象のアノテーションとしては以下の4種類がよく使われる
| アノテーション | 説明 |
|---|---|
| @Controller | MVCパターンのC(Controller)の役割を担うコンポーネントであることを示すアノテーション。このアノテーションを付与したコンポーネントでは、クライアントからのリクエストとクライアントへのレスポンスに関わる処理を実装する。ビジネスロジックは、@Serviceを付与したコンポーネントで行う |
| @Service | ビジネスロジック(ビジネスルール)を提供するコンポーネントであることを示すアノテーション。このアノテーションを付与したコンポーネントでは、ビジネスルールが関わる処理を実装する。データの永続化に関わる処理は@Repositoryを付与したコンポーネントで行う。 |
| @Repository | データの永続化に悪化÷処理を提供するコンポーネントであることを示すアノテーション。このアノテーションを付与したコンポーネントでは、ORM(Object-Relational Mapper)などの永続化ライブラリ等を使用して、データのCRUD処理を実装する |
| @Component | 上記3に当てはまらないコンポーネント(ユーティリティクラスやサポートクラスなど)に付与するアノテーション |
-
フィルタを明示したコンポーネントスキャン
- アノテーションによるフィルタ
- 代入可能な型によるフィルタ
- 正規表現によるフィルタ
- AspectJパターンによるフィルタ
-
フィルタを駆使するパターンについてメリットがあまり分からないため飛ばす.. あとで調べる
2.1.7 Beanのスコープ
- DIコンテナを使用するメリット
- Beanのスコープ(生存期間)の管理をコンテナに任せる事が出来ること
| スコープ | 説明 |
|---|---|
| singleton | DIコンテナの起動時にBeanのインスタンスを生成し、同一のインスタンスを共有して利用する。デフォルトの設定であり、スコープを設定しない場合はsingletonとして扱われる |
| prototype | |
| session | |
| request | |
| globalSession | |
| application | |
| カスタムスコープ(独自の命名) |
- 覚書
@Beanアノテーションの使いどころ@Configurationクラスの中に定義しているメソッドに付与して使う- DIコンテナに登録され、デフォルトではsingletonで参照出来る
2.1.8 Beanのライフサイクル
- 初期化フェーズ
- 利用フェーズ
- 終了フェーズ
2.1.9 Configurationの分割
-
DIコンテナで管理するBeanが多くなるとConfigurationも肥大化してしまう
-
Configurationの範囲を明確にし、可読性を上げるために、必要に応じてConfigurationの分割を行う
-
@importを使えば複数のConfigurationクラスに分割ができる- MEMO: importでまとめなくても、
@Configurationでコンポーネントスキャンされるので関係ないと思う - Contextから取得してわざわざアクセスしていた際に有用な方法だったと考える
- MEMO: importでまとめなくても、
2.1.10 Configurationのプロファイル化
-
Springでは異なる環境や目的ごとにConfigurationをグループ化することができる
-
このグループを「プロファイル」といいます。
-
例えば、環境ごとに「developmentプロファイル」「testプロファイル」などを作成することが考えられる
@Profileアノテーションを使用して指定する
-
使用するプロファイルの選択方法
- JVM引数
-Dspring.profiles.acrive=production - 環境変数
export SPRING_PROFILES_ACTIVE=production - WEB.xmlに記載することもできる(割愛)
application.ymlに指定することもできる(割愛)
- JVM引数
-
MEMO: 環境ごとの情報を定義するためのJavaクラスにアノテーションを付与して実現するのは微妙だと思うので、
application.ymlで管理するのが良いだろうと思う
2.1.11 JSR 330: Dependency Injection for Java
- Springで、Java標準のJSR330で定められたAPI(主にアノテーション)を使用することができる
| Spring | JSR | 説明 |
|---|---|---|
@Autowired | @Inject | @Injectには必須チェック(required属性)がない |
@Component | @Named | Springの場合はデフォルトでSingletonスコープであるが、JSR330の場合はデフォルトでprototypeスコープである |
@Qualifier | @Named | @Namedが兼用される |
@Scope | @Scope | JSR330の@Scopeはスコープを定義するカスタムアノテーションを作るためのメタアノテーション |
- Springを使うのであれば、特にこだわりがなければ、Springのアノテーションを使うのがいいでしょう
2.2 AOP
-
ロギング処理、キャッシュ処理など本質的ではない処理がいろいろなロジック中に散在するようになる
-
複数のモジュールにまたがって存在する処理は、「横断的関心事(Cross-Cutting Concern)」と呼ばれる
-
代表的なものとしては以下
- セキュリティ
- ログ出力
- トランザクション
- モニタリング
- キャッシュ
- 例外ハンドリング
-
プログラムの中から横断的関心事を取り除き、一箇所に集めることを「横断的関心事の分離(Separation Of Cross-Cutting Concerns)」と呼び
-
これを実現する手法をアスペクト指向プログラミングといいます
2.2.1 AOPの概要
-
AOPはDIと並ぶSpringFrameworkの重要な機能
-
AOPのコンセプト
- Aspect
- AOPの単位となる横断的な関心事を示すモジュールそのもの
- Join Point
- 横断的な関心事を実行するポイント(メソッド実行時や例外スロー時など)
- Join PointはAOPライブラリーによって使用が決められている
- SpringのAOPでは、Join Pointはメソッドの実行時
- Advice
- 特定のJoin Pointで実行されるコードのことで、横断的な関心事を実装する箇所
- Adviceには、Around,Before,Afterなど複数の種類が存在する
- PointCut
- 実行対象のJoin Pointを選択する表現式のこと
- SpringAOPではBean定義ファイルやアノテーションを利用してPointCutを定義
- Weaving
- アプリケーションコードの適切なポイントにAspectを入れ込む処理のこと
- AOPライブラリにはWeavingをコンパイル時に行うもの、クラスロード時に行うもの、実行時に行うものがあり、SpringAOPは実行時にWeavingを行う
- Target
- AOP処理によって、処理フローが変更されたオブジェクトのこと
- TargetオブジェクトはAdvisedオブジェクトと呼ばれることもある
- Aspect
2.2.2 Spring AOP
- SpringAOP は現場で広く使われているAOPフレームワークであるAspectJを利用している
2.2.3 Adviceの実装方法
- Before
- After Returning
- After Throwing など記載していく
- 括弧にPointCut式を記載する
2.2.4 XMLでAdviceの実装
XMLに記載する方法について
2.2.5 Pointcut式
Joint Pointを指すPointCutとしてexecution(* *..*ServiceImple.*(..))という式を使ってきたがその打ち合わけを記載
↓メソッド@After(execution(* com.example.domain.*Service.find*(..))) ↑戻り値 ↑パッケージ ↑型、クラス ↑引数-
PointCut式で利用可能なワイルドカード
*..+
-
名前付きポイントカットの書き方
2.2.6 Springプロジェクトで利用されているAOP
- トランザクション管理処理:メソッドに
@Transactionアノテーションを付与 - 認可処理:
@PreAuthorizeアノテーションを付与 - キャッシュ処理:
@Cacheable("key")ですでにキャッシュがある場合はメソッドを実行せずにキャッシュされた値を返す - 非同期処理:
@Asyncをメソッドに付与、指定された戻り値(CompletableFuture<Result>)を指定することで非同期実行ができる - リトライ処理:
@Retryable(maxAttempts=3)をメソッドに付与。信頼性をコントロールできない外部接続先の呼び出しなどで有用
2.3 データバインディングと型変換
- 本来であれば、
HttpServletRequestクラスからパラメータをgetしてBeanに1つ1つ設定しなければ行けないが、 - 型変換しなければ行けない場面もあり面倒でミスが起きやすい
- Springのデータバインディングを利用するとこれらの問題を解消できる
2.3.1 Springのデータバインディング
EmployeeForm form = new EmployeeForm();ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(form);dataBinder.bind(request);- これを記載することで、3行でデータバインディングができる
- また、SpringMVCの機能を使えば1行で済む (恐らく、
@ModelAttributeを付与することを言っているのだと思う)
2.3.2 Springの型変換
- 型変換を行うための仕組みとして以下の3つを提供している
- PropertyEditor
- Type Conversion
- Field Formatting
2.4 プロパティ管理
-
ハードコーディングを避けるためにプロパティから値を読み込む仕組みがある
-
Strutsを使用していたときはプロパティファイルの値取得はResourceBundleでUtilクラス経由で取得していたのに対して、
-
SpringのDIであれば、
@Value(xxxx)を引数やフィールドに指定するだけで取得できる -
MEMO: これは必ず使用する
- :で区切って記載すればデフォルト値も指定できる!
@Value(xxxx:5)などのように
2.5 Spring Expression Language(SpEL)
-
Spring Expression Language (SpEL)は、SpringFrameworkが提供しているExpression Language
-
MEMO: 使用しないことにする
2.5.1 SpELのセットアップ
- pom.xmlの例
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId></dependency>2.5.2 SpEl APIの利用
- 直接APIを使用するケースはないと思うが、SpELの仕組みを理解するために使用方法を説明
2.5.3 Bean定義でのSpELの利用
-
SpELで定義した値をコンストラクタの引数に渡す方法の説明
-
アノテーションの場合は、
@Value("#{T(java.util.UUID).randomUUID().toString()}") String idのような形で定義する -
SpELは様々なアノテーションの中で利用することができる
@EventListener@TransactionalEventListener@Cacheable@CachePut- TODO:後で調べる
2.5.4 SpELで使用可能な式の表現
-
リテラル値
-
オブエジェクトの生成
-
プロパティへの参照
-
メソッドの呼び出し
-
型の解決
-
変数の参照
-
Beanの参照
-
演算子
-
テンプレート
-
コレクションの操作
-
MEMO:テンプレートエンジン使うなら、SpELいらないのは説
2.6 リソースの抽象化
- アプリケーションは様々なリソースにアクセスする必要がある
- これらのリソースが格納されている場所は
- ファイルシステム上のディレクトリ
- クラスパス上のディレクトリ
- サーブレットコンテナ上のwarファイル
- jarファイル
- 別のWebサーバー
- など多岐に渡る
2.6.1 Resourceインターフェースと実装クラス
-
Resourceインターフェース実装クラス
- ClassPathResource
- FileSystemResource
- PathResource
- UrlResource
- ServletContextResource
-
TODO: Resourceインターフェースを実装しているクラスをいくつか紹介してくれているが、
- 実際にこれらのクラスを使ってみる必要あり
2.6.2 ResourceLoaderインターフェース
- MEMO: ResourceLoaderインターフェースと上記のつながりが理解できてないので再確認
- ResourceLoader経由のほうがよい?
- 上記の実装クラスを使わなくて済むならこっちのほうがよいと考える
2.6.3 Resourceインターフェースを利用したリソースアクセス
- TODO: 実際に使ってみる必要あり
2.6.4 XMLファイル上でのリソースの指定
-
割愛
-
MEMO: リソース取得先の指定はプロパティファイルにまとめるべき!
- すべて
@Value()で記載すればよい
- すべて
2.7 メッセージ管理
- 説明文や項目名などの固定文言
- 処理結果に通知するメッセージ
- エラーメッセージなどを表示する際に
- プロパティファイルなどの外部定義から取得することが求められるケースも多いはず
- メッセージの外部化のメリット
- 多言語サポートする要件を満たす
- 一箇所で一元管理する
2.7.1 MessageSourceインターフェースと実装クラス
-
MessageSource
- メッセージの格納先を抽象化するためのインターフェース
-
MessageSourceResolvable
- メッセージ解決に必要な値(code,args,defaultMessage)を保持していることを示すインターフェース
-
MessageSourceの実装クラス
- ResourceBundleMessageSource
- ReloadableResourceBundleMessageSource
2.7.2 MessageSourceの利用
- MessageSourceのBean定義
@Beanpublic MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBeannames("messages"); // クラスパス上に格納されているプロパティファイル(拡張子は除く)を指定する return messageSource;}-
メッセージの定義ファイルを作成
-
MessageSourceのAPI利用
- DIコンテナに登録したMessageSourceをインジェクションして
getMessageメソッドを呼び出す
- DIコンテナに登録したMessageSourceをインジェクションして
-
MEMO: 感じたこと
- 標準のクラスをDIコンテナで管理したい場合
@Beanをメソッドにつけているのかなと思った - 独自のクラスを作るのであれば、
@Componentでよいのではと
- 標準のクラスをDIコンテナで管理したい場合
-
MessageSourceResolvableの利用
- メッセージ引数もメッセージ定義で管理したい場合に利用するみたい
第3章 データアクセス(Tx、JDBC)
- データアクセス機能について解説
- JDBC関連の機能
- トランザクション管理機能
- データアクセスエラーのハンドリング機能
- その他ライブラリの機能(JPA、Hivernate、MyBatisといったORM)は以降の章で解説
3.1 Springによるデータアクセス
- まずはSpringが扱うことができるデータソースの種類について
3.1.1 データソースについて
データソースは、データベースにアクセスするためのコネクションをアプリケーションに提供する役割を担う
-
Springが提供するデータベースアクセス機能では、以下に示す3つのデータソースを利用することができる
- アプリケーション内に定義したデータソース
- アプリ内にユーザや接続情報などを記載するパターン
- アプリケーションサーバーに定義したデータソース
- APサーバーに定義されたデータソースを利用するパターン
- MEMO: JavaEE徹底入門ではこのパターンでサンプル実装されていた
- APサーバーに定義されたデータソースを利用するパターン
- 組み込みデータベースのデータソース
- HSQLDB、H2、Apache Derbyといった組み込みデータベースをデータソースとして利用する
- アプリケーション内に定義したデータソース
-
MEMO: Commons DBCP はコネクションプール機能付きのデータソースを提供するライブラリー
- 実際に定義を作成していく際に、コネクションプール設定方法について確認しておく
3.1.2 データソースのコンフィギュレーション
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.3.RELEASE</version></dependency>-
アプリケーション定義のデータソース
- アノテーション定義の例(詳細略)
- XML定義の例(詳細略)
-
アプリケーションサーバー定義のデータソース
- Jndiを指定して参照する(詳細略)
-
組み込みデータソース
- (詳細略)
-
TODO: 実際に上記の3種類で接続をしてみる必要がある
3.2 Spring JDBC
- 前節ではSpring JDBCを使用するために必要となるデータソースのBean定義方法を紹介した
- データアクセス処理を記述するための中心的な役割を持つJdbcTempleteクラスの使用方法を紹介
- SQLを実行する方法
- SQLへ値をバインドする方法
- SQLの実行結果からデータを取得する方法
3.2.1 Spring JDBCとは
SQLの内容にかかわらす共通に行われる定型的なJDBCの処理をSpringが代替する機能
- 定型的な処理
- コネクションのオープンやクローズ
- SQLステートメントの実行
- 処理結果行の繰り返し処理
- 例外ハンドリング
- SpringJDBCを利用することで、実装範囲を以下のような重要な処理に限定できる
- SQLの定義
- パラメータの設定
- ResultSetの取得結果において、各レコードに対して実行したい処理
3.2.2 JdbcTempleteクラスを利用したCRUD操作
- JdbcTempleteクラスを用いたCRUD操作の説明
- MEMO: ORMを使用したほうが効率が良さそう
- 念の為読んでおく程度で、覚えておかなくても良いと考える
3.2.3 取得結果の変換処理
-
SpringJDBCでは取得結果を変換できる3つのインターフェースを用意している
RowMapper- ResultSetの1行を特定のPOJOインスタンスに変換する
ResultSetExtractor- 複数行から1つのインスタンスを生成できる
RowCallbackHandler- ResultSetを参照してなんらかの処理を行うためのインターフェース
- 戻り値を返さない。取得結果のファイル出力や、データのチェックなどを行う場合に利用します。
-
RowMapperの実装方法
- RowMapperインターフェースを実装したクラスにメソッドを作成して使用する
- BeanPropertyRowMapperクラスを利用することもできる
- 制約はありルールに沿ってBean定義すればかんたんにResultSetを設定可能
-
ResultSetExtractorの実装
- 割愛
-
RowCallbackHandlerの実装
- 割愛
3.2.4 応用的なCRUD操作
- SQLのバッチ実行
batchUpdate()メソッドを利用する
- ストアドプロシージャの呼び出し
JdbcTemplateのcallメソッドやexecuteメソッドを利用することで呼び出すことができる- TODO: ストアド・プロシージャ使ってみる。使ったことないので。。
3.3 トランザクション管理
- アノテーションを用いたトランザクション管理方法
- プログラム内に直接commitメソッドやrollbackメソッドを記述する明示的なトランザクション管理について説明
3.3.1 トランザクションマネージャ
-
PlatformTransactionManager: Springのトランザクション管理の中心となるインターフェース -
トランザクションマネージャの定義
PlatformTransactionManagerのBeanを定義する- トランザクション対象とするメソッドを定義する
-
ローカルトランザクションを利用する場合
- 単一データベースに対する複数の操作
- XMLにTransactionManagerの定義を記載する方法を紹介
- BeanIDは
transactionManagerを指定することを推奨
-
グローバルトランザクションを利用する場合
- 異なるデータベースに対する複数の操作
- グローバルトランザクションの仕組みはJTAというJavaEEの仕様として標準化されておりアプリケーション・サーバーからJTAの仕組みが提供されている
- 実装クラスとして
JtaTransactionManagerを使用する - ただし、製品ごとの
JtaTransactionManagerが提供されているのでそちらを使う 自動的に最適なクラスを使う仕組みが用意されているらしい
3.3.2 宣言的トランザクション
-
宣言的トランザクションとは
- 事前に宣言されたルールに従い、トランザクションを制御する方法のこと
- メリット
- トランザクションの開始やコミット、ロールバックなどの典型的な処理をビジネスロジックの中に記述する必要がなくなる
- 利用方法
@Transactional- XMLコンフィギュレーション
-
@Transactionalを利用した宣言的トランザクション -
トランザクション制御で必要となる情報
- アノテーションの引数について説明(割愛)
- クラスに付与するかメソッドに付与するか
-
コンフィギュレーションクラスに定義する方法も紹介している
-
XMLコンフィギュレーションによる宣言的トランザクション
- 割愛
3.3.3 明示的トランザクション
-
明示的トランザクションとは
- コミットやロールバックといったトランザクション制御に関する処理をソースコードに明示的に記述する方法
-
PlatformTransactionManagerを利用した明示的トランザクション制御- commit(),rollback()などを直接書く
-
TransactionTempleteを利用した明示的トランザクション制御- @ConfigurationクラスにBean定義する際にトランザクションを設定
-
MEMO: 明示的トランザクションはどちらも使いにくいような印象
3.3.4 トランザクションの分離レベルと伝播レベル
- TODO: ★見返して実装に組み込む
- トランザクション分離レベル
- TODO: トランザクション分離レベルについて復習すべし
- トランザクション伝播レベル
- MEMO: 伝播レベルについて意識したことはなかった
- トランザクション処理が入れ子になったときに初めて意識することになる。
- 通常の処理の場合はデフォルトの
Requiredで問題ないような気がする。 - TODO: 逆に入れ子にするパターンってどのような要件が考えられるか確認する
- →本書に解説があった。
- 業務ロジックのトランザクションとは別に、ログ出力用の処理でDBアクセスがある場合
- 業務ロジックでロールバックが発生すると、ログ出力用のデータもロールバックされてしまう。
- こうならないように、ログ出力用のトランザクションは
REQUIRES_NEWで生成しておく必要があるとのこと - そもそもDBにログを貯める仕様が良くないのではと思う
- →本書に解説があった。
3.4 データアクセスエラーのハンドリング
- データアクセス処理でエラーが発生した際のハンドリング処理を実装しておく必要がある
- Springにおけるデータアクセス例外の抽象化の考えかたを理解した上で、エラーハンドリング処理の実装方法や抽象化のカスタマイズ方法を紹介
3.4.1 Springが提供するデータアクセス例外
-
DataAccessExceptionを親クラスとするデータアクセス例外の階層構造
- MEMO: かなり多くの種類のエラーが、
DataAccessExceptionから切られていることを確認した
- MEMO: かなり多くの種類のエラーが、
-
非検査例外によるDataAccessExceptionの実装
- DataAccessExceptionはRuntimeExceptionが親クラスなので、例外ハンドリングが強制されてませんよという説明
-
実装を隠蔽したデータアクセス例外
- DBごとに例外コードが異なるが、Springのデータアクセス機能で共通の例外クラスに変換している
- MEMO: これは画期的ですごい便利だと感じた→当たり前でもあるか..
3.4.2 データアクセス例外のハンドリング
-
非検査例外のため、ハンドリングを行いたい場所だけcatchする、行いたくない場所は何もする必要が無い
-
データアクセス例外のハンドリングを行う実装例
- Springが提供している例外でcatchできるようにtry-catchで囲む
- catch後は、プロジェクトの例外を再throwする
3.4.3 データアクセス例外の変換ルールのカスタマイズ
- 各データベースのエラーコードとデータアクセス例外の対応はspring-jdbc-xxx.jarに含まれるsql-error-codes.xmlに定義されているが、クラスパス直下にsql-error-codes.xmlを配置することでこの定義をカスタマイズすることができる
第4章 Spring MVC
-
第三章までで得た知識で開発できるのは、データベースにアクセスするスタンドアロンアプリケーション
-
本章から7章までにかけて、SpringMVCの機能を利用したWebアプリケーションの開発方法について解説
-
本章
- SpringMVCの特徴を簡単に説明
- シンプルなサンプルアプリケーションを作成しながらSpringMVCの基礎を学ぶ
- SpringMVCのアーキテクチャについて説明
4.1 Spring MVC とは
- フレームワークのアーキテクチャとしてMVCパターンを採用している
- TODO: MVC以外のアーキテクチャについて理解を深める
- Springは正確に言うと、フロントコントローラパターンを採用しているらしい詳細は4.3SpringMVCのアーキテクチャで解説
4.1.1 Webアプリケーション開発における特徴
-
SpringMVCはWebアプリケーションをストレスなく快適に開発することができるフレームワークで、次のような特徴がある
- POJO(Plain Old Java Object)での実装
- フレームワーク独自のインターフェースを実装する必要が無いため、作成するクラスの単体テストのテスタビリティを確保
- アノテーションを使用した定義情報の指定
- 柔軟なメソッドシグネチャの定義
- Controllerに渡す引数や戻り値も様々な形がサポートされている
- ServletAPIの抽象化
- ServletAPI(HttpServletRequest,HttpServletResponse,HttpSessionなどのAPI)を抽象化する仕組みを提供
- テスタビリティを確保
- Viewの実装技術の抽象化
- コントローラはView名(Viewの論理名)を返却し、SpringMVCのフレームワーク処理が呼び出すViewを決定
- ControllerはViewの実装技術(Thymeleaf,ServletAPI,FreeMarkerなど)を意識する必要がなくなる
- SpringのDIコンテナとの連携
- SpringMVCはSpringのDIコンテナ上で動作するフレームワーク
- DIやAOPなどの仕組みを活用できる
- POJO(Plain Old Java Object)での実装
-
MEMO: この辺は開発で感触をつかめているのですんなり理解できた
4.1.2 MVCフレームワークとしての特徴
-
豊富な拡張ポイントの提供
- 処理の役割に応じてインターフェースを定義している
-
エンタープライズアプリケーション向けの機能の提供
- メッセージ管理
- セッション管理
- 国際化
- ファイルアップロードといったエンタープライズアプリケーション向けのWebアプリケーションを開発する際に必要となる機能も提供
-
サードパーティのライブラリとの連携部品の提供
- ★Jackson(JSON/XML操作) → JSON操作は他に、org.jsonがあるみたいだが、Jacksonの方が良さそう
- Apache Tiles(レイアウトエンジン) → レイアウトを組める。Thymeleafのincludeで十分なので使わない
- FreeMarker(テンプレートエンジン) → 変数を持つ定型文言を作成しておき(*.ftlファイル)
- ★Rome(RSS/Feed操作) → 他に、Informaというのがあるらしい。
- ★JsperReports(帳票出力) → 他にも色々なライブラリがあるみたいだが、一旦これを使用したい
- ★Apache POI(Excel操作)
- ★Hibernate Validator(Bean Validation)
- Joda Time(日付操作) → Java7時代の日付操作
- など
- サードパーティ自体がSpringMVCとの連携部品を提供しているケースもある。
- Thymeleaf(テンプレートエンジン)
- ★HDIV(セキュリティ強化)
-
TODO: ★つけたライブラリーは使用したい
4.2 はじめてのSpring MVCアプリケーション
4.2.1 開発プロジェクトの作成
-
InteliJ IDEA でプロジェクトを作成することにした
-
mavenプロジェクトをstartarのアーキタイプで作成
-
pom.xmlをサンプルアプリに合わせる
- mavenがうまく動かない不具合発生
- 原因: urlがmavenレポジトリを向いていなかったため
- 結果: 解消しなかった
-
Intelijだとうまくいかないと判断し、Eclipseで実施
-
Eclipseでサンプルプログラムを配置
-
mavenの設定をサンプルに合わせる
-
javaeeのモジュールがデフォルトで読み込めなくなったみたいなので、pomに定義を入れる
-
tomcat9サーバーを立ち上げる
-
context-pathはデフォルトでプロジェクト名?になるみたいなので、
http://localhost:8080/firstapp4-2でアクセスするとうまく行った -
Web.xmlに
jsp-configというものを定義すると、すべてのjspに対してデフォルトでincludeさせることができるみたい -
TODO: spring bootで生成したプロジェクトだと、web.xmlが必要ない? そのあたりの仕組みが理解できていない
4.2.2 Spring MVCの適用
4.2.3 トップ画面表示処理の実装
4.2.4 入力画面表示処理の実装
4.2.5 送信処理の実装
4.2.6 入力チェック処理の実装
-
ここまででサンプルアプリケーションの説明終了
- Controllerクラス
- フォームクラス
- View(JSPなどのテンプレートファイル)
-
本来であれば、JSPではなくThymeleafとかを使うべきだと思うので、詳細な実装はサラッと流し読み
4.2.7 XMLファイルを使用したBean定義
- 割愛
4.3 Spring MVCのアーキテクチャ
4.3.1 フレームワークのアーキテクチャ
-
Spring MVCは「フロントコントローラパターン」と呼ばれるアーキテクチャを採用している
- クライアントからのリクエストをフロントコントローラと呼ばれるコンポーネントが受け取り、リクエストの内容に応じて実行するHandler(Controller)を選択するアーキテクチャ
-
フロントコントローラが担う処理
- クライアントからのリクエストの受付
- リクエストデータのJavaオブジェクトへの変換
- 入力チェックの実行(Bean Validation)
- Handlerの呼び出し
- Viewの解決
- クライアントへのレスポンスデータの応答
- 例外ハンドリング
-
TODO: Springフレームワークの全体を把握したときにもう一度見直す
第5章 Webアプリケーションの開発
5.1 Webアプリケーションの種類
-
SpringMVCは大きく分けて以下の2種類のアプリケーションを作成するための機能を提供
- 画面を応答するアプリケーション
- データのみを応答するアプリケーション(RESTful Webサービス)
-
メモ
- SpringMVCの仕組みは使用していないが、Spring4.0よりWebSocketの連携モジュールが提供されている
- フロントとサーバーサイドの双方向通信用
- TODO: 使ってみる
-
Hamdlerメソッド作成
@RequestMapping()は使用せずに、@GetMapping()を使用する
-
Handlerメソッドの引数に指定可能な型、アノテーションを紹介
- TODO: 再度確認する
-
暗黙的な引数の解決!覚えておく
- 引数の型がStringやIntegerといったシンプル型の場合、引数名に一致するリクエストパラメータの値を取得
- 引数の方がJavaBeansだった場合、デフォルトの属性名に一致するオブジェクトをModelから取得する
- 該当するオブジェクトがModelに存在しない場合、デフォルトコンストラクタを呼び出して新しいオブジェクトを生成
-
ServletAPI(HttpServletRequest,HttpServletResponse,HttpSession,Partなど)や低レベルのJavaAPI(InputStream,Reader,OutputStream, Writer,Map)なども指定できるが、これらのAPIを自由に使うとメンテナンス性を低下させる可能性があるので、使用しないようにする必要がある
-
TODO: コーディング規約として利用を制限していくべき!
5.3.5 Handlerメソッドの戻り値
-
Handlerメソッドは戻り値として様々なオブジェクトを返却できる
-
メモ
- 返却できるオブジェクトは
org.springframework.web.method.support.HandlerMethodReturnValueHandlerインターフェースの実装クラスを作成することで拡張できる
- 返却できるオブジェクトは
-
SpringMVCがサポートしている主な型
java.lang.String- Model
- ModelAndView
- void
- ResponseEntity<?>
- HttpHeaders
5.3.6 View Controllerの利用
- Viewを呼び出すだけであれば、SpringMVCが提供しているViewControllerの仕組みを利用することができる
5.4 リクエストマッピング
-
@RequestMappingの属性値を使ってリクエストマッピングの条件を指定する -
指定可能な属性
- value
- path
- method
- params
- headers: リクエストヘッダー
- consumes: Content-Typeヘッダー
- produces: Acceptヘッダー
- name
-
value,pathは複数指定することができる
- or条件として扱われる
-
パスパターンの使用
- URIテンプレート形式のパスパターン
- 正規表現も使える
- Antスタイルのパスパターン
-
paramsはメソッドが実行される条件としてパラメータの有無も入る
5.5 リクエストデータの取得
- 種類
@PathVariable@RequestParam@RequestHeader- リクエストパラメータ値の一括取得
5.5.4 コンパイルオプションの注意点
@PathVariable,@RequestParam,@RequestHeader,@CookieValueのvalue属性を省略する場合、-gオプションまたはJavaSE8から追加された-parametersオプションのどちらかのコンパイルオプションを有効にしておく必要がある- TODO: バインドされないとき確認
5.5.7 アノテーションを使用したフォーマットの指定
-
@org.springframework.format.annotation.DateTimeFormat -
@org.springframework.format.annotation.NumberFormat -
JSR354 : Money and Currency APIというものがあるらしい
- TODO: 後で確認する
5.6 フォームクラスの実装
5.6.1 フォームオブジェクトのスコープ
-
スコープの種類
- リクエストスコープ
- フラッシュスコープ:PRGパターンのリクエスト間でオブジェクトを共有するためのスコープ
- セッションスコープ:HttpSessionに格納され、明示的に破棄するまで残り続ける
-
フラッシュスコープ
RedirectAttributesのaddFlashAttribute()メソッドを使用して詰める
@RequestMapping(path = "create", method = RequestMethod.POST)public String create( @Validated AccountCreateForm form, BindingResult result, RedirectAttributes redirectAttributes) { redirectAttributes.addFlashAttribute(form); return "redirect:/account/create?complete"; }5.6.2 フォームクラスの作成
@Datapublic class LoginFormRequestDto implements Serializable { private static final long serialVersionUID = 1L; private String id; private String password;}- Serializableインターフェスを実装しておく。これが必要なのはオブジェクトをセッションスコープで管理する場合だが、スコープに関係なく定義しておくのが無難
5.7 入力チェック
- SpringMVCではBeanValidationの仕組みを利用して、リクエストパラメータ値がバインドされたフォームクラス(またはコマンドクラス)に対して入力チェックを行う
5.7.1 入力チェックの有効化
- 入力チェックを行う場合、
- 入力チェックを行うメソッドの引数にフォームクラスを定義して、
@org.springframework.validation.annotation.Validatedまたは@org.springframework.validation.annotation.Validを指定する@Validatedを使用すると、BeanValidationのバリデーショングループの仕組みが使用できるらしい
- 入力チェックを行うメソッドの引数にフォームクラスを定義して、
5.7.2 入力チェック結果の判定
- BindingResultで処理する
5.7.3 未入力の扱い
-
未入力は許容するが、入力された場合は6文字移譲であること
- という要件をBeanValidation標準アノテーションを使用して満たすことができない
-
この場合は、Springが提供している
org.springframework.beans.propertyeditors.StringTrimerEditorを使用することを検討 -
TODO: 普通に独自アノテーションを作成した方がシンプルなような気がする
5.7.5 ネスト下JavaBeansの入力チェック
-
ネストしたJavaBeansやコレクション内のJavaBeansに定義したプロパティに対して入力チェックを行いたい場合は、
@Validを指定する- チェック対象とすることを明示する必要がある
-
@Validと@Validatedの違い
5.7.6 入力チェックルールの追加
-
独自の入力チェックツール追加方法2つ
- 既成ルールを合成して作成する方法
- 独自のバリデータを実装して作成する方法
-
既成ルールを合成して作成する方法
- →こっちは使った方がよい
import static java.lang.annotation.ElementType.*;import static java.lang.annotation.RetentionPolicy.*;
import java.lang.annotation.Documented;import java.lang.annotation.Retention;import java.lang.annotation.Target;
import javax.validation.Constraint;import javax.validation.Payload;import javax.validation.ReportAsSingleViolation;import javax.validation.constraints.Pattern;
@Documented@Constraint(validatedBy = {})@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })@Retention(RUNTIME)@ReportAsSingleViolation@Pattern(regexp = "[a-zA-Z0-9]*")public @interface AlphaNumeric {
String message() default "{validation.AlphaNumeric.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {};
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) @Documented public @interface List { AlphaNumeric[] value(); }
}-
メモ TODO: 覚えておく
- 上の例では1つしか既成ルールを使用していないが、既成ルールを複数まとめた合成アノテーションを作成することもできる
- なお、既成ルールをまとめた合成アノテーションを作成する場合、
@ReportAsSingleViolationも付与するのが一般的で、付与すると、message属性で指定したメッセージが利用される用になる。付与しない場合は、既成ルールに指定したメッセージが利用される
- なお、既成ルールをまとめた合成アノテーションを作成する場合、
- 上の例では1つしか既成ルールを使用していないが、既成ルールを複数まとめた合成アノテーションを作成することもできる
-
独自のバリデータを実装して作成する方法
- →冗長になりがちだと思うので作成しないほうが良いのでは?
- 相関チェックも実装できそうだが、
@AssertTrueを使用したほうがシンプル
- 相関チェックも実装できそうだが、
- →冗長になりがちだと思うので作成しないほうが良いのでは?
-
@AssertTrueの使用方法
5.7.7 入力チェックツールの切り替え
- コントローラーのメソッドでパラメータ指定して、呼び出すメソッドを変更することで実現する例を記載している
5.7.8 エラー情報の表示
-
<form:errors path="name">- このように記載することでエラー情報取れますよという説明がある
-
エラーメッセージ要素の構成案
<div class="error-message d-none"> <form:errors path="name"></div>document.querySelectorAll(".error-message").forEach(el=>{ if(el.innerText) el.classList.remove("d-none");})- みたいな感じにすればいいのではなかろうかと
5.7.9 エラーメッセージの解決
-
エラーメッセージの定義方法
- Springが提供するMessageSourceで読み込んだプロパティファイルにメッセージを定義する
- BeanValidation管理のプロパティファイルにメッセージを定義する
- 制約アノテーションのmessage属性に直接メッセージを定義する
-
Spring管理プロパティファイル
- 制約アノテーションのクラス名+「.」+フォームオブジェクトの属性名+「.」+プロパティ名
- 制約アノテーションのクラス名+「.」+フォームオブジェクトの属性名
- 制約アノテーションのクラス名+「.」+プロパティ名
- 制約アノテーションのクラス名+「.」+プロパティの型名(FQCN)
- 制約アノテーションのクラス名
-
BeanValidation管理のプロパティファイルにエラーメッセージを定義
- クラスパス直下のValidationMessages.propertiesにメッセージを定義する
- →あまり使用する機会無いのではと思う
- クラスパス直下のValidationMessages.propertiesにメッセージを定義する
5.7.10 BeanValidationのカスタマイズ
- java configに設定
- →あまりメリットが理解できなかったのでスキップ
5.7.11 Spring Validatorの利用
- 割愛
5.8 画面遷移
5.8.1 遷移先の指定方法
- View名をHandlerメソッドの戻り値として返却することで実現
5.8.2 リクエストパスへのリダイレクト
- View名に「redirect: + リダイレクト先のリクエストパス」
- RedirectAttributesを使用してパラメータを設定する
- パス変数を指定することもできる
5.8.3 リクエストパスへのフォワード
- 「forward: + 転送先のリクエストパス」
return "forward:/auth/authenticate";- MEMO: 使い所が知りたい
5.8.4 Viewとのデータ連携
- JavaオブジェクトをModelに格納する方法は、以下の2つがある
- ModelのAPIを直接呼び出す
- ModelAttributeアノテーションを付与したメソッドを用意する
5.8.5 リダイレクト先とのデータ連携
- RedirectAttributesのフラッシュスコープの説明
5.9 Viewの解決
- jspとかいろいろ使えるよ
- JSP使うなら、ViewResolverRegistryに登録する必要があるよっていう説明
5.10 JSPの実装
- JSP使用しないため割愛
5.11 SpringのHTMLフォーム用タグライブラリの利用
- JSP使用しないため割愛
5.12 Springの汎用タグライブラリの利用
- Springで使用できるタグについて説明
- MEMO: 必要であれば読み返す
- できるだけthymeleafだけの方が良いのではと思う
5.13 例外ハンドリング
5.13.1 例外の種類
- Webアプリケーションで発生する例外は大きく3つある
- システム例外:処理を継続することができない例外
- アプリケーション自体のバグ
- 依存ライブラリのバグ
- ミドルウェアやハードウェアの故障
- システムリソースの枯渇
- ネットワーク障害
- リクエスト不正を通知する例外:リクエストの内容が不正なときに発生する例外
- 存在しないパスへのリクエスト
- バインディングエラー
- 入力チェックエラー
- アプリケーション例外:ビジネスルールに違反したときに発生する例外
- ユーザー登録時のIDの重複エラー
- 排他エラー
- 在庫数の不足エラー
- システム例外:処理を継続することができない例外
5.13.2 例外の発生箇所とハンドリング方法
- 以下の箇所で例外が発生する可能性があり、それぞれ例外ハンドリングの方法も異なる
-
- Servlet Fileter
- サーブレットコンテナへのエラーページ機能(web.xmlの
<error-page>要素)を使用してエラー処理を実装する
-
- DispatcherServlet: SpringMVCが提供する例外ハンドリングの仕組み(HandlerExceptionResolver)
-
- アプリケーション(Controller, Service, Repositoryなど):SpringMVCが提供する例外ハンドリングの仕組み(HandlerExceptionResolver)
-
- View(JSPなど)
- Viewの中で発生した例外は、サーブレットコンテナのエラーページ機能を使用して絵r-あ処理を実装
-
5.13.3 サーブレットコンテナのエラーページ機能を利用
web.xmlにerror-pageを記載する方法を紹介
5.13.5 @ExceprtionHandlerメソッドの利用
-
複数のController間で共通の処理
-
@ExceptionHandlerメソッドの引数- Exception
- HandlerMethod
- java.util.Locale
- java.util.Timezone
- java.time.ZoneId
- java.security.Principal
-
戻り値について
- String
- ModelAndView
- void
- ResponseEntity<?>
5.13.6 @ResponseStatusを指定した例外クラスの利用
@ResponseStatus(HttpStatus.NOT_FOUND)public class ResourceNotFoundException extends RuntimeException { // *****}- TODO: 独自Exceptionを作成していくべきかどうか
第6章 RESTful Webサービスの開発
6.1 REST APIのアーキテクチャ
- RESTは「REpresentational State Transfer」の略で、クライアントとサーバー間でデータをやり取りするアプリケーションを構築するためのアーキテクチャスタイルの1つ
- 最も重要なのは「リソース」という概念
- REST APIはデータベースなどで管理している情報の中から、クライアントに提供する情報を「リソース」として抽出
- 抽出したリソースはWeb上に公開し、リソースにアクセスするための手段としてRESTAPIを用意します
6.1.1 Resouce Oriented Architecture (ROA)
6.1.2 フレームワークのアーキテクチャ
- 割愛 TODO: 詳細を知りたくなったら読む
6.2 アプリケーションの設定
6.2.1 ライブラリのセットアップ
- リソース形式としてJSONを使用する際に利用する「FasterXML Jackson Databind」を依存ライブラリに追加する
- MEMO: SpringBootだと不要みたい
6.2.2 サーブレットコンテナの設定
-
HiddenHttpMethodFilterの適用
- RESTAPIを提供する場合、HTTPメソッドとして、PUT、PATCH、DELETEなども使用するが、
- Webブラウザなどクライアントの実装によっては、GETとPOSTしか使用できなかったりする
- そこをサポートするために
org.springframework.web.filter.HiddenHttpMethodFileterクラスを利用する_method=putというパラメータでリクエストが送られると、サーブレットコンテナ内で行われる処理はPUTメソッドでアクセスしたときとおなじになる
-
HttpMessageConverterのカスタマイズ
- MEMO: Converterの設定をしているが、SpringBootだと不要だと思うので飛ばす
6.3 @RestControllerの実装
-
大きく2つ種類がある
- メソッドシグネチャを参照してフロントコントローラが処理を行う「宣言型」の処理
- Controllerクラスのメソッド内に処理を実装する「プログラミング型」の処理
-
MEMO: クライアントへ返却するオブジェクトは共通で作成しているもの(クラス)を使用するのが良いと思った
-
以下、割愛
第7章 Spring MVC の応用
- SpringMVCの機能を利用したWebアプリケーションの開発方法を学びましたが、
- 典型的なWebアプリケーションの開発では、
- セッションの利用
- ファイルアップロード
- 画面やメッセージの国際化
- 共通処理の適用
- 静的リソースのキャッシュ制御 などへの考慮も必要になる
- 非同期処理(SSE(Server-Sent Events))
7.1 HTTPセッションの利用
- セッション管理する方法3つある
- セッション属性(@SessionAttributes)の使用
- セッションスコープのBeanの利用
- HttpSessionのAPIの利用
7.1.1 セッション属性(@SessionAttributes)
- 1つのController内で扱う複数のリクエスト間でデータを共有する場合に有効な方法
- 入力画面が複数のページで構成される場合や、複雑な画面遷移を伴う場合は
@SessionAttributesを使用することを検討
- 入力画面が複数のページで構成される場合や、複雑な画面遷移を伴う場合は
- シンプルな画面構成の場合(入力画面→確認画面→完了画面とかの場合)は、HTMLフォームのHiddenで値を持ち回る方法を検討すること
7.1.2 セッションスコープBean
- 複数のControllerをまたぐ画面遷移において、Controller間でデータを共有する場合に有効な方法
7.2 ファイルアップロード
- SpringMVCでファイルをアップロードする場合は、以下のいずれかの方法を利用する
- Servlet標準のアップロード機能
- ApacheCommonsFileUploadのアップロード機能
7.2.2 ファイルアップロード機能のセットアップ
-
web.xmlに
<multipart-config />を追加するとのこと -
Servlet標準のファイルアップロード機能をデフォルトのまま利用するとアップロードできるファイルのサイズに上限がないため、上限を設けたい場合は、ファイル単位の最大サイズ、アップロード時のリクエスト全体の最大サイズ、一時ファイル出力有無の閾値サイズの3つを指定する必要がある
-
上限に引っかかると、
MultipartExceptionが発生するので、Handlerでハンドリングすること -
メモ
- SpringMVCのDispatcherServletより前にリクエストパラメータにアクセスする処理があると、MultipartExceptionが発生しない可能性がある
- SpringWEBから提供されているフィルターを利用すると制御できるとのこと
- TODO: SpringBootだとどのように記載するか確認
7.2.3 アップロードデータの取得
-
Formクラスの作成
- 普通にFormクラスの作成
MultipartFile型で変数定義する
- 普通にFormクラスの作成
-
Viewの作成
- input type=“file”で作成して送るだけ
-
Controller
- ファイルを取得して永続化操作
-
Validation
- ファイルサイズや、コンテンツタイプ、ファイル名などをチェックする場合は、Validatorを作成してチェック
-
TODO: 実際にアイコン画像などをDBで保持できる永続化ロジックまで書く
7.3 非同期リクエスト
7.3.1 非同期リクエストの仕組み
-
非同期実行が終了してからHTTPレスポンスを開始
- 勘違いしやすいのが、HTTPレスポンスは非同期実行している処理が終了したあとに行うため、クライアント側から見ると、同期処理と同じ動作になる
-
SpringMVCはこのパターンの非同期処理をサポートするために以下の2つの方法を提供
- SpringMVC管理のスレッドを使用した非同期処理
- SpringMVC管理外のスレッドを使用した非同期処理
-
非同期実行の処理中にHTTPレスポンスを開始
- ロングポーリングを使用した非同期処理
- SSE(Server-Sent Events)に準拠した非同期処理
7.3.2 非同期実行を有効にするための設定
- web.xmlに設定追記
- java configにBean定義追加
7.3.3 非同期処理の実装
-
以下の2つの非同期処理の実装方法を紹介
- CompletableFutureを使用した非同期処理
- SseEmitterを使用したPush型の非同期処理
-
@Asyncの利用- 本書で説明する非同期処理は、どちらもSpringMVC管理外のスレッドを使用した非同期処理
- SpringFrameworkは、特定のメソッドを別スレッドで実行する仕組みを提供しており、別スレッドで実行したいメソッドに、
org.springframework.scheduling.annotation.Asyncを付与するだけ
-
MEMO: SpringBootでの利用に参考
-
CompletableFutureを使用した非同期処理の実装
CoompletableFuture<String>を返却する
-
SseEmitterを使用したPush型の非同期処理の実装
new SseEmitter();でイベント処理を行う- 具体的な使用方法について説明なし
7.3.4 非同期実行の例外ハンドリング
DeferredResultを使用して結果を設定する- TODO: 結果を非同期で画面に通知する方法確認
7.3.5 非同期実行に対する共通処理の実装
-
CallableProcessingInterceptorもしくはDeferredResultProcessingInterceptorのAdopterを実装したクラスを作成
-
現在は、interfaceにデフォルトメソッドが定義できるようになったので、Adopterではなくて、interfaceの方を使用すべきとのことで、
@Deprecatedになっている -
CallableProcessingInterceptorインターフェース
7.4 共通処理の実装
ControllerのHandlerメソッドの呼び出し前後に共通処理を実行する方法について説明します。
7.4.1 サーブレットフィルタの利用
-
SpringMVCの呼び出し前後に共通する処理を実行するには、
javax.servlet.Filterインターフェースの実装クラスを作成する -
Filterクラスを直接実装してもよいが、ここではSpringが提供しているサポートクラスを利用する方法を紹介
-
サポートクラス
GenericFilterBeanクラスOncePerRequestFilterクラス
-
DIコンテナで管理しているBeanのインジェクション方法
- サーブレットフィルター内の処理でDIコンテナ管理しているBeanを利用したい場合は、サーブレットフィルタをDIコンテナに登録し、DelegatingFilterProxy経由でサーブレットフィルタの処理を実行する
- DelegatingFilterProxyは、SpringのDIコンテナに登録されているサーブレットフィルターに処理を移譲するサーブレットフィルタクラス
7.4.2 HandlerInterceptorの利用
-
Controllerでハンドリングする処理に対してだけ共通処理を実行したい場合は、
org.springframework.web.servlet.HndlerInterceptorインターフェースの実装クラスを作成する
-
メソッド
- preHandle:実行前
- postHandle:例外時は呼び出されない
- afterCompletion:実行後
@Slf4jpublic class LoggingInterceptor extends HandlerInterceptorAdapter {
public void postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { if (log.isInfoEnabled()) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = ((HandlerMethod) handler).getMethod(); log.info("[SUCCESS CONTROLLER] {}.{}", method.getDeclaringClass().getSimpleName(), method.getName()); }
}} @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoggingInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/resources/**"); }7.4.3 @ControllerAdviceの利用
- Controllerクラスには、Handlerメソッドとは別に、Controller専用の特殊なメソッド(
@InitBinderメソッド、@ModelAttributeメソッド、@ExceptionHandlerメソッド)を実装することができる - これらのメソッドを複数のControllerクラスで共有するには、
@ControllerAdviceを付与したクラスを作成する
@Slf4j@ControllerAdvicepublic class ErrorController {
@GetMapping("/error") public String handleError() { return "error"; }
@ExceptionHandler(Throwable.class) public String handleThrowable(Throwable exception, Model model) { model.addAttribute("", ""); return "error"; }
@ExceptionHandler(Exception.class) public String handleException(Exception exception, Model model) { model.addAttribute("", ""); return "error"; }
@ExceptionHandler(IOException.class) public String handleIOException(IOException exception, Model model) { model.addAttribute("", ""); return "error"; }}7.4.4 HandlerMethodArgumentResolverの利用
- SpringMVCのデフォルトでサポートされていないオブジェクトをControllerのHandlerメソッドの引数に渡したい場合は、
org.springframework.web.method.support.HandlerMethodArgumentResolverインターフェースの実装クラスを作成します。 - あまり利用するシーンがわからないため割愛する
7.5 静的リソース
- ルートから任意のディレクトリに配置した静的リソースにアクセスできますという説明
7.5.1 デフォルトサーブレットとDispatcherServletの共存
- Servletの使用では、ルートパス()にマッピングされたサーブレットのことを「デフォルトサーブレット」と呼び、デフォルトサーブレット経由でWebアプリケーションのドキュメントルート配下のファイルにアクセスすることができる
- SpringMVCアプリケーションでは、DispatcherServletをルートパスにマッピングするスタイルを採用することがよくあるが、DispatcherServletをルートパスにマッピングすると、Webアプリケーションのドキュメントルート配下のファイルにアクセスできなくなってしまいます。
- この動作を変更するには、SpringMVCが提供している「DispatcherServletで受けたリクエストをデフォルトサーブレットへ転送する機能」を有効化する必要がある
7.5.2 SpringMVC独自の静的リソース解決の仕組み
-
HTTPのキャッシュ制御
-
ResourceResolverとResouceTransformerの利用
- ResourceHttpRequestHandlerには
- バージョン付き公開パスを使用した静的リソースへのアクセス
- Gzip化された静的リソースへのアクセス
- WebJars内の静的リソースのバージョン番号の隠蔽
- を行う機能がある
- ResourceHttpRequestHandlerには
-
ResourceResolverインターフェース
- 静的リソースにアクセスするための公開パス
- サーバー上の物理的な静的リソースを相互に解決するためのメソッドを提供
-
ResourceTransformerインターフェース
- 静的リソースのコンテンツデータを書き換えるためのメソッドを提供
-
バージョン付きの公開パスを使用した静的リソースへのアクセス
-
Thymeleafでのアクセス方法について確認する
- ここで紹介されていたhttps://ksoichiro.blogspot.com/2015/04/spring-boot_14.html
th:href="@{/css/main.css}"などと記載する
7.6 国際化
7.6.1 ロケールの解決
-
アプリケーション内で扱うロケール解決には、
org.springframework.web.servlet.LocaleResolverインターフェースを使用する -
SpringMVCは、ロケールの保存場所に応じて以下の実装クラスを提供しており、デフォルトではAcceptHeaderLocaleResolverが有効になっている
-
提供されているLocaleResolverの実装クラス
- AcceptHeaderLocaleResolver
- SessionLocaleResolver
- CookieLocaleResolver
- FixedLocaleResolver
-
クライアントからロケールの指定がない場合、デフォルトロケールが利用され、
- LocaleResolverに指定したデフォルトロケール
- JVMに指定したロケール
- OSに指定したロケール
- の順番で解決されます。
7.6.2 ロケールの利用
- VIEWからロケールにアクセスするときはSpringのタグを使用する
- Handlerメソッドからロケールにアクセスするときは引数にLocaleを指定する
- Handler以外の場所からロケールにアクセスするときは、RequestContextUtilsのgetLocaleメソッドを使用する
7.6.3 UIを使用したロケールの切り替え
-
画面などのUIを使用してロケールを切り替える方法について説明
-
ロケールの切り替えは、
org.springframework.web.servlet.i18n.LocaleChangeInterceptorを利用することで簡単に行うことができる -
LocaleResolverのBean定義
-
LocaleChangeInterceptorのBean定義
-
ロケール切り替え用の画面要素の表示
<a href="?locale=en">English</a><a href="?locale=ja">Japanese</a>このリンクをクリクすると、LocaleChangeInterceptorが呼び出され、 リクエストパラメータで指定されたロケールがSpringMVCアプリケーションに反映される
第8章 Spring Test
-
本章では、SpringFrameworkが提供するテスト支援モジュールを利用して、Springアプリケーションに対してテストを行う方法について解説していく
-
紹介を行うテスト
- DIコンテナに登録したBeanへのテスト
- データベースアクセスを伴う処理へのテスト
- SpringMVC上で動くControllerへのテスト
-
第9章では、SpringSecurityの機能を利用した処理へのテストを紹介
8.1 Spring Testとは
-
Spring Testとは
- Spring Framework上で動かすために作成したクラスのテストを支援するモジュール
-
単体テスト
- テスト対象のクラス内で実装しているロジックのみをテスト
- 単体テストを行う際には、テスト対象のクラスの中で依存している他のコンポーネントはモックやスタブを使用し、実行結果が他のコンポーネントの実装内容に左右されないようにする
-
結合テスト
- 基本的にモックやスタブは使わず、プロダクション環境で使用するクラスを結合してテストを行います。
- ポイントは、システムやアプリケーション全体が正しく動作するかを検証するのではなく、開発者が作成したクラスがSpringのフレームワーク上で正しく動作するかをテストするという点
-
提供されている機能
- JUnitやTestNGといったテスティングフレームワーク上でのSpringのDIコンテナを動かす機能
- トランザクション制御をテスト向けに最適化する機能
- アプリケーションサーバーを使わずにSpringMVCの動作を再現する機能
- テストデータをセットアップするためのSQLを実行する機能
- RestTemplateを使用したHTTP通信に対してモックレスポンスを返却する機能
8.2 DIコンテナ管理のBeanに対するテスト
- junitを使用する前提で説明
8.2.1 Beanの単体テスト
-
Serviceクラスをテスト
-
依存しているコンポーネントはできるだけモック化することを検討する
- 外部ファイル参照サービスやDBアクセスなど
8.2.2 DIコンテナ内のBeanに対する結合テスト
- Springの機能を使用して、DIコンテナ内のリソースを取得してテストを行う
- 単体テストは本当にクラス単体の観点なのに対して、結合テストはプロジェクト内のリソースを結合したテストとみなしている
8.2.3 Spring Testcontext Framework
- 他のランナーとSpringを併用したい場合は、
@ClassRuleと@Ruleを使用すれば実現できるという説明 - TODO: 使用するランナーによって何が違うのかを後で確認する必要ある
8.2.4 DIコンテナのコンフィギュレーション
DIコンテナを作成するには、
@org.springframework.test.context.ContextConfigurationをテストクラスケースに付与する
-
デフォルトのBean定義ファ入りう
-
Webアプリケーション向けのDIコンテナのコンフィギュレーション
@WebApplicationConfiguration付与について説明- Webアプリケーション向けのDIコンテナに加えて、
- ServletAPIに依存する各種モックオブジェクトなどをテストケースクラスにインジェクションできる
8.2.5 DIコンテナのライフサイクル制御
-
Spring TestContext Framework上に生成されたDIコンテナは、テスト実行時のJavaVMが終了するまでキャッシュされ、必要に応じてテストケース間で共有される仕組みになっています。
-
DIコンテナのキャッシュ
- デフォルトの動作では、同一テストケースクラスのテストメソッドで同じDIコンテナが使われる
- さらにテストケースクラスが別の場合でも、
@ContextConfigurationなどに指定した属性値が同じであれば、キャッシュ済みのDIコンテナが利用される
-
DIコンテナの破棄
- 割愛…TODO: 実際のテストケースの組み方について調査する必要あり
8.2.6 プロファイルの指定
- Springのプロファイル機能を使用しているアプリケーションに対してテストを行う場合は、
@org.springframework.test.context.ActiveProfilesを使う
8.2.7 テスト用のプロパティ値の指定
-
テスト用のプロパティ値を設定できる
@org.springframework.test.context.TestPropertySourceを使う
-
プロパティ値の指定には2つの方法がある
- アノテーションに直接指定する
- プロパティファイルに指定する
8.3 データベースアクセスを伴う処理のテスト
- データベースへアクセスするBeanに対するテスト方法について説明
- データベースにアクセスするBeanに対してテストを行う場合、以下の作業が必要になる
- テスト用のデータソースの設定
- テストデータのセットアップ
- テストケース用のトランザクション制御
- テーブルの中身の検証
8.3.1 テスト用のデータソースの設定
- Test用のConfigクラスを作成して、既存のコンフィグクラスを上書きする説明
8.3.2 テストデータのセットアップ
@Sqlを使用すると、テストケース・メソッドの呼び出し前に任意のSLQを実行できる
/** * Sqlを付与することで、テストメソッド実行前に任意のSQL文を実行することができる */ @Test @Sql({ "/account-delete.sql", "/account-insert-data.sql" }) final void testFindOne() { Account account = accountRepositry.findOne("001"); }- メモ
@SqlにはJavaSE8で追加された@Repeatableが付与されているため、JavaSE8以降を使う場合は同じ箇所に複数指定できる- JavaSE7以前のJavaでも、
@org.springframework.test.context.jdbc.SqlGroupを使うことで、複数のSQLを指定できる
- JavaSE7以前のJavaでも、
8.3.3 テストケース用のトランザクション制御
-
デフォルトではテストデータをセットアップする際に使用するトランザクションと、
-
テスト対象のデータアクセス処理で使用するトランザクションは別々になってしまう
-
テストが途中で失敗して、レコードが更新されてしまったり、データの状態が変わってしまうため注意が必要
-
このような事故を防ぐには、JUnit専用のデータベースを用意しておくと確実
-
ローカルなんかも優位だと思う
-
あるいは、SpringTestが提供しているテスト用のトランザクション制御の仕組みを利用して防ぐこともできる
-
トランザクション境界の移動
- SpringTestでは、、JUnit実行時のトランザクション境界を、テストケースメソッドの呼び出し前に移動する仕組みを提供している。
- この仕組を利用すると、
@Sqlで指定したSQLファイルの実行とテストを同一のトランザクション内で行うことができる @Transactionalをクラス、メソッドに指定する
-
トランザクション境界でのロールバック/コミットの制御
- 処理が完了したあと、ロールバックするのではなくコミットしたい場合、
@Commitを付与すれば実現できる
- 処理が完了したあと、ロールバックするのではなくコミットしたい場合、
-
永続コンテキストをフラッシュ
- JPAやHibernateがEntityへの更新操作を永続コンテキストと呼ばれるインメモリ領域に蓄積しておき、トランザクションのコミット時にSQLを発行する仕組みになっているため、明示的にSQLが発行されるようにフラッシュする必要がある
8.3.4 テーブルの中身の検証
- JdbcTemplateを使用して検証する
- DIコンテナに入っている同じオブジェクトを使用すること
8.4 Spring MVC のテスト
-
SpringMVC上で動くControllerに対するテスト方法について説明
-
Controllerに対するテストの話をするときにいつも出てくる話題がある
- →「Controllerに対する単体テストは必要か?」という話題
-
Controllerの主な役割は、
- リクエストマッピング
- 入力チェック
- リクエストデータの取得
- ビジネスロジックの呼び出し
- 遷移先の制御
-
これらは、SpringMVCのフレームワークと結合しないと妥当性を検証することができないので、単体テストではなく、結合テストとして行ったほうがよい
-
では、SpringMVCのフレームワーク機能と結合した状態でControllerをテストするにはどうすればよいのでしょうか?
-
最もオーソドックスな選択肢は、Webアプリケーションをアプリケーションサーバーにデプロイし、E2E(End to End)テストとして実施する方法
-
E2Eテストとして実施すると、Viewが生成したレスポンスデータの妥当性を検証できるのがメリット
-
一方、以下のようなデメリットがある
- アプリケーションサーバーやデータベースの起動が必須となる
- トランザクションがコミットされるため、テスト実施前の状態に戻すことができない
- 回帰テストを実行するために、Seleniumなどを利用したテストケースの実装が必要になる
- Seleniumを使うと、テストの実行時間が長くなる
-
SpringTestはE2Eテストのデメリットを解消しつつ、SpringMVCのフレームワーク機能と結合した状態でControllerをテストするためのプラットフォームとして、
org.springframework.test.web.servlet.MockMvcというクラスを提供している
8.4.1 MockMvcとは
-
アプリケーションサーバー上にデプロイせず、SpringMVCの動作を再現する仕組みを提供するクラス
-
流れ
- テストケース・メソッドは、DispatcherServletにリクエストするデータ(リクエストパスやリクエストパラメータなど)をセットアップする
- MockMvcは、DispatcherServletに対して擬似的なリクエストを行う。実際に使われるDispatcherServletは、テスト用に拡張されている、
org.springframework.test.web.servlet.TestDipacherSevletとなる - DispatcherServletは、リクエスト内容に一致するHandlerのメソッドを呼び出す
- テストケースメソッドは、MockMvcが返却する実行結果を受け取り、実行結果の妥当性を検証する
-
動作モードには2つある
- ユーザー指定のDIコンテナと連携するモード
- スタンドアロンモード
-
SpringMVCのコンフィギュレーションも含めてテストしたい場合、ユーザー指定のDIコンテナと連携するモードを利用すること
-
メモ
- 本書では扱わないが、SpringTestは、MockMvcとHtmlUnitを連携する機能も提供している
- HtmlUnitと連携することで、テンプレートエンジンが生成したHTMLを検証することができる
- さらに、SeleniumWebDriverやGebと連携すると、Page Object Patternを活用した可読性および再利用性の高いテストケースを記載することも可能
- TODO: あとで確認してみる
8.4.2 MockMvcのセットアップ
-
ユーザー指定のDIコンテナと連携するモード
-
スタンドアロンモード
- SpringMVCのコンフィギュレーションはSpringTest側が行い、SpringTestが生成したDIコンテナを使用してSpringMVCの動作を再現
-
サーブレットフィルタの追加
- MockMvcには、サーブレットフィルタを追加することができる
-
staticメソッドのインポート
- テストを書く前に、MockMvcを使用したテストをサポートしてくれるstaticメソッドをインポートします。
// よく使用するstaticメソッドimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;8.4.3 テストの実行
- テストを実行する際は、Controllerを呼び出すために必要なリクエストデータをセットアップし、MockMvcにリクエストの実行依頼を行います。
public void testHome() throws Exception { mockMvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(forwardedUrl("/WEB-INF/index.jsp")); }8.4.4 リクエストデータのセットアップ
- リクエストデータのセットアップは、
org.springframework.test.web.servlet.request.MockHttpServletRequestBuilderorg.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder- のファクトリメソッドを使用して行います。
@Test public void testBooks() throws Exception { mockMvc.perform(get("/books") .param("name", "Spring") .accept(MediaType.APPLICATION_JSON) .header("X-Track-Id", UUID.randomUUID().toString())) .andExpect(status().isOk()); }8.4.5 実行結果の検証
-
MockMvcResultMathcersの主なメソッド
- status
- header
- cookie
- content
- view
- forwardedUrl
- redirectedUrl
- model
- flash
- request
-
メモ
- ResultMatcherでサポートされていない検証を行いたい場合は、以下のいずれかの方法で検証ロジックを実装する
- 独自のResultMatcherを作成する
- ResultActionsのandReturnメソッドを呼び出して、MvcResultを取得し、テストケース内で検証ロジックを実装する
- MEMO: 検証ロジックを複数のテストケースで共有したい場合は、ResultMatcherを作成すること
- ResultMatcherでサポートされていない検証を行いたい場合は、以下のいずれかの方法で検証ロジックを実装する
8.4.6 実行結果の出力
- 実行結果をログなどに出力する場合は、
org.springframework.test.web.servlet.ResultActionsのandDoメソッドを使用する - andDoメソッドの引数には、実行結果に対して、任意の処理を行う
org.springframewok.test.web.servlet.ResultHandlerを指定する
mockMvc.perform(get("/books")) .andExpect(status().isOk()) .andDo(log());- log: 実行結果をデバッグレベルでログ出力する
- pring: 実行結果を任意の出力先に出力する
第9章 Spring Security
-
SpringSecurityの「セットアップ方法」と「アーキテクチャ」について説明したあと、
-
セキュリティ対策の基本となる
- 「認証」と「認可」、
-
さらにセキュリティを強化するために必要となる
- 「CSRF対策」
- 「セッション管理」
- 「ブラウザのセキュリティ対策機能との連携(セキュリティヘッダーの出力)」について説明
-
最後に、SpringSecurityが提供する支援モジュールを使用して、セキュリティ対策が正しく適用されているかをテストする方法についても紹介
9.1 SpringSecurityとは
- アプリケーションにセキュリティ対策機能を実装する際に使用するフレームワーク
9.1.1 SpringSecurityの特徴
- 豊富なオプションの提供
- SpringSecurityのデフォルト実装の動作をカスタマイズするためのオプションが豊富に提供されている
- このため、デフォルトの動作がセキュリティ要件に合致しない場合であっても、オプションの値を変更することで要件にあった動作に変更できるケースがある
- 豊富な拡張ポイントの提供
- SpringSecurityは動作をカスタマイズするための拡張ポイントを豊富に提供します。
- SpringSecurityのデフォルト実装を使って要件を満たせない場合は、拡張クラスを作成することで要件にあった動作にカスタマイズすることができます。
9.1.2 基本機能
-
セキュリティ対策の基本機能として「認証機能」と「認可機能」の2つを提供しています。
-
認証機能:アプリケーションを利用するユーザーの正当性を確認する機能を提供する
-
認可機能:アプリケーションが提供するリソースや処理に対するアクセスを制御する機能を提供する
9.1.3 強化機能
-
SpringSecurityでは認証と認可という基本機能に加え、
-
Webアプリケーションのセキュリティを強化するための機能をいくつか提供している
-
セキュリティ対策の強化機能
- セッション管理機能
- CSRF対策機能
- ブラウザのセキュリティ対策機能との連携機能
- ※他にある!TODO: 他のセキュリティ機能も確認しておく
9.2 Spring Securityのセットアップ
9.2.1 ライブラリのセットアップ
- pom.xmlに設定を追加する手順の説明
9.2.2 SpringSecurityのBean定義
SpringSecurityのコンポーネントをBean定義します
- コンフィギュレーションクラスの作成
// SpringSecurityが提供しているコンフィギュレーションクラスがインポートされ、SpringSecurityを利用するために必要となるコンポーネントのBean定義が自動で行われる仕組み@EnableWebSecuritypublic class AppSecurityConfig extends WebSecurityConfigurerAdapter { // 継承すると、デフォルトで適用されるBean定義を簡単にカスタマイズすることができる
@Override public void configure(WebSecurity web) { // セキュリティ対策が不要なリソースがある場合、SpringSecurityの処理を適用しないようにする web.ignoring().antMatchers("/resources/**"); }}- web.xmlへの設定追加方法説明
- TODO: SpringBootだと必要?
9.2.3 サーブレットフィルタの設定
-
最後に、SpringSecurityが提供しているサーブレットフィルタクラス(FilterChainProxy)をサーブレットコンテナに登録する
-
TODO: SpringBootだとConfigでフィルター追加する
-
メモ
- Sevlet3.0以降のサーブレットコンテナでは、サーブレットコンテナの初期化処理をJavaのコードで行うことができる
- SpringSecurityでは、サーブレットコンテナの初期化処理をJavaを使って行うためのサポートクラス説いて、
AbstractSecurityWebApplicationInitializerという抽象クラスを提供しています- 以下の初期化処理を自動で行ってくれる
- ContextLoaderListenerをサーブレットコンテナに登録する処理
- SpringSecurityのサーブレットフィルタクラスをサーブレットコンテナに登録する処理
- 参考:https://qiita.com/opengl-8080/items/c105152c9ca48509bd0c
9.3 Spring Security のアーキテクチャ
-
各機能の詳細な説明を行う前に、SpringSecurityのアーキテクチャ概要とSpringSecurityを構成する主要なコンポーネントの役割を見ていく
-
メモ
- ここで紹介する内容は、SpringSecurityが提供するデフォルトの動作をそのまま利用する場合や、
- SpringSecurityのコンフィギュレーションをサポートする仕組みを利用する場合は、開発者が直接意識する必要ない
- そのため、まず各機能の使い方を知りたい場合は、本説を読み飛ばしてOK
- カスタマイズする際に必要になってくるので、アーキテクトを目指す方は一読しておくことをおすすめします。
9.3.1 Spring Securityのモジュール構成
-
提供しているモジュールを紹介
-
コンポーネントの役割などに応じてモジュール分割されており、標準的なWebアプリケーションに対してセキュリティ対策を講じる際に必要となるモジュールは以下の4つになる
spring-security-core: 認証と認可機能を実現するためのコアなコンポーネントが格納されているspring-security-web: Webアプリケーションのセキュリティ対策を実現するためのコンポーネントが格納されているspring-security-config: 各モジュールから提供されているコンポーネントのセットアップをサポートするためのコンポーネント(Java ConfigをサポートするクラスやXMLネームスペースを解析するクラスなど)が格納されているspring-security-taglibs: 認証情報や認可機能にアクセスするためのJSPタグライブラリが格納されている
-
本書で使い方を紹介しないが、上記以外にも以下のようなモジュールがある
- 一般的に利用される認証方法(LDAP、OpenID、CASなど)をサポートするためのモジュール
- ACL(AccessControlList)を使用したドメインオブジェクトの認可制御を行うモジュール
- SpringのWebSocket機能に対してセキュリティ対策を追加するためのモジュール
- SpringSecurityの機能を用いる処理に対するテストを支援するためのモジュール
-
メモ
- SpringSecurityのモジュールではないが、OAuth2.0の仕組みを使用してAPIの認可を実現するためのモジュール(spring-security-oauth2)などが姉妹ライブラリとして提供されている
9.3.2 フレームワークのアーキテクチャ
-
処理の流れ
- クライアントはWebアプリケーションに対してリクエストを送る
- SpringSecurityのFilterChainProxyクラスがリクエストを受け取り、HttpFirewallインターフェースのメソッドを呼び出して、HttpServletRequestとHttpServletResponseに対してファイアウォール機能を組み込む
- FilterChainProxyクラスはSecurityFilterChainに設定されているセキュリティ対策用のSecurityFilterクラスに処理を移譲する
- SecurityFilterChainには複数のSecurityFilterが設定されており、SecurityFilterの処理が正常に終了すると皇族のSecurityFilterが呼び出される
- 最後のSecurityFilterの処理が正常に終了した場合、後続処理を呼び出し、Webアプリケーション内のリソースへアクセスする
- FilterChainProxyクラスは、Webアプリケーションから返却されたリソースをクライアントに返却する
-
FilterChainProxy
- FilterChainProxyクラスは、フレームワーク処理のエントリーポイントとなるサーブレットフィルタクラス
- このクラスはフレームワーク処理の全体の流れを制御し、具体的なセキュリティ対策処理はSecurityFilterに移譲するスタイルとなっている
-
HttpFirewall
- HttpFirewallインターフェースは、HttpServletRequestとHttpServletResponseに対して、ファイアウォール機能を組み込むためのインターフェースです。デフォルトでは、DefaultHttpFirewallクラスが使用され、ディレクトリトラバーサル攻撃や、不正なリダイレクト先の指定によるHTTPレスポンス分割攻撃に対するチェックなどが実装されている
-
SecurityFilterChain
- SecurityFilterChainインターフェースは、FilterChainProxyが受け取ったリクエストに対して適用する「SecurityFilterリスト」を管理するためのインターフェース
- デフォルトではDefaultSecurityFilterChainクラスが使用され、以下のようなBean定義を行うと、指定したパスパターンごとに異なるセキュリティ対策が適用できます。
-
Security Filter
- SecurityFilterクラスは、フレームワーク機能やセキュリティ対策機能を提供するサーブレットフィルタクラスです。
- SpringSecurityは、複数のSecurityFilterを連鎖させることで、Webアプリケーションのセキュリティ対策を行う仕組みになっています。
-
コアなSecurityFilter
- SecurityContextPersistenceFilter
- UsernamePasswordAuthenticationFilter
- LogoutFilter
- FilterSecurityInterceptor
- ExceptionTranslationFilter
9.4 認証処理の適用
-
認証処理は、アプリケーションを利用するユーザーの正当性を確認するための処理
-
最も標準的な方法はアプリケーションを使用できるユーザーをデータストアに登録しておいて、利用者が入力した認証情報と照合する方法
-
利用者に認証情報を入力してもらう方式もいくつかあり、
- HTMLの入力フォームを使う方式
- RFCで定められているHTTP標準の認証方式(BASIC認証やDigest認証など)を利用するのが一般的
- OpenID認証、シングルサインオン認証などの認証方式を利用するケースもある
-
本節では、HTMLの入力フォームで入力した認証情報とリレーショナルデータベースに格納されているユーザー情報を照合して認証処理を行う実装例を紹介しながら、SpringSecurityの認証機能を解説
9.4.1 認証処理の仕組み
-
認証処理の流れ
- クライントは認証処理を行うパスに対して資格情報(ユーザー名とパスワード)を指定してリクエストを送信する
- Authentication Filterはリクエストから資格情報を取得し、AuthenticationManagerクラスの認証処理を呼び出す
- ProviderManager(デフォルトで使用されるAuthenticationManagerの実装クラス)は、実際の認証処理をAuthenticationProviderインターフェースの実装クラスに移譲する
-
メモ
- Authentication FilterとAuthenticationProviderの実装クラスは複数用意されており、要件に合わせて使用するクラスを選択する仕組みになっている
- TODO: 選択できるクラスについて確認
-
Authentication Filter
- 認証方式に対する実装を提供するサーブレットフィルタ
- 本書では、フォーム認証用のサーブレットフィルタクラス(UsernamePasswordAuthenticationFilter)をシヨすうる前提で説明しますが、SpringSecurityはBasic認証、Digest認証、Remember Me 認証用のサーブレットフィルタクラスも提供しています
-
AuthenticationManager
- 認証処理を実行するためのインターフェース
- SpringSecurityが提供するデフォルトの実装(ProviderManager)では、実際の認証処理はAuthenticationProviderに移譲し、AuthenticationProviderで行われた認証処理結果をハンドリングする仕組みになっています。
-
AuthenticationProvider
- 認証処理の実装をて依拠するためのインターフェース
- 本書では、データストアに登録しているユーザーの資格情報とユーザーの状態をチェックして認証処理を行う実装クラス(DaoAuthenticationProvider)を使用する前提で説明しますが、SpringSecurityは認証方法別の実装クラスも提供している。
- ※DBだけでなく他の認証方式にも対応しているという意味だと理解
9.4.2 フォーム認証
-
SpringSecurityは以下のような流れでフォーム認証を行う
- クライアントは、フォーム認証を行うパスに対して資格情報(ユーザー名とパスワード)をリクエストパラメータとして送信する
- UsernamePasswordAuthenticationFilterクラスは、リクエストパラメータから資格情報を取得して、AuthenticationManagerの認証処理を呼び出す
- UsernamePasswordAuthenticationFilterクラスは、AuthenticationManagerから返却された認証結果をハンドリングする。認証処理が成功した場合は、AuthenticationSuccessHandlerのメソッドを、認証処理が失敗した場合は、AuthenticationFailureHandlerのメソッドを呼び出し、画面遷移を行う
-
フォーム認証の適用
- Bean定義
// SpringSecurityが提供しているコンフィギュレーションクラスがインポートされ、SpringSecurityを利用するために必要となるコンポーネントのBean定義が自動で行われる仕組み@EnableWebSecuritypublic class AppSecurityConfig extends WebSecurityConfigurerAdapter { // 継承すると、デフォルトで適用されるBean定義を簡単にカスタマイズすることができる
@Override public void configure(WebSecurity web) { // セキュリティ対策が不要なリソースがある場合、SpringSecurityの処理を適用しないようにする web.ignoring().antMatchers("/resources/**"); }
// ★追加!! @Override public void configure(HttpSecurity http) throws Exception { http.formLogin(); // formLoginメソッドを呼び出すと、フォーム認証が有効になり、FormLoginConfigurerのインスタンスが返却される。 }}-
デフォルトの動作
- SpringSecurityのデフォルトの動作では、
/loginに対して、GETメソッドでアクセスするとSpringSecurityが用意しているデフォルトのログインフォームが表示され、ログインボタンを謳歌すると/loginに対してPOSTメソッドでアクセスして認証処理を行います。
- SpringSecurityのデフォルトの動作では、
-
ログインフォームの作成
- SpringSecurityは、フォーム認証用のログインフォームをデフォルトで提供しているが、そのまま利用するケースは殆どないと思う
- ここでは、自身で作成したログインフォームをSpringSecurityに適用する方法を紹介する
- まず、ログインフォームを表示するためのJSPを作成
- ここでは、SpringMVCのViewResolverに指定しているベースパス(src/main/webapp/views/)の直下にJSPを配置し、SpringMVC経由でログインフォームを表示する前提で説明
-
ログインフォームをSpringSecurityに適用するために以下のようなBean定義を行う
- loginPageメソッドを呼び出し、ログインフォームを表示するためのパスを指定する
- 匿名のユーザーが認証を必要するリソースにアクセスした場合、ここで指定したパスにリダイレクトしてログインフォームを表示する仕組みになっている。loginPageメソッドに与えられた引数によって、認証パス(loginProcessingUrl)も連動して変わる
- permitAll()メソッドを呼び出して、すべてのユーザーに対してログインフォームへのアクセス件を付与する
- loginPageメソッドを呼び出し、ログインフォームを表示するためのパスを指定する
@Override public void configure(HttpSecurity http) throws Exception { // http.addFilter(this.preAuthenticatedProcessingFilter()); // http.formLogin(); http.formLogin() .loginPage("/login") // 認証を必要とするURLに遷移した場合、このURLにリダイレクトしてログインフォームを表示する仕組み .permitAll(); // すべてのユーザーに対してログインフォームへのアクセス件を付与する http.authorizeRequests() .anyRequest() .authenticated(); // formLoginメソッドを呼び出すと、フォーム認証が有効になり、FormLoginConfigurerのインスタンスが返却される。 }- デフォルト動作のカスタマイズ
- フォーム認証処理のカスタマイズポイントとして
- 認証パス
- 資格情報を送るリクエストパラメータ名の変更方法を紹介
- フォーム認証処理のカスタマイズポイントとして
http.formLogin() //.loginPage("/login") .loginProcessingUrl("authenticate") .usernameParameter("uid") .passwordParameter("pwd") .permitAll();- loginPageもloginProcessingUrlもやっていることは同じらしい
9.4.3 認証成功時のレスポンス
-
SpringSecurityは、認証成功時のレスポンスを制御するためのコンポーネントとして、AuthenticationSuccessHandlerというインターフェースと実装クラスを提供している
-
AuthenticationSuccessHandlerの実装クラス
- SavedRequestAwareAuthenticationSuccessHanlder: 認証前にアクセスを試みたURLにリダイレクト(デフォルト)
- SimpleUrlAuthenticastionSuccessHandler: コンストラクタに指定したURLにリダイレクトまたはフォワードする
-
デフォルトの動作
- 認証前にアクセスを拒否したリクエストをHTTPセッションに保存しておいて、認証が成功した際にアクセスを拒否したリクエストを復元してリダイレクトする仕組みになっている
-
デフォルト動作のカスタマイズ
- 認証成功時のレスポンスのカスタマイズポイントとして、認証成功時に遷移するデフォルトのパスの変更方法を紹介
http.formLogin() .loginPage("/login") // 認証を必要とするURLに遷移した場合、このURLにリダイレクトしてログインフォームを表示する仕組み .defaultSuccessUrl("/menu") // 認証成功時のデフォルトアクセスはルート。カスタマイズするために記載 .permitAll(); // すべてのユーザーに対してログインフォームへのアクセス件を付与する9.4.5 データベース認証
-
データベース認証の仕組み
- SpringSecurityはクライアントからの認証依頼を受け、DaoAuthenticationProviderの認証処理を呼び出す
- DaoAuthenticationProviderは、UserDetailsServiceのユーザー情報取得処理を呼び出す
- UserDetailsServiceの実装クラスは、データストアからユーザー情報を取得する
- UserDetailsServiceの実装クラスは、データストアから取得したユーザー情報からUserDetailsを生成する
- DaoAuthenticationProviderは、UserDetailsServiceから返却されたUserDetailsとクライアントが指定した認証情報との照合を行い、クライアントが指定したユーザーの正当性をチェックする。クライアントが指定したユーザーが正当なユーザーでない場合は、認証例外をスローする
-
メモ
- SpringSecurityはユーザー情報をリレーショナルデータベースからJDBC経由で取得するための実装クラスを提供しているが、最低限の認証処理しか行わないため、そのまま利用できるケースは少ないと思われる
- そのため本書では、UserDetailsとUserDetailsServiceの実装クラスを作成する方法を紹介します。
-
UserDetailsの作成
- MEMO:※細かいのでソースを確認 後で追記
-
認証処理の適用
9.4.6 パスワードのハッシュ化
- いくつか、パスワードを扱う実装クラスが提供されている
- MEMO:※細かいのでソースを確認 後で追記
9.4.7 認証イベントのハンドリング
-
SpringSecurityは、SpringFrameworkが提供しているイベント通知の仕組みを利用して、
-
認証処理の結果を他のコンポーネントへ連携する仕組みを提供しています
-
この仕組を利用すると以下のようなセキュリティ要件をSpringSecurityの認証機能に組み込むことができます
- 認証成功、失敗などの認証履歴をデータベースやログに保存したい
- パスワードを連続して誤った場合にアカウントをロックしたい
-
認証イベントの通知は以下のような仕組みで行われる
-
認証イベントの通知の流れ
- SpringSecurityの認証機能は、認証結果をAuthenticationEventPublisherに渡して認証イベントの通知依頼を行う
- AuthenticationEventPublisherインターフェースのデフォルトの実装クラスは、認証結果に対応する認証イベントクラスのインスタンスを生成し、ApplicationEventPublisherに渡してイベントの通知依頼を行う
- ApplicationEventPublisherインターフェースの実測クラスは、ApplicationListenerインターフェースの実装クラスにイベントを通知する
- ApplicationListenerの実装クラスの1つであるApplicationListenerMethodAdaptorは、
@org.springframework.context.event.EventLintenerが付与されているメソッドを呼び出してイベントを通知する
-
メモ
- Spring4.1まではApplicationListenerインターフェースの実装クラスを作成して、イベントを受け取る必要があった
- Spring4.2からは、POJOに
@EventListenerを付与したメソッドを実装するだけでイベントを受け取ることができる - Spring4.2以降も、従来と同じ用にApplicationListenerインターフェースの実装クラスを作成してイベントを受け取ることができる
-
認証成功イベント
- 認証が成功したときにSpringSecurityが通知する主なイベントは以下の3つです。
- この3つのイベントは途中でエラーが発生しなければ、以下の順番ですべて通知されます
- AuthenticationSuccessEvent
- AuthenticationProviderによる認証処理が成功したことを通知する。
- このイベントをハンドリングすると、クライアントが正しい認証情報を指定したことを検知することができるが、後続の認証処理でエラーになる可能性がある
- SessionFixationProtectionEvent
- セッション固定攻撃対策の処理(セッションIDの変更処理)が成功したことを通知する。このイベントをハンドリングすると、変更後のセッションIDを検知することができる
- InteractiveAuthenticationSuccessEvent
- 認証処理がすべて成功したことを通知する。このイベントをハンドリングすると、画面遷移を除くすべての認証処理が成功したことを検知することができる
- AuthenticationSuccessEvent
-
認証失敗イベント
- 認証が失敗したときにSpringSecurityが通知する主なイベントは以下の通り
- 認証に失敗した場合は、以下のいずれか1つのイベントが通知される
- AuthenticationFailureBadCredentialsEvent
- AuthenticationFailureDisabledEvent
- AuthenticationFailureLockedEvent
- AuthenticationFailureExpiredEvent
- AuthenticationFailureCredentialsExpiredEvent
- AuthenticationFailureServiceExceptionEvent
-
イベントリスナの作成
- 認証イベントの通知を受け取って処理を行いたい場合、
@EventListenerを付与したメソッドを実装したクラスを作成し、DIコンテナに登録するだけ
- 認証イベントの通知を受け取って処理を行いたい場合、
/** * - 認証イベントの通知の流れ * 1. SpringSecurityの認証機能は、認証結果をAuthenticationEventPublisherに渡して認証イベントの通知依頼を行う * 2. AuthenticationEventPublisherインターフェースのデフォルトの実装クラスは、認証結果に対応する認証イベントクラスのインスタンスを生成し、ApplicationEventPublisherに渡してイベントの通知依頼を行う * 3. ApplicationEventPublisherインターフェースの実測クラスは、ApplicationListenerインターフェースの実装クラスにイベントを通知する * 4. ApplicationListenerの実装クラスの1つであるApplicationListenerMethodAdaptorは、`@org.springframework.context.event.EventLintener`が付与されているメソッドを呼び出してイベントを通知する * * - `@EventLitener`を付与したメソッドを実装するだけで認証成功/失敗時の処理を実装できる仕組み * @author Tomo * */@Slf4j@Componentpublic class AppSecurityEventListener {
// ============================== // SUCCESS EVENT HANDLERS // ==============================
/** * AuthenticationProviderによる認証処理が成功したことを通知する。 * このイベントをハンドリングすると、クライアントが正しい認証情報を指定したことを検知することができるが、 * 後続の認証処理でエラーになる可能性がある。 * @param event */ @EventListener public void handleAuthenticationSuccess(AuthenticationSuccessEvent event) { // }
/** * セッション固定攻撃対策の処理(セッションIDの変更処理)が成功したことを通知する。 * このイベントをハンドリングすると、変更後のセッションIDを検知することができる。 * @param event */ @EventListener public void handleSessionFixationProtection(SessionFixationProtectionEvent event) { // }
/** * 認証処理がすべて成功したことを通知する。 * このイベントをハンドリングすると、画面遷移を除くすべての認証処理が成功したことを検知することができる。 * @param event */ @EventListener public void handleInteractiveAuthenticationSuccess(InteractiveAuthenticationSuccessEvent event) { // }
// ============================== // FAILURE EVENT HANDLERS // ==============================
@EventListener public void handleBadCredentials(AuthenticationFailureBadCredentialsEvent event) { log.info("BAD Credentials is detected. username : {}", event.getAuthentication().getName()); }
@EventListener public void handleDisabled(AuthenticationFailureDisabledEvent event) { log.info("Disabled user is detected. username : {}", event.getAuthentication().getName()); }
@EventListener public void handleLocked(AuthenticationFailureLockedEvent event) { log.info("Locked user is detected. username : {}", event.getAuthentication().getName()); }
@EventListener public void handleExpired(AuthenticationFailureExpiredEvent event) { log.info("Expired user is detected. username : {}", event.getAuthentication().getName()); }
@EventListener public void handleCredentialsExpired(AuthenticationFailureCredentialsExpiredEvent event) { log.info("CredentialsExpired is detected. username : {}", event.getAuthentication().getName()); }
@EventListener public void handleServiceException(AuthenticationFailureServiceExceptionEvent event) { log.info("ServiceException is detected. username : {}", event.getAuthentication().getName()); }}9.4.8 ログアウト
-
SpringSecurityは以下のような流れでログアウト処理を行う
- クライアントは、ログアウト処理を行うためのパスにリクエストを送信する
- LogoutFilterは、LogoutHandlerのメソッドを呼び出してログアウト処理を行う
- LogoutFilterは、LogoutSuccessHandlerのメソッドを呼び出して画面遷移を行う
-
LogoutHandlerの実装クラス
- SecurityContextLogoutHandler: 認証情報のクリアとセッションの破棄を行う
- CookieClearingLogoutHandler: 指定したクッキーの削除するためのレスポンスを行う
- CsrfLogoutHandler: CSRF対策用のトークンの破棄を行う
-
こららのLogoutHandlerは、SpringSecurityが提供しているBean定義をサポートするクラスが自動でLogoutFilterに設定する仕組みになっているので、基本的にはアプリケーションの開発者が直接意識する必要はない
-
ログアウト処理の適用
- ログアウト処理を適用するには、以下のようなBean定義を行う必要がある
http.authorizeRequests() .antMatchers("/", "/find", "/login", "/signup", "/error", "/login-error").permitAll() .anyRequest().authenticated() .and() // Login処理 .formLogin() .loginPage("/login") // 認証を必要とするURLに遷移した場合、このURLにリダイレクトしてログインフォームを表示する仕組み .defaultSuccessUrl("/menu") // 認証成功時のデフォルトアクセスはルート。カスタマイズするために記載 .failureUrl("/login") // 認証失敗時ログイン画面に戻す .usernameParameter("id") .passwordParameter("password") .and() // Logout処理 .logout() .logoutRequestMatcher(new AntPathRequestMatcher("logout**")) .logoutSuccessUrl("/login") .permitAll(); // すべてのユーザーに対してログインフォームへのアクセス件を付与する- デフォルトの動作のカスタマイズ
- 遷移先を変える
logoutSuccessUrlの引数を変えてあげる
- 遷移先を変える
9.4.10 認証情報へのアクセス
-
認証済みのユーザーの認証情報は、SpringSecurityのデフォルト実装では、セッションに格納される
-
セッションに格納された認証情報は、リクエストごとにSecurityContextPersistenceFilterクラスによって
-
SecurityContextHolderというクラスに格納され、同一スレッド内であればどこからでもアクセスすることができる用になる
-
Javaからのアクセス
- 一般的な業務アプリケーションでは、「いつ」「誰が」「どのデータに」「どのようなアクセスをしたか」を
- 記録する監査ログを取得することがある。この要件を実現する際の、「誰が」は、認証情報から取得できる
// 認証情報(Authenticationオブジェクト)を取得するAuthentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userUuid = null;// Authentication#getPrincipal()メソッドを呼び出して、UserDetailsオブジェクトを取得する// 認証済みでない場合は、匿名ユーザーを表す文字列が返却されるので注意if (authentication.getPrincipal() instanceof AccountUserDetails) { AccountUserDetails userDetails = AccountUserDetails.class.cast(authentication.getPrincipal()); // UserDetailsから処理に必要な情報を取得する userUuid = userDetails.getAccount().getUserUuid();}-
アノテーションでアクセスした方が良さそう
-
JSPからのアクセス
- 一般的なWebアプリケーションでは、ログインユーザーのユーザー情報などを画面に表示することがある。
<% taglib prefix="sec" uri="http://www.springframework.org/security/tags" %><%-- ... --%>ようこそ<sec:authentication property="principal.account.lastName" />さん。- 追記:thymeleafからのアクセス
<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> <!-- これを追加してThymeleafからSpringSecurityを利用 -->
<!-- 省略 --><body> <h1>こんにちは、<span sec:authentication="principal.userId"></span>さん</h1> <!-- principal.メンバ変数名 で参照できる --></body>9.4.11 認証処理とSpringMVCの連携
- SpringSecurityは、SpringMVCと連携するためのコンポーネントをいくつか提供している
- ここでは、認証処理と連携するためのコンポーネントの使い方を紹介
- SpringSecurityは認証情報(UserDetails)をSpringMVCのコントローラーのメソッドに引き渡すためのコンポーネントとして、AuthenticationPrincipalArgumentResolverというクラスを提供している
- これを使用すると、コントローラーのメソッド引数として、UserDetailsインターフェースまたはその実装クラスのインスタンスを受け取ることができる
@Controllerpublic class Controller {
@RequestMapping("/") public index(@AuthenticationPrincipal UserDetailsImpl userDetails) { System.out.println(userDetails.getUserId) // userId System.out.println(userDetails.getPassword) //password System.out.println(userDetails.getHoge) //hoge }9.4.12 エラーメッセージ
-
認証に失敗した場合、SpringSecurityが用意しているエラーメッセージが表示されます
-
このエラーメッセージは内容を変更したり、表示しないようにすることができます。
-
エラーメッセージの変更
- 認証失敗時に表示されるエラーメッセージを変更したい場合は、MessageSourceで読み込んでいるプロパティファイルに
- SpringSecurityが用意しているメッセージの定義を追加してください
AbstractUserDetailsAuthenticationProvider.badCredentials = 入力した認証情報に誤りがあります。AbstractUserDetailsAuthenticationProvider.credentialsExpired = 認証情報の利用期限が切れています。AbstractUserDetailsAuthenticationProvider.disabled = 無効なアカウントです。AbstractUserDetailsAuthenticationProvider.expired = アカウントの期限が切れています。AbstractUserDetailsAuthenticationProvider.locked = アカウントがロックされています。-
この他にも多数のメッセージが用意されている
-
種類を確認するには、
spring-security-coreモジュールのjarファイルの中のorg/springframework/security/message.propertiesファイルを確認してください -
メッセージ定義定数クラスは自動生成するべきな件
-
メモ
- MessageSourceの中でプロパティファイルをISO 8859-1(デフォルト)で読み込んでいる場合は、マルチバイト文字は
- Unicodeコード(\udddd表記)形式に変換する必要がある。
- なお、プロパティファイルを任意の文字コードで読み込む場合は、MessageSourceのdefaultEncodingプロパティに文字コードを指定してください。
@Bean public MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasenames("i18n/messages"); // クラスパス上に格納されているプロパティファイル(拡張子は除く)を指定する messageSource.setDefaultEncoding("UTF-8"); // ★ここのこと! return messageSource; }-
システムエラー時のメッセージ
- 認証処理の中で予期しないエラー(システムエラーなど)が発生した場合、InternalAuthenticationServiceExceptionという例外が発生する。InteractiveAuthenticationServiceExceptionが保持するメッセージには、
- 原因例外のメッセージが設定されるため画面にそのまま表示するのは好ましくない
- システムエラーの例外メッセージを画面に表示しない用にするには、ExceptionMappingAuthenticationFailureHandlerやDelegatingAuthenticationFailureHandlerを使用して、InternalAuthenticationServiceExceptionが発生したときの遷移先をシステムエラー画面にするのが良いでしょう。
-
TODO: この辺見て実装する
9.5 認可処理
- 認可処理は、アプリケーションの利用者がアクセスできるリソースを制御するための処理
- 最も標準的な方法は、リソース毎にアクセスポリシーを定義しておいて、利用者がリソースにアクセスしようとしたときにアクセスポリシーを調べて制御する方法
- アクセスポリシーには、どのリソースにどのユーザーからのアクセスを許可するかを定義します
- SpringSecurityでは、Webリソース、Javaメソッド、ドメインオブジェクトに対してアクセスポリシーを定義できる
※ドメインオブジェクトに関する認可処理は本書では扱わない 興味がある場合は、下記参照 http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#domain-acls
9.5.1 認可処理の仕組み
-
SpringSecrityが提供する認証処理の仕組みを理解しましょう
-
以下のような流れで認可処理を行います
- クライアントが任意のリソースにアクセスする
- FilterSecurityInterceptorクラスは、AccessDecisionManagerインターフェースのメソッドを呼び出し、リソースへのアクセス権の有無をチェックする
- AffirmativeBasedクラス(デフォルトで使用されるAccessDecisionManagerの実装クラス)は、AccessDecisionVoterインターフェースのメソッドを呼び出し、アクセス件の有無を投票してもらう
- FilterSecurityInterceptorは、AccessDecisionManagerによってアクセス権が付与された場合に限り、リソースへアクセスする
-
ExceptionTranslationFilter
- 認可処理(AccessDecisionManager)で発生した例外をハンドリングし、
- クライアントに適切なレスポンスを行うためのサーブレットフィルタ
- デフォルトの実装では、未認証ユーザーからのアクセスの場合は、認証を促すレスポンス、認証済みユーザーからのアクセスの場合は、認可エラーを通知するレスポンスを返却する
-
FilterSecurityInterceptor
- HTTPリクエストに対して認可処理を適用するためのサーブレットフィルタで、実際の認可処理はAccessDecisionManagerに移譲しています。
- AccessDecisionManagerインターフェースのメソッドを呼び出す際には、クライアントがアクセスしようとしたWebリソースに指定されているアクセスポリシーを連携します。
-
AccessDecisionManager
- アクセスしようとしたリソースに対してアクセス権があるかチェックを行うためのインターフェース
- SpringSecurityが提供する実装クラスでは、このあと紹介するAccessDecisionVoterというインターフェースのメソッドを呼び出してアクセス権を付与するか否かを投票するしくみになっており、デフォルトで適用されるクラスはAffirmativeBasedクラスです。AffirmativeBasedクラスは、いずれかのAccessDecisionVoterが付与を投票した場合にアクセス権を与える実装クラス
-
AccessDecisionVoter
- アクセスしようとしたリソースに指定されているアクセスポリシーを参照し、アクセス権を付与するか否かを投票する(付与、拒否、棄権)するためのインターフェース
- SpringSecurityではいくつかの実装クラスを提供しているが、4.0からデフォルトで適用されるクラスは、
WebExpressionVoterに統一されている - WebExpressionVoterはSpring Expression Languageを使用して、利用者が持つ、権限情報とリクエスト情報を参照して投票を行う実装クラス
9.5.2 アクセスポリシーの記述方法
-
SpringSecurityは、アクセスポリシーを指定する記述方法として、SpringExpressionLanguage(SpEL)をサポート
-
SpELを使わない方法もあるが、本書では、Expressionを使ってアクセスポリシーを指定する方法で解説する
-
CommonExpressions
- ※割愛
9.5.3 Webリソースへの認可(JavaConfig編)
-
JavaConfigを使用して、Webリソースに対してアクセスポリシーを定義する方法について説明
-
アクセスポリシーを適用するWebリソースの指定
- まずは、アクセスポリシーを適用するリソースを指定
- アクセスポリシーを適用するリソースの指定は、ExpressionInterceptUrlRegistryクラスの以下のメソッドを呼び出して行います。
- antMatchers
- regexMatchers
- requestMatchers
- anyRequests
@Override public void configure(HttpSecurity http) throws Exception { // http.addFilter(this.preAuthenticatedProcessingFilter()); // http.formLogin(); http.authorizeRequests() .antMatchers("/", "/find", "/login", "/signup", "/error", "/login-error").permitAll() // ★記載順には気をつける必要がある .antMatchers("/admin/accounts/***").hasRole("ACCOUNT_MANAGER") .antMatchers("/admin/***").hasRole("ADMIN") .anyRequest().authenticated()- アクセスポリシーの指定
- 次に、アクセスポリシーを指定します。
- アクセスポリシーの指定は、AuthorizedUrlクラスのメソッドを使用して行います。
http.authorizeRequests() .antMatchers("/", "/find", "/login", "/signup", "/error", "/login-error").permitAll() // .antMatchers("/admin/accounts/***").hasRole("ACCOUNT_MANAGER") // .antMatchers("/admin/***").hasRole("ADMIN") .antMatchers("/admin/***").access("hasIpAddress('127.0.0.1') and hasRole('CONFIGURATION_MANAGER')") .antMatchers("/admin/***").hasRole("ADMIN") .anyRequest().authenticated()9.5.4 Webリソースへの認可(XMLファイル編)
- 割愛
9.5.5 メソッドへの認可
-
SpringSecurityは、SpringAOPの仕組みを利用して、アプリケーションコンテキスト内で管理しているBeanのメソッド呼び出しに対して認可処理を行う仕組みを提供しています。
- メソッドに対応する認可処理を使用すると、メソッドの引数や戻り値のオブジェクトの状態を参照できるため、よりきめ細かいアクセスポリシーの定義が行える
- メソッドへの認可を使用する場合は、メソッド呼び出しに対して認可処理を行うためのコンポーネント(AOP)を有効にしてから、アクセスポリシーをクラスやメソッドのアノテーションに定義します。
- つまり
- AOP有効化
- メソッドにアノテーション定義
- つまり
-
SpringSecurityがサポートしているアノテーションは以下
- @PreAuthorize,@PostAuthorize,@PreFilter,@PostFilter: SpringSecurityのアノテーション
- @Secured: SpringSecurityのアノテーション
- JSR250(javax.annotation.securityパッケージ)のアノテーション(@RolesAllowedなど)
-
本書では、アクセスポリシーの指定にExpressionを使用することができる
@PreAuthorizeと@PostAuthorizeを紹介する -
メソッド認可の有効化
- まず、メソッドに対して認可処理を行うAOPを有効化します
@EnableGlobalMethodSecurity(prePostEnabled = true) // メソッドに対して認可処理を行うAOPを有効化 prePostEnabled属性にtrueを指定すると、Expressionを使用してアクセスポリシーを定義することができるアノテーションが有効になるpublic class AppSecurityConfig extends WebSecurityConfigurerAdapter {}- メソッド実行前に適用するアクセスポリシーの指定
- メソッドの実行前に適用するアクセスポリシーを指定する場合は
@PreAuthorizeを使用する @PreAuthorizeのvalue属性に指定したExpressionの結果がtrueになるとメソッドの実行が許可される
- メソッドの実行前に適用するアクセスポリシーを指定する場合は
// 管理者以外の人間が他人のアカウント情報にアクセスできないように定義している@PreAuthorize("hasRole('ADMIN') or (#username == principal.username)")public Account findOne(String username) { return accountRepository.findOne(username);}-
ここでポイントになるのが、Expressionの中からメソッドの引数にアクセスしている部分
-
具体的には、#usernameの部分が引数にアクセスしている部分になる
-
Expression内で「# + 引数名」形式のExpressionを指定するとメソッドの引数にアクセスすることができる
-
メモ
- SpringSecurityは、クラスに出力されているデバッグ情報から引数名を解決する仕組みになっているが、
- アノテーション(
@P)を使用して明示的に引数名を指定することもできる - 以下の場合は、明示的に引数名を指定してあげる必要がある
- 引数のデバッグ情報を出力しない
- Expressionの中から実際の引数名とは別の名前を使ってアクセスする(例:短縮した名前)
@PreAuthorize("hasRole('ADMIN') or (#username == principal.username)")public Account findOne(@P("username") String username) { return accountRepository.findOne(username);}-
JavaSE8から追加されたコンパイルオプション(-parameters)を使用すると、メソッドパラメータにリフレクション用のメタデータが生成されるため、アノテーションを指定しなくても引数名を解決してくれる
-
メソッド実行後に適用するアクセスポリシーの指定
@PostAuthorizeを使用する
@PreAuthorize("hasRole('DEPARTMENT_MANAGER')")@PostAuthorize("(returnObject == null) or (returnObject.departmentCode == principal.account.departmentCode)")public Account findOne(@P("username") String username) { return accountRepository.findOne(username);}9.5.6 JSPの画面項目への認可
SpringSecurityはJSPタグライブラリを使用してJSPの画面項目に対して認可処理を適用することができる
- ※管理者の場合、この項目を表示するなどの制御
- このURLへ遷移させるなどの制御
9.5.7 認可エラー時のレスポンス
-
SpringSecurityはリソースへのアクセスを拒否した場合、以下のような流れでエラーハンドリング、レスポンスを行う
- SpringSecurityは、リソースやメソッドへのアクセスを拒否するために、AccessDeniedExceptionをスローする
- ExceptionTranslationFilterクラスは、AccessDeniedExceptionを捕捉し、AccessDeniedHandlerまたは、AuthenticationEntryPointインターフェースのメソッドを呼び出してエラー応答を行う
- 認証済みのユーザーからのアクセスの場合は、AccessDeniedHandlerインターフェースのメソッドを呼び出してエラー応答を行う
- 未認証ユーザーからのアクセスの場合は、AuthenticationEntryPointインターフェースのメソッドを呼び出してエラー応答を行う
-
AccessDeniedHandler
-
AuthenticationEntryPoint
-
認可エラー時の遷移先
http.exceptionHandling().accessDeniedPage("/accessDeniedError");- デフォルト動作のカスタマイズ
- TODO: 認証エラー時にメッセージを出す処理を作るときに記述しないと行けない気がする
9.6 CSRF対策
9.6.1 Spring SecurityのCSRF対策
- Spring Securityはセッション単位にランダムなトークン値(CSRFトークン)を払い出し、払い出されたCSRFトークンをリクエストパラメータ(HTMLフォームのhidden項目)として送信することで、そのリクエストが正規のWebページからなのか、それとも攻撃者が用意したWebページからなのかを判断する機能がある
- SpringSecurityのデフォルト実装では、POST,PUT,DELETE,PATCHのHTTPメソッドを使用したリクエストに対して、CSRFトークンチェックを行います
9.6.2 CSRF対策機能の適用
- CSRF対策機能はSpring3.2から追加された機能で、SpringSecurity4.0からデフォルトで適用されるようになりました。
- そのため、CSRF対策機能を有効にするための特別な定義はありません。
- なおCSRF機能を適用したくない場合は、明示的に無効にする必要がある
@Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable(); }-
HTMLフォーム使用時のトークン値の連携
- JSPでの設定を紹介しているため割愛
- Thymeleafだとこんな感じhttps://qiita.com/nenokido2000/items/22a97a26a5858ddb164f
-
Ajax使用時の連携
- Ajaxを使ってリクエストを送信する場合は、SpringSecurityから提供されている
<sec:csrfMetaTag>要素を使用して、HTMLの<meta>要素としてCSRFトークンの情報を出力し<meta>要素から取得したトークン値をAjax通信時のリクエストヘッダーに設定して連携します。 - Thymeleafの場合、以下を参考に、Cookieからcsrfトークンを取り出して、詰めて送る
- https://qiita.com/nenokido2000/items/22a97a26a5858ddb164f
- CookieにCSRFトークンを詰める処理は以下で行えるようになっている
http.csrf().csrfTokenRepository(new CookieCsrfTokenRepository());
- Ajaxを使ってリクエストを送信する場合は、SpringSecurityから提供されている
9.6.3 トークンチェックエラー時のレスポンス
-
CSRFトークンチェックでエラーが発生した場合、SpringSecurityはAccessDeniedHandlerインターフェースを使用してエラーのレスポンスを行う
-
CSRFトークンチェックでエラーが発生したときに専用のエラーページに遷移させる場合は、SpringSecurityから提供されているDelegatingAuthenticationFailureHandlerクラスを利用して、それぞれの例外にAccessDeniedHandlerインターフェースの実装クラスを指定してください
-
CSRFトークンチェックで使用される例外クラス
- InvalidCsrfTokenException
- MissingCsrfTokenException
- TODO: この例外処理を実装する必要がある XMLで記載している例はあるけど、Javaで書いている例がない..
9.6.4 CSRF対策機能とSpringMVCとの連携
- 自動でFormにcsrfトークンが入りますよという説明
9.7 セッション管理
9.7.1 セッション管理機能の適用
- セッション管理機能を使用するには、以下のようなBean定義を行う
@Override public void configure(HttpSecurity http) throws Exception { http.sessionManagement(); }-
sessionManagementメソッドを呼び出し、SessionManagementConfigurerのインスタンスを取得する
-
SessionManagementConfigurerには、セッション管理機能のコンポーネントの動作をカスタマイズするためのメソッドが定義されている。なお、WebSecurityConfigurerAdapterを継承して、コンフィギュレーションクラスを作成している場合は、sessionManagementメソッドは親クラスの処理で呼び出されるため、デフォルトでセッション管理機能が適用されている
-
RESTAPIなどセッションを使用しない場合は、セッションの作成方式を stateless に変更する必要がある
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)- セッションの作成方式は以下のオプションから選択することができる
- always
- ifRequired (デフォルト)
- never
- stateless
9.7.2 URL Rewriting抑止機能
URL Rewritingが行われると、URL内にセッションIDが露出してしまうため、セッションIDを盗まれるリスクが高くなります。
SpringSecurityでは、URL Rewritingを抑止するための仕組みも提供しており、この機能はSpringSecurity4.0以上ではデフォルトで適用されます。
- TODO: Cookieを許可しない設定になっているとどのような挙動になる?クエリに埋め込まれない?
9.7.3 セッション固定攻撃対策機能
-
セッション管理機能を適用すると、デフォルトでセッション固定攻撃機能が有効になる
-
セッション固定攻撃対策機能を使用すると、ログイン成功時に新たセッションIDを払い直すため、攻撃者が事前に払い出したセッションIDが使われることはありません。
-
セッション固定攻撃への対策オプション
- changeSessionId: Servlet3.1で追加されたHttpServletRequest#changeSessionIdメソッドを使用してセッションIDを変更する(Servlet3.1以上のコンテナでのデフォルト動作)
- migrateSession: ログイン前に使用していたセッションを破棄し、新たにセッションを作成する。ログイン前にセッションに格納されていたオブジェクトは新しいセッションに引き継がれる
- newSession: migrateSessionと同じ方法でセッションIDを変更するが、ログイン前に格納されていたオブジェクトは新しいセッションには引き継がれない
- none: SpringSecurityはセッションIDを変更しない
-
指定方法
http.sessionManagement().sessionFixation().newSession();9.7.4 多重ログインの制御
-
SpringSecurityは同じユーザー名(ログインID)を使った多重ログインを制御する機能を提供しているが、
-
SpringSecurityが提供しているデフォルト実装にはいくつかの制約や注意事項がある
-
本書では、これらの制約と注意事項について紹介するが、具体的な使い方は扱わない、リファレンス参照
-
SpringSecurityが提供しているデフォルト実装では、ユーザー毎にセッション情報をアプリケーションサーバーのメモリ内で管理します。
-
そのため、複数のアプリケーションサーバーを同時に実行するシステムでは利用することができません。
-
また、アプリケーションサーバーを停止または再起動するとメモリ内で管理していたセッション情報はクリアされます。
-
使用するアプリケーションサーバーによっては、停止または再起動時のセッション状態を復元する機能を持っているため、実際のセッション状態とSpringSecurityが管理しているセッション情報に不整合が生じる可能性がある
-
TODO: Redisで管理する方法探る
9.7.5 無効なセッションを使ったリクエストの検知
- SpringSecurityは無効なセッションを使ったリクエストを検知する機能を提供している
- 無効なセッションとして扱われるリクエストの大部分は、セッションタイムアウト後のリクエストです。
- 以下の例では、無効なセッションを検知した際の遷移先として
"/error/invalidSession"を指定することで、この機能を有効化している
http.sessionManagement().invalidSessionUrl("/error/invalidSession");9.8 ブラウザのセキュリティ対策機能との連携
- ブラウザが提供するセキュリティ対策機能の一部は、サーバー側で、HTTPのレスポンスヘッダーを出力することで動作を制御することができる
9.8.1 セキュリティヘッダー出力機能の適用
- Spring3.2から追加された機能で、Spring4.0からデフォルトで適用されるようになった
// 無効にする方法 http.headers().disable();9.8.2 デフォルトでサポートしているセキュリティヘッダー
-
SpringSecurityがデフォルトでサポートしているレスポンスヘッダーは以下の5つ
- Cache-Control(Pragma, Expires)
- コンテンツのキャッシュ方法を支持するヘッダー
- 保護されたコンテンツがブラウザにキャッシュされないようにすることで、権限のないユーザーが保護されたコンテンツを閲覧できてしまうリスクを減らすことができる
- X-Frame-Options
- フレーム(
<frame>または<iframe>要素)内でコンテンツの表示を許可するか否かを支持するためのヘッダー - フレーム内でコンテンツが表示されないようにすることで、クリックジャッキングと呼ばれる攻撃手法を使って機密情報を盗み取られるリスクを無くすことができる
- フレーム(
- X-Content-Type-Options
- コンテンツの種類の決定方法を指示するためのヘッダー
- 一部のブラウザでは、Content-Typeヘッダーの値を無視して、コンテンツの内容を見て決定します。
- コンテンツの種類を決定する際にコンテンツの内容を見ないようにすることで、クロスサイトスクリプティングを使った攻撃を受ける可能性をへらすことができます。
- X-XSS-Protection
- ブラウザのXSSフィルタ機能を使って有害なスクリプトを検知する方法を支持するためのヘッダー
- XSSフィルタ機能を有効にして有害なスクリプトを検知するようにすれば、クロスサイトスクリプティングを使った攻撃を受ける可能性をへらすことができる。
- Strict-Transport-Security
- HTTPSを使ったアクセスをしたあとに、HTTPを使ってアクセスしようとした際に、HTTPSに置き換えてからアクセスすることを支持するためのヘッダー
- HTTPSでアクセスした後に、HTTPが使われないようにすることで、中間者攻撃と呼ばれる攻撃手法を使って悪意のあるサイトに誘導されるリスクをへらすことができる
- Cache-Control(Pragma, Expires)
-
TODO: 攻撃手法と対策について再度整理する必要あり
9.8.3 セキュリティヘッダーの選択
- 出力するセキュリティヘッダーを選択したい場合は、以下のようなBean定義を行います。
- ここではSpringSecurityが提供するすべてのセキュリティヘッダーを出力する例になっていますが、実際は必要なものだけ指定する
http.headers() .defaultsDisabled() .cacheControl().and() .frameOptions().and() .contentTypeOptions().and() .xssProtection().and() .httpStrictTransportSecurity();- 不要なものだけ無効化する方法もある
9.9 Spring Securityのテスト
- MockMvcを使用して「認証処理」や「認可処理」などのテストを行うための機能
- テスト時に適用する認証情報をアノテーションで指定できる機能
9.9.1 Spring Security Testのセットアップ
- 依存ライブラリーの追加
testImplementation 'org.springframework.security:spring-security-test'-
SpringSecurityのサーブレットフィルタの追加
-
TODO: テスト環境が作れて無いため一旦スキップする
第10章 Spring Data JPA
10.6 Repositoryの作成と利用
10.6.4 ページネーション
- TODO: DOMAにあるかどうか
10.6.6 監査情報の付与
- TODO: DOMAにあるかどうか
第11章 Spring + MyBatis
- XMLに記述しないといけないので採用したくない
第12章 Spring+Thymeleaf
- SpringBootを始め、ViewにJSPではなくテンプレートエンジンであるThymeleafを使用するケースが増えている
- まずはThymeleaf自体や、Springとセットで開発する際に欠かせない連携ライブラリーの概要を紹介
12.1 Thymeleafとは
-
Thymeleafは、Webアプリケーションと親和性の高いテンプレートエンジン
-
テンプレートエンジンとは
- 雛形となるドキュメント(テンプレート)に対して、可変データを埋め込むことで動的にドキュメントを生成する仕組み
- この仕組は、MVCフレームワークのModelとViewを分割する考え方と親和性が高く、しばしばMVCフレームワークのVIEWとし利用される
-
特徴
- XHTMLやHTML5に準拠した形で記述できること
-
JSPはブラウザが認識できないタグライブラリなどが含まれるため、開発中のJSPを直接ブラウザ上で正確に表示させることが難しいという問題があった
-
一方ThymeleafのテンプレートはHTML5に準拠しているため、テンプレートをブラウザで直接表示させたり。HTMLをデザイナーとプログラマの間で共有することができる
-
MEMO: パーツをIncludeする際はうまく表示できない問題はあると思う
12.1.1 Thymeleafのテンプレート
ThymeleafはXHTMLやHTML5などで書かれたテンプレートをDOMに変換してから処理を行う仕組みになっている 「処理対象のDOMノード」と「DOMノードに適用する処理」をthネームスペースの属性(th属性)を使用して指定します。
th属性が指定されているDOMノードは「プロセッサ」と呼ばれるコンポーネントによってDOM操作(追加、削除、変更)が行われる。th属性の属性値には、OGNL(Object-Graph Navigation Language)と呼ばれる式言語を指定でき、式の中から、ユーザー定義のオブジェクトやThymeleafが提供する暗黙オブジェクトにアクセスすることができる
-
以下3つのことをDialectと呼ぶ
- DOM操作を行うプロセッサ
- th属性の属性値に指定された式を解釈するコンポーネント
- 暗黙オブジェクトを生成するコンポーネント
- →デフォルトでは、StandardDialectクラスが使用される
-
Dialectは拡張可能な仕組みになっており、本書で紹介するthymeleaf-spring4を使う場合は、StandardDiarectクラスを継承した、SpringStandardDialectクラスが使用される
12.1.2 ThymeleafとSpringの連携
-
連携する場合、Thymeleafが提供するthymeleaf-spring4モジュールを利用
-
SpringMVCがJSP向けに提供しているタグライブラリと同様の機能を、Thymeleafで利用することができる
-
実現できる機能
- Thymeleafが管理するテンプレートをSpringMVCのViewとして扱うことができる
- テンプレート内でSpringELを利用することができる
- テンプレートと、フォームクラスおよび入力値チェック結果のバインドが可能となる
- Springが管理するメッセージリソースを利用し、国際化対応のメッセージを表示することができる
12.2 Spring + Thymeleafのセットアップ
12.2.1 ライブラリのセットアップ
- thymeleaf-spring4を入れる
12.2.2 SpringとThymeleafを連携するための設定
- SringBootだと書かなくても動くっぽいけど明示する意味で
@Configuration@Import(ThymeleafConfig.class) // Thymeleafを使用することを明示public class AppConfig implements WebMvcConfigurer {}
@Configurationpublic class ThymeleafConfig {
@Bean public ClassLoaderTemplateResolver templateResolver() { ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver(); resolver.setPrefix("/WEB-INF/templates/"); resolver.setSuffix(".html"); resolver.setTemplateMode("HTML5"); resolver.setCharacterEncoding("UTF-8"); return resolver; }
@Bean public SpringTemplateEngine templateEngine() { SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver()); return templateEngine; }
@Bean public ViewResolver viewResolver() { ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); viewResolver.setTemplateEngine(templateEngine()); viewResolver.setCharacterEncoding("UTF-8"); return viewResolver; }
}12.3 Thymeleafを利用したViewの実装
12.3.1 はじめてのThymeleaf
- Thymeleafを利用したViewへの変更
- テンプレートの実装
12.3.2 テキストの出力
- th
--- 属性値に指定した値をXHTMLサニタイジングして出力する - ユーザからの入力を表示する際はこちら
- th
--- 属性値に指定した値をXHTMLサニタイジングせずに出力する - プロパティファイルでHTML要素を使用して意図的に装飾しているような場合は、th
属性を使用してXHTMLサニタイジングせずに出力する必要がある
- プロパティファイルでHTML要素を使用して意図的に装飾しているような場合は、th
12.3.3 式の構文
- 基本的な式
- 変数式
- 選択変数式
- メッセージ式:これは利用しておきたい
- リンクURL式
- リテラル
- 基本的な演算子
- テキスト演算子
- 条件演算子
12.3.4 th属性による属性値の設定
-
Thymeleafのテンプレートエンジンとしての機能は、th属性に指定された式を解釈し、HTMLの各要素の属性値を設定または上書きすることです。
-
特定の属性に値を設定する方法
-
現在の属性値の前後に値を追加する方法
-
存在有無が重要な属性の出力を制御する方法
-
複数の属性に同じ値を設定する方法:AltとTitleに同じ値を入れたりとか
-
任意の属性に値を設定する方法:独自データ属性に値を設定したい場合に使用する
<button th:attr="data-product-id=${product.id}">削除</button>
12.3.5 HTML要素の出力制御
ここまでth属性を使ってHTML属性の属性値を動的に設定する方法を見てきましたが、 動的にHTML要素の出力を制御する必要が出てくる場合がある
-
例えば、特定の条件下の場合のみメッセージを表示したり、
-
データの件数だけ行を追加して出力したりする場合などです。
-
JSPでは、それらの専用タグライブラリが用意されていましたが、Thymeleafでも相当する機能がth属性として提供されている
-
条件による出力有無の制御
- th
- th
- th
- th
- th
-
繰り返し出力の制御
- th
- java.util.Listの実装クラス
- java.util.Iterableの実装クラス
- java.util.Mapの実装クラス
- 配列
- th
12.3.6 インライン記述
th属性を利用しない方法であるインライン記述について説明
-
[[ ${user.name} ]]でかけるよ -
インライン記述はデフォルトで無効になっている
-
有効にするには、
th:inline属性をインライン記述を利用する要素、もしくは親要素に付与する必要がある -
インライン記述のでメリット
- テンプレートファイルをブラウザ上で直接表示した際に、インライン記述のテキストがそのまま表示されてしまう点
- th
の用にサンプルデータを表示することができないため、デザイナーとの分業に支障をきたす可能性がある
-
メモ
- インライン記述はJavascriptなどのスクリプト内でも利用することができる
- これを利用すると、テンプレートをブラウザで静的に表示した場合や、アプリケーションサーバーにデプロイして動的に表示した場合、その両方においてスクリプトを正常に動作させられる。
-
MEMO: 結論(個人的な)
- インライン記述は原則禁止したほうがよいと思う(コーディング規約などで明記すべき)
12.3.7 コメント
<!-- このブロックはThymeleafの処理後もテンプレートに残ります。 --><!--/* このブロックはThymeleafの処理後に削除される。*/-->12.3.8 Springとの連携
本項では、thymleaf-springが提供しているSpringとの連携機能に焦点を当てる
-
フォームオブジェクトのバインディング
- th属性
- th
属性
-
入力エラーの表示
- SpringMVCの入力チェック機能で発生したエラーの表示は、
- th
属性 --- エラーメッセージの出力対象を指定するための属性 - th
属性 --- エラー時に適用するCSSを指定するための属性 - th
オブジェクト --- エラー情報にアクセスするための便利なメソッドを提供するオブジェクト - を使用して行う
- th
- SpringMVCの入力チェック機能で発生したエラーの表示は、
-
TODO: エラー処理について設計する必要あり
-
SpELの利用
- Thymeleafは数式をOGNLとして解釈しますが、thymeleaf-springを利用すると、
- 変数式はSpELとして解釈されます。
- これにより、テンプレート内から、DIコンテナ内に登録されているBeanにアクセスできるようになる
<span th:text="${@appSettings.passwordValidDays}">60</span><!-- @Bean名でアクセスできる -->- ConversionServiceとの連携
- SpringMVCに適用されているConversionServiceと連携して、値の型変換を行うことができる
public class AppSettings implements Serializable { @Value("${vasicPostage:1250}") @NumberFormat(style = NumberFormat.Style.NUMBER) private int basicOneDayCost;}<span th:text="${@appSettings.basicOneDayCost}">1300</span>円<span th:text="${{@appSettings.basicOneDayCost}}">1300</span>円12.3.9 テンプレートの共通化
-
テンプレートのフラグメント化
- 共通な内容を別ファイルに切り出す
-
テンプレートのレイアウト化
- 複数のテンプレートで同じデザインレイアウトを適用する場合は、通常、共通的なレイアウトを定義して共有することになる。このような場合に有効なライブラリとして、
Thymeleaf Layout Dialectがある
- 複数のテンプレートで同じデザインレイアウトを適用する場合は、通常、共通的なレイアウトを定義して共有することになる。このような場合に有効なライブラリとして、
-
テンプレートのフラグメント化
- テンプレートの一部を分割して別ファイルに切り出すことができる
- ヘッダー、フッター、メニューがフラグメントとしてよく利用されるが、特定のUIコンポーネントをフラグメントとして切り出すことも可能
- フラグメントの利用方法
- フラグメントの定義と参照という2つの作業が必要になる
- 定義方法には以下の2つの方法がある
- Thymeleafのth
属性を利用したフラグメント定義 ★個人的にこっちがわかりやすくていいと思う - CSSセレクタと同様、id属性を利用したフラグメント定義
- Thymeleafのth
- 定義したフラグメントを読み込む方法として以下の2つ
- Thymeleafのth
属性を利用したフラグメントのインクルード - Thymeleafのth
属性を利用したフラグメントのち缶
- Thymeleafのth
- フラグメントの利用方法
-
テンプレートのレイアウト化
- Thymeleaf Layout Dialectを利用したテンプレートのレイアウト化について説明
-
セットアップ
thymeleaf-layout-dialectのリポジトリ追加- コンフィギュレーションクラスの実装
@Bean public SpringTemplateEngine templateEngine() { SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setDialect(new LayoutDialect()); templateEngine.setTemplateResolver(templateResolver()); return templateEngine; }-
Thymeleaf Layout DialectによるViewの実装
- 共通レイアウトとなるテンプレートを「Decorator」
- 共通レイアウトを適用する側の個別のテンプレートを「Fragment」と呼ぶ
-
MEMO: 画面の全体像がわかりづらくなりやすいので、この方法は採用したくないため割愛
12.3.10 SpringSecurityとの連携
-
SpringSecurityが提供する画面表示に関する機能をThymeleafで利用するためには、
-
SpringSecurityDialectを利用します。
-
以下の機能
- 認証情報にアクセスする機能を持つ sec
属性を提供 - SpringSecurity expression を利用した認可処理と同等の機能を持つ sec
属性を提供 - URLベースの認可処理を行う sec
属性を提供する - ACL(Access Control List)を利用した認可処理を行う sec
属性を提供 - CSRFトークンにアクセスする機能を提供
- 認証情報にアクセスする機能を持つ sec
-
SpringSecurityDialectのセットアップ
thymeleaf-extras-springsecurity4
-
認証情報へのアクセス
<!doctype html><html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.ort/extras/spring-security" th:with="title=トップページ"> <span sec:authentication="principal.username">テストユーザ名</span>-
画面項目への認可
- 適用する画面項目とアクセスポリシーの指定
sec:authorize="hasRole('ADMIN')"属性を付与- Trueの場合、HTMLを表示
- Webリソースとして指定したアクセスポリシーとの連動
sec:authorize-url="/admin/accounts"にアクセスできることを条件として表示非表示を設定している
- 適用する画面項目とアクセスポリシーの指定
-
CSRFトークンへのアクセス
- Spring Security Dialectを適用するだけでOK hidden項目としてHTMLに埋め込まれます
- ajax通信の際は取得してあげる必要がある
- headerのmeta要素に設定しといて、JSで取得してあげる感じにする
- TODO: 取得してPOSTする処理は実装考えてみる
- headerのmeta要素に設定しといて、JSで取得してあげる感じにする
12.3.11 JSR 310: Date and Time APIの利用
-
Thymeleafは、テンプレート内でJSR310
and TimeAPIのオブジェクトを操作するための機能を標準ではサポートしておらず、Thymeleafが提供する拡張ライブラリが必要になる -
依存ライブラリの追加
- thymeleaf-extras-java8timeを利用する
- なお、thymeleaf-extras-java8timeはSpringIO Platformで管理されていないため、バージョンの指定が必要
-
Bean定義の追加
@Bean public SpringTemplateEngine templateEngine() { SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setDialect(new Java8TimeDialect()); templateEngine.setTemplateResolver(templateResolver()); return templateEngine; }- ユーティリティオブジェクト(temporals)を利用
<title th:text="|${#temporals.format(date, 'yyyy/mm/dd')}の会議室|">2020/05/19の会議室</title>第13章 Spring Boot
機能が豊富な事による課題がある どう組み合わせていいかわからなかったり、ちょっとしたWebアプリケーションを作成するだけでも、数多くの設定が必要であったりする点です。
SpringBootを利用することでこられの課題を解決することができる
13.1 SpringBootとは
-
SpringBoot
- 2013年 :開発開始
- 2014年4月:1.0リリース
- 2016年6月:1.3.5
- 2020年5月:2.3.0
-
SpringBootを使えば、何も設定しなくてもデフォルトで様々な機能が利用可能
-
XMLまたはJavaConfigによるBean定義、ログの設定、Servletの設定などが不要になる
-
アプリケーションサーバーをデプロイする必要もなくなり、Javaのmainメソッドを実行すればアプリケーションを実行できる
13.1.1 SpringBootで作るHello Worldアプリケーション
13.1.2 AutoConfigureに夜自動設定
- 自動設定の詳細を知りたい場合は、spring-boot-autoconfigureプロジェクトの◯◯AutoConfigurationクラスのソースを読むことをおすすめします。
13.1.3 Starterによる依存ライブラリの解決
- starterライブラリの説明
13.1.4 実行可能jarの作成
- 実行可能jarとしてパッケージングされる
13.2 SpringBootでSpringMVC
13.2.1 RESTful Webサービスの作成
13.2.2 画面遷移型アプリケーションの作成
-
SpringBootの自動設定では、TemplateResolverがビュー名につけるプレフィックスとサフィックスのデフォルト値は、
-
それぞれ、
classpath:/templates/と.htmlになる -
テンプレートからメッセージを取得する方法
<title th:text="#{app.title}"> <!-- ${変数名}ではなく#{メッセージキー名}-->13.3 SpringBootでデータアクセス
13.3.1 SpringJDBC
-
これまで必要だったがSpringJDBCでは不要な定義
- データソースの定義
- トランザクションマネージャの定義
- JdbcTemplateの定義
-
SpringBootのデフォルトの挙動としてクラスパス直下に
schema.sqlが存在すると起動時にそのSQLファイルを実行
13.3.2 Spring Data JPA
13.3.3 MyBatis
13.3.4 コネクションプールライブラリの変更
-
SpringBootではDatasourceを定義する必要はなく、自動で生成されます。コネクションプーリングの仕組みも自動で決まり、以下のライブラリのうちクラスパス上にあるものが利用されます。
- TomcatJDBC
- HikariCP
- Commons DBCP
- Commons DBCP2
-
TODO: HikariCPが一番速いとのこと
13.4 SpringBootでSpring Security
- Spring Security用のプロジェクトも当然用意されています。
13.4.1 Basic認証
- デフォルトで有効になるらしい
- デフォルトユーザーはuser
- パスワードはランダムに設定される
13.4.2 認証・認可のカスタマイズ
- SpringSecruityの認証・認可の設定は
org.springframework.security.config.annotation.web.configuration.WebSecrityConfigurerAdapterを用いて行う
13.5 Spring Bootで型安全なプロパティ設定
application.propertiesから値を取得できる
- TODO: EnumによるConst管理とどっちがいいか
- メッセージのとり方確認
- 定数管理方法のベストプラクティス
13.5.1 @ConfigurationPropertiesを用いたプロパティの設定
-
プロパティを多用するSpringBootでは、安全にプロパティを扱うための仕組みとして、
-
@ConfigurationPropertiesアノテーションが用意されている -
TODO: プロパティ管理はこれが良さそう
13.5.2 Bean Validationに夜プロパティ値のチェック
- プロパティ値も、起動時にBindされる際、
@NotEmptyなどで値の検証ができる
13.5.3 IDEによるプロパティの補完
-
@ConfigurationPropertiesを用いて定義したプロパティはIDEで補完が効く -
補完させるためにプロパティのメタ情報を生成する必要がある
-
spring-boot-configuration-processorの説明
-
SpringBootでプロパティを外部化する際は積極的に使用していくとよい
13.6 SpringBootAcruatorで運用機能強化
-
SpringBootでは開発を容易にする機能が提供されるだけでなく、アプリケーションの運用面を考慮した機能も提供されている
-
これを提供するのがSpringBootActuator
-
これだけでアプリケーションの状態を検査するためのエンドポイント(HTTP,JMX,SSH)が追加されたり、ヘルスチェック機能やメトリクス取得機能が有効になる
-
TODO: 有効にして活用したい
13.6.1 HTTPエンドポイントの追加
-
HTTPエンドポイント
- /autoconfig
- /beans
- /env
- /configprops
- /dump
- /health
- /info
- /logfile
- /metrics
- /mappings
- /shutdown
- /trace
- /flyway
- /liquibase
-
エンドポイントのコンテキストパスやポート番号などはプロパティを使用して変更できる
management.context-path=/managemanagement.port=8081management.address=127.0.0.1 # localhostからのみアクセスを許可する
# 個別のエンドポイントの有効無効を次のプロパティで設定endpoints.shutdown.enabled=trueendpoints.mapping.enabled=falseendpoints.trace.enabled=false
# エンドポイントはHTTPだけでなくJMXでもアクセスできる これらの無効にもできるmanagement.port=-1 # HTTPエンドポイントを無効にするendpoints.jmx.enabled=false # JMXエンドポイントを無効にする13.6.2 ヘルスチェック
- SpringBootActuatorはヘルスチェック機能を持っている
13.6.3 メトリクス
-
SpringBootActuatorはメトリクス取得機能も備えている
-
次の2つのメトリクスがサポートされている
- gauge: 絶対値を記録する
- counter: 差分値を記録する
-
TODO: メトリクスは収集して管理できる用にしておく
第14章 チュートリアル
- 会議室予約システムを作成していく手順を記載