1. 概要

Spring Security 5.3では、DSL(ドメイン固有言語)のKotlinバージョンが導入されました

このチュートリアルでは、新しく導入されたKotlin DSLと、それがボイラープレートを削減し、セキュリティを簡潔に構成できるようにする方法について説明します。

2. Spring Security Kotlin DSL

2.1. セキュリティの構成

Springセキュリティアプリケーションでは、WebSecurityConfigurerAdapterクラスがデフォルトのセキュリティ構成を提供します。 このクラスを拡張して、デフォルトのセキュリティ構成をカスタマイズおよびオーバーライドします。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests(authz -> authz
            .antMatchers("/greetings/**").hasAuthority("ROLE_ADMIN")
            .antMatchers("/**").permitAll()
        )
        .httpBasic(basic -> {});
}

上記では、 /greetings パスとそのサブ子を保護するために、役割がADMINとして構成されているユーザーのみを許可しています。 他のすべてのエンドポイントには、すべてのユーザーがアクセスできます。

同等のKotlinDSL構成は次のようになります。

override fun configure(http: HttpSecurity?) {
  http {
    authorizeRequests {
      authorize("/greetings/**", hasAuthority("ROLE_ADMIN"))
      authorize("/**", permitAll)
    }
    httpBasic {}
  }
}

場合によっては、さまざまなエンドポイントに異なるセキュリティ構成を使用したいことがあります。 これは、エンドポイントに複数のWebSecurityConfigurerAdapter実装を提供することで実現されます。

@Order(1)
@Configuration
class AdminSecurityConfiguration : WebSecurityConfigurerAdapter() {
  override fun configure(http: HttpSecurity?) {
    http {
      securityMatcher("/greetings/**")
      authorizeRequests {
          authorize("/greetings/**", hasAuthority("ROLE_ADMIN"))
      }
      httpBasic {}
    }
  }
}

@Configuration
class BasicSecurityConfiguration : WebSecurityConfigurerAdapter() {
  override fun configure(http: HttpSecurity?) {
    http {
      authorizeRequests {
          authorize("/**", permitAll)
      }
      httpBasic {}
    }
  }
}

最初のものは/greetingsエンドポイントとそのサブ子を保護し、もう1つは他のすべてのエンドポイントをカバーします

これを行うには、値1の @Orderを使用して、BasicSecurityConfigurationの前にAdminSecurityConfigurationを適用する必要があることをSpringに通知します。

2.2. ユーザーの構成

構成を検証するには、2人のユーザーが必要です。1人はUSERの役割を持つ通常のユーザーで、もう1人はADMINの役割を持つユーザーです。

@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
  UserDetails user = User.withDefaultPasswordEncoder()
    .username("user").password("password").roles("USER").build();
  UserDetails admin = User.withDefaultPasswordEncoder()
    .username("admin").password("password").roles("USER", "ADMIN").build();
	
  InMemoryUserDetailsManager inMemoryUserDetailsManager 
    = new InMemoryUserDetailsManager();
  inMemoryUserDetailsManager.createUser(user);
  inMemoryUserDetailsManager.createUser(admin);
  return inMemoryUserDetailsManager;
}

KotlinBeansDSLを使用して同じ構成を記述できます。

beans {
  bean {
    fun user(user: String, password: String, vararg  roles: String) =
      User.withDefaultPasswordEncoder().username(user).password(password)
        .roles(*roles).build()
    InMemoryUserDetailsManager(user("user", "password", "USER"), 
	  user("admin", "password", "USER", "ADMIN"))
  }
}

2.3. エンドポイントの構成

別のBeanを作成して、RESTエンドポイントを定義しましょう。

bean {
  router {
    GET("/greetings") {
      request -> request.principal().map { it.name }
        .map { ServerResponse.ok().body(mapOf("greeting" to "Hello $it")) }
        .orElseGet { ServerResponse.badRequest().build() }
    }
  }
}

上記のBean定義では、HTTPGETメソッドを使用して/greetingsルーターを定義しています。 さらに、リクエストからプリンシパルを取得し、そこからユーザー名を取得します。 次に、ログインしたユーザーに挨拶して応答を返します。

3. 保護されたアプリケーションの消費

cURL コマンドは、アプリケーションとの対話に関しては、頼りになるツールです。

まず、通常のユーザーに/挨拶エンドポイントをリクエストしてみましょう。

$ curl -v -u user:password http://localhost:8080/greetings

予想される403Forbiddenを取り戻します。

HTTP/1.1 403
Set-Cookie: JSESSIONID=F0CBE263219CDCEDD28DE2F0C8DE3A75; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 11 Jun 2020 09:43:44 GMT

ブラウザはこの課題を解釈し、簡単なダイアログで資格情報の入力を求めます。

次に、同じリソース( / greetings エンドポイント)をリクエストしましょう。ただし、今回は管理者ユーザーにアクセスしてもらいます。

$ curl -v -u admin:password localhost:8080/greetings

これで、サーバーからの応答は 200OKCookieです。

HTTP/1.1 200
Set-Cookie: JSESSIONID=D1E537C2424467AF426E494610BF950F; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 11 Jun 2020 09:49:38 GMT

ブラウザからは、アプリケーションを正常に使用できます。 実際、主な違いは、すべてのブラウザが HTTP基本認証をサポートし、ダイアログを使用してユーザーに資格情報の入力を求めるため、ログインページがもはや難しい要件ではないことです。

4. 結論

このチュートリアルでは、Spring SecurityKotlinDSLを使用してSpringアプリケーションのセキュリティ面を宣言する方法を説明しました。 さらに、cURLを使用してアプリケーションをテストしました。

いつものように、このチュートリアルのソースコードはGitHubにあります。