Spring Securityがアクセスをはねると@ExceptionHandlerで処理できない話(だがSpring Bootは処理する)


はじめに

最近Springに触れる機会がありました。Javaなんて5より前の時代でしかまともにプログラム書いたことない化石なのでアノテーションとかメソッドチェーンとか1「現代的なJava」をQiitaの先人方の記事を参考に習得させていただきました。

で、作ろうとしたシステムですが以下のような感じです。

  • ユーザ認証を行う。これはSpring Security使えばよいようだ。
  • ロールも使って特定ページには管理者しかアクセスできないようにする。
  • エラーページはSpring Bootが出してるようだけどデフォルトのままじゃもちろん駄目なのでエラーハンドリングを行う。

で、最後のエラーハンドリングの方法を調べたところ@ExceptionHandlerアノテーションを付ければいいということがわかりました。というところまでが前置き。

なお今から説明するコード(状況再現コード)の全体はgithubに置いときました。
https://github.com/junjis0203/spring-security-boot-error-example

とりあえず@ExceptionHandler使ってみる

@ExceptionHandlerアノテーション付けて、戻り値はStringでビュー名返すようにしてました。

CommonControllerAdvice.java
    @ExceptionHandler
    @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
    public String handle(Exception e) {
        logger.error(e.toString());
        return "error";
    }

管理者権限与えてないユーザでアクセスして、ちゃんと返してるビューが表示されていることを確認。

さてExceptionと大味に捕まえてしまったが実際どの例外が・・・
あれ?(。´・ω・)?ログが出てない。ログ設定おかしいのかな。

Spring Security「わいやで」

???「一体いつから、エラーハンドリングできていると錯覚していた」
さわだ「お前は!?」

Spring Security「そのリクエストは私がフィルタした。つまり、コントローラには届いていない」
さわだ「な、なんだってー!では何故自分で書いたビューが表示されるのだ」

Spring Boot「わいやで」

Spring Boot「あ、それやってるのわい」
さわだ「え?」
Spring Boot「ここに書いてあるで。ざっくり訳すと、サーブレットコンテナのグローバルなエラーページとして登録されてます、となる。つまり、Spring Securityはんがはねたエラーでもわいはハンドリングできるんや」
さわだ「error.html使うなんて書いてないぞ」
Spring Boot「ソースコード参照」

解決策

というわけで、Spring Securityを使ってアクセス制御したところ、権限エラーを捕まえることができない、しかしSpring Bootは捕まえられるということがわかりました。
私としては「何故権限エラーがハンドリングされないのか」と「何故ハンドリングされてないのに自分で書いたページが表示されるのか」さえ明確になればよかったのでerror/403.htmlを置いてアクセス権がないことを知らせるようにしました。

あとがき

ちょっと前に書いたSinatraの話など、個人的に「プログラミング記法」に関する話題は大好きなのでその点Springはとてもおもしろいですね。
また、「でこれ結局どうやって動いてるの?」とコードを読むのも大好きなのですが、Springの森は深過ぎですね。本体にたどり着く前にインターフェースと抽象クラスが何個あることか。でも別件でソース探索を少ししておいたおかげで今回のBasicErrorControllerへは比較的早くたどり着くことができました(笑)


  1. JUnit4は見たことあるのでアノテーションをばりばり使ったプログラミングについてはそこそこ知見がありました。