saba1024のブログ

どうでも良い思いついた事とかプログラミング関係のメモとか書いていきます。

Grails 3.2.8と3.2.9でSpring Security CoreとDatabase Migrationを利用する際の注意点

前提条件

Spring Security Coreラグインのバージョンは3.1.2です。
Database Migrationプラグインのバージョンは3.0.0です。
また、認証用のドメインを作成するs2-quickstartコマンドを以下のように実行したと仮定します。

s2-quickstart example User Role

これで、UserRoleUserRoleというドメインが生成されます。

Grails 3.2.8の場合

Spring Security Coreプラグイン

デフォルトでは正しく動きません
というのも、Grails3.2.8からドメイン内のプロパティに対してDIがデフォルトで動かないようになったためです。
これはパフォーマンスのための措置とのことです。
通常のアプリケーションであれば特に問題になることはないと思いますが、Spring Security Coreラグインを利用している場合、UserドメインUser.groovy)にSpringSecurityService springSecurityServiceというプロパティが設定されています。
今までは、Userドメインsaveする際にUser#encodePassword()が呼ばれて、その中でspringSecurityServiceを使って自動的にパスワードを暗号化してくれていました。
しかしGrails 3.2.8からはspringSecurityServiceがDIされていないためnullになっており、それが原因でパスワードは暗号化されずに平文のままDBに保存される状態になっています。
ログインページからログインしようとした場合、入力したパスワードは自体は本来の暗号化アルゴリズムで暗号化されてDBに問い合わせされるので、当然一致するパスワードが無いのでログイエラーとなってしまいます。
この問題の解決するためには、GORMのDIを以前と同じように有効化する必要が有ります。
具体的には、application.ymlで、autowiretrueにするだけです。

grails:
    gorm:
        autowire: true #これをfalseからtrueへ。もしなければ単に追加すればOK。

Grailsを3.2.7から3.2.8にアップグレードした場合、既存ユーザのログイン自体は問題なく動作して、この問題は新しいユーザを追加したタイミングで初めて発生します。
非常に気付きづらいので、Spring Security Coreプラグインを利用している場合、3.2.8にアップグレードするのであればこの部分は要注意です。

Database Migrationプラグイン

さらに、データベースがH2(デフォルト)で、Database Migrationプラグインを利用する際にも注意が必要です。
問題は単純で、Userドメインpasswordというカラム名がDatabase Migrationプラグインからうまく利用できません。
起動時にマイグレーションツールを適用(updateOnStart: true)しようとすると、以下のようなエラーが発生します。

grails> run-app
| Running application...

Configuring Spring Security Core ...
... finished configuring Spring Security Core

INFO 17/05/24 13:14: liquibase: Can not use class org.grails.plugins.databasemigration.liquibase.GormDatabase as a Liquibase service because it does not have a no-argument constructor
2017-05-24 13:14:23.099 ERROR --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : 列 "THIS_.password" が見つかりません
Column "THIS_.password" not found; SQL statement:
select this_.id as id1_8_0_, this_.version as version2_8_0_, this_.account_expired as account_3_8_0_, this_.account_locked as account_4_8_0_, this_.date_created as date_cre5_8_0_, this_.enabled as enabled6_8_0_, this_.last_updated as last_upd7_8_0_, this_."password" as pa
ssword8_8_0_, this_.password_expired as password9_8_0_, this_.username as usernam10_8_0_ from user this_ where this_.username=? [42122-193]
2017-05-24 13:14:23.184 ERROR --- [           main] o.s.boot.SpringApplication               : Application startup failed

解決策として、User.groovyに自動生成されるmappingの部分でDBのカラム名を以下のように変更します。

static mapping = {
    // これは元々あったやつ。コレがマズイ。(H2)
    //password column: '`password`'
    //コレを追加
    password column: 'passwd'
}

これで、再度dbm-gorm-diffでGORMから差分適用用のchangelogを生成しなおせばちゃんとマイグレーションが動作します。

Grails 3.2.9の場合

Spring Security Coreプラグイン

Spring Security Coreプラグインを利用すること自体には特に問題はありません。

Database Migrationプラグイン

もしもDatabase Migrationプラグインも利用するなら要注意です。
というよりも、結論としてはDatabase MigrationプラグインとSpring Security Coreラグインを同時に利用するのであれば、Grails 3.2.9は見送ったほうが良いと思います。
Hibernate5との絡みの問題らしくて、既にフィックスしたという情報も有りますが少なくとも私の環境では動作しませんでした。

これはGrails 3.2.9がデフォルトで利用するGORMのバージョンが6.0.10.RELEASEになったために発生します。
このGORMのバージョンからs2-quickstartコマンドで生成するUser.goorvyには、パスワードの暗号化用のメソッドencodePassword()が記載されなくなっています。
その代わりに、新しいsrc/groovy/example/UserPasswordEncoderListener.groovyというファイルが生成されます。このファイルの中に暗号化用の処理が移動されたようです。
そして、resources.groovyにそのUserPasswordEncoderListenerインスタンスを生成する以下のコードが1行挿入されます。

userPasswordEncoderListener(UserPasswordEncoderListener, ref('hibernateDatastore'))

冒頭にも述べましたが、Spring Security Coreプラグイン自体は正しく動作するので、特に問題ありません。
また、最初のGrails 3.2.8のDI問題を解決するためにこの仕組みが導入されたわけなのでSpring Security Coreプラグインとしては正当な進化となります。
しかし、残念ながらDatabase Migrationプラグインと同時に利用することが出来ません。

この状態でDatabase Migrationプラグインのコマンド(dbm-*)を実行すると、以下のようなエラーが発生します。

***************************
APPLICATION FAILED TO START
***************************

Description:

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

   authenticationUserDetailsService
      ↓
   userDetailsService
┌─────┐
|  hibernateDatastore
↑     ↓
|  userPasswordEncoderListener
└─────┘

resources.groovyに追加したコードからインスタンスの参照なりが循環してしまってエラーになっているようです。
なので、もしもresources.groovyから件の1行のコードを省けばパット見動作はするようになりますが、既に述べたように本来それは平文のパスワードを暗号化するために利用されるものなので、結果的にGrails 3.2.8の時のように平文でパスワードが保存されるようになり、ログインできなくなります。

こちらに関しては、Grails 3.3以降、対応したSpring Security Coreプラグイン(3.2.0系になる予定)などがリリースされるはずですので、そのタイミングで再度確認したほうがいいかなと思います。

まとめ

今回詳細は述べませんでしたが、どうやらGrails 3.2.9だとAsset-Pipelineを利用するとwarに固めでデプロイするとAssetアクセスできないという問題も有るようです。
なので、個人的にはGrails 3.2.9は使わずに、Grails 3.2.8で、application.ymlgrails.gorm.autowiretrueにして様子見をするのが良いと思います。

参考

Grails Spring Security Core Plugin
Grails Database Migration Plugin
Error creating ‘hibernateDatastore’ - circular reference when running plugin commands
runCommand detects a dependency cycle for AbstractPersistenceEventListener Bean
23. Tutorials
Grails 3.2.8 Upgrade Notes.md