kagamihogeの日記

kagamihogeの日記です。

WildFlyでSpringのJTA設定

JTAの実体はWildFlyを使用してSpringの設定を行う。

環境

事前準備

WildFlyOraclePostgreSQLのXAデータソースを作成しておく。

pg_hba.confにmax_prepared_transactionsのコメントアウトを外してとりあえず20にしておく。

max_prepared_transactions = 20

これを設定しておかないと以下のようなエラーになる。

Caused by: org.postgresql.util.PSQLException: ERROR: prepared transactions are disabled
  ヒント: Set max_prepared_transactions to a nonzero value.

コード

動作確認用のspring-mvcとspring-jdbcをpom.xmlに入れる。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.8.RELEASE</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.8.RELEASE</version>
</dependency>

configurationを作る。特にこれといって何の変哲もなく、JTA固有の設定項目としてはtransactionManagerにJtaTransactionManagerを使用している程度。

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages={"kagamihoge.springjtasample"})
public class SpringJtaConfig {
    
    @Bean
    public DataSource postgresqlDataSource() {
        JndiDataSourceLookup l = new JndiDataSourceLookup();
        return l.getDataSource("java:/PostgresXADS");
    }
    
    @Bean
    public DataSource oracleDataSource() {
        JndiDataSourceLookup l = new JndiDataSourceLookup();
        return l.getDataSource("java:/XAOracleDS");
    }
    
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new JtaTransactionManager();
    }
    
    @Bean
    public JdbcTemplate postgresqlJdbcTemplate() {
        return new JdbcTemplate(postgresqlDataSource());
    }
    
    @Bean
    public JdbcTemplate oracleJdbcTemplate() {
        return new JdbcTemplate(oracleDataSource());
    }
}

configuration以降はほとんど今まで通りのコードとやることは変わらない。

JdbcTemplateで適当な更新SQLを二つのRDBMSに対して実行してみる。

@Component
public class SampleComponent {
    
    @Autowired
    private JdbcTemplate postgresqlJdbcTemplate;
    
    @Autowired
    private JdbcTemplate oracleJdbcTemplate;
    
    @Transactional
    public void execute() {
        int i1 = oracleJdbcTemplate.queryForObject("select 1 from dual", Integer.class);
        int i2 = postgresqlJdbcTemplate.queryForObject("select 1", Integer.class);
        System.out.println(i1 + " " + i2);
        
        int j1 = oracleJdbcTemplate.update("insert into t1(c1) values (1)");
        int j2 = postgresqlJdbcTemplate.update("insert into t1(c1) values (1)");
        System.out.println(j1 + " " + j2);
    }
    
    @Transactional
    public void executeAndRollback() {
        int j1 = oracleJdbcTemplate.update("insert into t1(c1) values (1)");
        int j2 = postgresqlJdbcTemplate.update("insert into t1(c1) values (1)");
        
        throw new RuntimeException();
    }
}

これまで通り、@Transactionalアノテーションをつけておけばメソッド実行後にコミットされる。適当な例外をスローすればロールバックが行われる。

メモ

JdbcTemplateは特に何もしなくても動作するが、ORマッパーのライブラリによってはデータソースをTransactionAwareDataSourceProxyでラップする必要がある。

Spring Security Reference 4.2.2のI. Prefaceをテキトーに訳した(2)

http://kagamihoge.hatenablog.com/entry/2017/05/17/234536 の続き。

5.8 Method Security

version 2.0以降のSpring Securityではサービスレイヤーのメソッドにセキュリティを付与する機能が大幅に進化しました。フレームワーク固有の@Securedアノテーションと共にJSR-250アノテーションセキュリティをサポートしています。また、3.0からは新たにexpression-based annotationsも使用可能です。ビーン宣言に付与するintercept-methods要素で単一のビーンにセキュリティを適用したり、AspectJスタイルのポイントカットでサービスレイヤー全体で横断的に複数のビーンにセキュリティを適用できます。

5.8.1 EnableGlobalMethodSecurity

@Configurationインスタンス@EnableGlobalMethodSecurityを付与することでアノテーションベースのセキュリティを有効化できます。例えば、以下はSpring Securityの@Securedアノテーションを有効化しています。

@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
// ...
}

メソッド(もしくはクラスかインタフェース)にアノテーションを付与することでそのメソッドへのアクセスが制限されるようになります。Spring Security固有のアノテーションではメソッドに対する属性を定義します。その属性はAccessDecisionManagerに渡されて何らかの判定が行われます。

public interface BankService {

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();

@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}

JSR-250アノテーションを有効化することも出来ます。

@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class MethodSecurityConfig {
// ...
}

上記は標準ベースで単純なロールベースの制約を適用できますが、豊富なSpring Security固有のアノテーションを使えません。expression-based syntaxを使うには以下のようにします。

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}

Spring Security固有の例と同等なコードは以下のようになります。

public interface BankService {

@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);

@PreAuthorize("isAnonymous()")
public Account[] findAccounts();

@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}

5.8.2 GlobalMethodSecurityConfiguration

場合によっては@EnableGlobalMethodSecurityアノテーションで出来る以上の複雑なオペレーションが必要となるケースがあります。その場合、GlobalMethodSecurityConfigurationを拡張し、そのサブクラスに@EnableGlobalMethodSecurityアノテーションを付与します。例えば、MethodSecurityExpressionHandlerをカスタムしたい場合、以下のような設定にします。

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        // ... 自前のMethodSecurityExpressionHandlerを作成してreturnする ...
        return expressionHandler;
    }
}

オーバーライド可能なメソッドに関する詳細についてはGlobalMethodSecurityConfigurationjavadocを参照してください。

5.9 Post Processing Configured Objects

Spring SecurityのJava Configurationは設定が行われるオブジェクトのすべてのプロパティを公開してはいません。これにより大多数のユーザ向けに設定を簡易化しています。すべてのプロパティが公開されたとして、どちらにせよ一般的なビーン設定を使うことになります。*1

すべてのプロパティへの直接アクセスをさせない理由には正当性がありますが、より高度な設定オプションを使いたいケースも当然ながらあり得ます。Spring SecurityはObjectPostProcessorの概念を導入しており、これによりJava Configurationが生成する多数のオブジェクトのインスタンスを変更あるいは置き換えられます。たとえば、FilterSecurityInterceptorfilterSecurityPublishAuthorizationSuccessプロパティを設定する場合は以下のようになります。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .anyRequest().authenticated()
            .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                public <O extends FilterSecurityInterceptor> O postProcess(
                        O fsi) {
                    fsi.setPublishAuthorizationSuccess(true);
                    return fsi;
                }
            });
}

5.10 Custom DSLs

Spring Securityの自前のDSLを指定できます。たとえば、以下のようなものです。。

public class MyCustomDsl extends AbstractHttpConfigurer<CorsConfigurerMyCustomDsl, HttpSecurity> {
    private boolean flag;

    @Override
    public void init(H http) throws Exception {
        // initメソッドで必要なconfigurerを追加するメソッド
        // があればここに書く。
        http.csrf().disable();
    }

    @Override
    public void configure(H http) throws Exception {
        ApplicationContext context = http.getSharedObject(ApplicationContext.class);

        // ここでApplicationContextからルックアップしている。
        // 新規インスタンスをここで生成可能。
        MyFilter myFilter = context.getBean(MyFilter.class);
        myFilter.setFlag(flag);
        http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
    }

    public MyCustomDsl flag(boolean value) {
        this.flag = value;
        return this;
    }

    public static MyCustomDsl customDsl() {
        return new MyCustomDsl();
    }
}

上記はHttpSecurity.authorizeRequests()の実際の実装に近いです。

自前のDSLは以下のようにして使います。

@EnableWebSecurity
public class Config extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .apply(customDsl())
                .flag(true)
                .and()
            ...;
    }
}

このコードは以下の順序で実行されます。

  • ‘Config'のconfigureメソッドが実行される。
  • ‘MyCustomDsl'のinitメソッドが実行される。
  • ‘MyCustomDsl'のconfigureメソッドが実行される。

なお、SpringFactoriesを使用してWebSecurityConfiguerAdapterMyCustomDslをデフォルトにできます。たとえば、以下の内容をクラスパス下にMETA-INF/spring.factoriesという名前のファイルとして作成します。

META-INF/spring.factories.

org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl

デフォルトを無効化する場合はそれを明示します。

@EnableWebSecurity
public class Config extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .apply(customDsl()).disable()
            ...;
    }
}

6. Security Namespace Configuration

6.1 Introduction

Namespace configurationはSpring Frameworkのバージョン2.0から利用可能となっています。これは従来型のSpring beanアプリケーションコンテキストのシンタックスを追加のXMLスキーマによって補うものです。詳細についてはReference Documentationを参照してください。namespaceの要素を使うことで個々のbeanの設定方法が簡潔でシンプルになり、問題領域と一致させやすい設定のシンタックスを別途定義することで基礎部分の複雑さをユーザから隠蔽します。シンプルな要素でアプリケーションコンテキストに追加される処理ステップと複数のbeanを隠蔽します。たとえば、以下のsecurity namespace要素をアプリケーションに追加すると、アプリケーション内にテスト用の組み込みLDAPサーバが起動します。

<security:ldap-server />

同じことをするApache Directory Serverのbeanをワイヤリングするのに比べればかなりシンプルです。よくある設定のための属性がldap-server要素に用意されており、beanのプロパティ名やbean生成に関しては調べる必要はありません。[1]. アプリケーションコンテキストファイル編集の際には頭の良いXMLエディタを使用すると使用可能な要素と属性をアシストしてくれます。Spring Tool Suiteを推奨し、これはSpring namespacesをサポートする特別な機能があります。

[1] - ldap-server要素の詳細についてはChapter 29, LDAP Authenticationを参照してください。

アプリケーションコンテキストでsecurity namespaceを使うには、まずクラスパスにspring-security-configのjarを置きます。次にアプリケーションコンテキストファイルにスキーマ宣言を追加します。

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/security
      http://www.springframework.org/schema/security/spring-security.xsd">
    ...
</beans>

リファレンス内のサンプルの多く(とサンプルアプリケーション)は、デフォルトnamespaceに"beans"ではなく"security"を使いますが、これは読みやすさのためで、すべてのsecurity namespace要素からプレフィクスを省略するためです。また、アプリケーションコンテキストを別々のファイルに分割し、そのファイルの一つにセキュリティ設定の大半を詰め込みたい場合にもこのようにします。この場合のアプリケーションコンテキストファイルは以下のようになります。

<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/security
      http://www.springframework.org/schema/security/spring-security.xsd">
    ...
</beans:beans>

このチャプターでは上記のシンタックスを使用していると仮定します。

6.1.1 Design of the Namespace

namespaceはフレームワークの最も一般的な使い方を捉えて、シンプルかつ簡潔なシンタックスでアプリケーションを設定出来るように設計されています。この設計はフレームワークの様々な依存性全体をベースに出来ており、以下の領域に分けられます。

  • Web/HTTP Security - 一番複雑な部分。フィルタと関連サービスbeanをセットアップする。beanはフレームワークの認証メカニズムを適用するのに使われ、URLのセキュア化、ログインとエラーページなどのレンダリングを行う。
  • Business Object (Method) Security -サービスレイヤをセキュア化するためのオプション。
  • AuthenticationManager - フレームワークの他部分からの認証リクエストの処理を担当。
  • AccessDecisionManager - webとメソッドセキュリティでのアクセス判断を行う。デフォルト実装が登録されるが、一般的なSpring bean宣言でそれとは別のカスタム実装を選択可能。
  • AuthenticationProviders - 認証マネージャがユーザを認証する際のメカニズム。namespaceは複数の標準的なオプションをサポートしつつ、従来型のカスタムbean宣言で追加することも可能。
  • UserDetailsService - 認証プロバイダと密接に関連するものだが、別のbeanがこれを要求することがある。

以降のセクションでこれらの設定方法について見ていきます。

6.2 Getting Started with Security Namespace Configuration

このセクションでは、フレームワークの主要機能のいくつかをnamespace configurationで設定する方法を見ていきます。まず最初に、なるべく早く動くものを作成して認証サポートを追加して既存のwebアプリケーションにテスト用のログインユーザにアクセス制御をつける、をやっていきます。次に、データベースもしくは別のセキュリティリポジトリを使用した認証への変更を見ていきます。それ以降のセクションでは高度なnamespace configurationオプションを紹介します。

6.2.1 web.xml Configuration

まず以下のフィルター宣言をweb.xmlを追加します。

<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Spring Securityのweb基盤にフックを仕掛けています。DelegatingFilterProxySpring Frameworkのクラスで、アプリケーションコンテキストでSpringのbeanとして定義してあるフィルタ実装へ移譲を行います。上の場合、bean名は"springSecurityFilterChain"で、これはnamespaceが生成する内部的な基盤beanで、webセキュリティを処理します。なお、このbean名を別のbeanには使用してはいけません。web.xmlに上記を追加してから、アプリケーションコンテキストファイルを編集します。<http>要素を使用してWeb security serviceを設定します。

6.2.2 A Minimal Configuration

webセキュリティの有効化はまず以下のようにします。

<http>
<intercept-url pattern="/**" access="hasRole('USER')" />
<form-login />
<logout />
</http>

上記設定の意味は、アプリケーション内の全URLをセキュア化し、その全URLへのアクセスにROLE_USERロールを要求し、ユーザ名とパスワードのフォームでアプリケーションにログインを行うようにして、アプリケーションでログアウトするためのURLが用意されます。<http>要素は、web関連のすべてのnamespace機能の親要素です。<intercept-url>要素にはantパスシンタックスでリクエストURLにマッチングさせるpatternを定義します[2]。なお、正規表現によるマッチングも可能です(詳細はnamaspaces appendixを参照)。access属性はパターンにマッチしたリクエストに対するアクセス要求を定義します。デフォルト設定では、ロールのカンマ区切りリストで、アクセスが許可されるにはユーザはそのうちの一つを持っている必要があります。プレフィクス"ROLE_“はユーザの権限が単純比較で行われることを示すマーカーです。つまり、normal role-based checkが行われる、ということです*2。Spring Securityのアクセス制御は単純なロール以外もあります(つまり異なる種類のセキュリティ属性を区別するのにプレフィクスを使用する)。その処理の詳細については後で見ます。footnote:[The interpretation of the comma-separated values in the access attribute depends on the implementation of the -1- which is used. In Spring Security 3.0, the attribute can also be populated with an -2-.*3

[2] - マッチングの詳細についてはWeb Application InfrastructureのSection 13.4, “Request Matching and HttpFirewall”を参照してください。 異なるURLで異なるアクセス要求を定義するのに複数の<intercept-url>要素を使用でき、リスト順に評価されて最初にマッチしたものが使われます。よって、リストのトップに最も広くマッチするものを置く必要があります。また、特定のHTTPメソッド(GET, POST, PUTなど)のマッチに制限するのにmethod属性が使えます。

テストユーザを何人か追加するには、namespaceに直接テストデータを追加します。

<authentication-manager>
<authentication-provider>
    <user-service>
    <user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
    <user name="bob" password="bobspassword" authorities="ROLE_USER" />
    </user-service>
</authentication-provider>
</authentication-manager>

フレームワークのnamespace以前のバージョンの知識がある場合、上記で何が行われているか大まかに推測できるかと思います。<http>要素はFilterChainProxyを生成する責務を負い、要素で使うビーンをフィルタリングします。フィルタの順序間違いなどのありがちな問題は、フィルタのポジションが予め定義されるため、問題にならなくなりました。

<authentication-provider>要素はDaoAuthenticationProvider beanを生成し、<user-service>要素はInMemoryDaoImplを生成します。すべてのauthentication-provider要素は<authentication-manager>の子要素である必要があり、<authentication-manager>ProviderManagerを生成して認証プロバイダを登録します。生成されるbeanの詳細についてはnamespace appendixを参照してください。フレームワークの重要なクラスとその使われ方を理解したくなった場合、とくにカスタマイズをする場合は、namespace appendixを活用してください。

上記設定例では、アプリケーションにおける(アクセス制御に使われる)パスワードとロール、二人のユーザを定義しています。user-serviceproperties属性でプロパティファイルからユーザ情報をロードするようにも出来ます。ファイルフォーマットの詳細についてはin-memory authenticationを参照してください。<authentication-provider>要素は、認証リクエストを処理する認証マネージャが使用するユーザ情報を指定します。異なるアプリケーションソースをを定義するのに複数の<authentication-provider>要素を使用可能で、順番に処理されます。

以上でアプリケーションを開始可能となりログインが要求されます。設定を書いて試すか、"tutorial"サンプリケーションで試してみてください。

6.2.3 Form and Basic Login Options

ログインをしようとした時にログインフォームがどこから来たのか、HTMLやJSPを何も用意していないので、疑問に感じたかもしれません。実際、ログインページ用のURLを明示的に設定しない場合、Spring Securityは、デフォルト値で有効化される機能でログインのサブミットを処理するURLでログインページを自動生成し、デフォルトのターゲットURLがログイン後にユーザに送られます。なお、namaspaceはカスタマイズ可能なオプションを多数用意しています。たとえば、自前のログインページを表示したい場合、以下のようにします。

<http>
<intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page='/login.jsp'/>
</http>

追加でintercept-url要素を足しており、これはログインページに対するリクエストをアノニマスユーザで利用可能にしています。[3] IS_AUTHENTICATED_ANONYMOUSLYの詳細はAuthenticatedVoterを参照してください。ログインページ以外へのリクエストはパターン/**にマッチし、このパターンではログインページにアクセス出来なくなります。これは良くある設定エラーでアプリケーションは無限ループになります。ログインページがセキュアと考えられる場合、Spring Securityはwarningログを出します*4。特定パターンにマッチするすべてのリクエストがセキュリティフィルターチェーンをバイパスする設定が可能で、これは以下のようにパターンごとに別々のhttp要素を定義します。

[3] - Chapter 22, Anonymous Authenticationを参照。

<http pattern="/css/**" security="none"/>
<http pattern="/login.jsp*" security="none"/>

<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page='/login.jsp'/>
</http>

Spring Security 3.1以降、異なるリクエストパターンごとのセキュリティフィルターチェーン設定を定義するのに複数のhttp要素を使用可能です。http要素にpattern属性を書かない場合、すべてのリクエストにマッチします。非セキュアなパターンの作成はこのシンタックスの一例で、ここでのパターンは空のフィルターチェーンが関連付けされます。[4] このシンタックスの詳細についてはSecurity Filter Chainを参照してください。

[4] - 複数<http>要素は重要な機能で、例えば、同一アプリケーションでステートフルとステートレスのパス両方を同時に実現出来ます。この機能追加は、以前のシンタックスである、intercept-url要素のfilters="none"属性とは非互換で、3.1では未サポートです。

これら非セキュアなリクエストはSpring Securityのweb関連configurationやrequires-channelなどの属性から完全に関知されなくなる点は重要で*5、そのリクエストにおいてカレントユーザの情報にアクセスしたりセキュアなメソッド呼び出しが出来ません。その場合でもセキュリティフィルタチェーンを適用したい場合、代わりにaccess='IS_AUTHENTICATED_ANONYMOUSLY'を使用してください。

フォームログインではなくベーシック認証を使いたい場合、設定を変更します。

<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_USER" />
<http-basic />
</http>

ベーシック認証が優先され、ユーザが保護リソースにアクセスしようとした場合にログインプロンプトが使われます。この設定でもフォーム認証は利用可能で、例えば別のwebページに埋め込んだログインフォームを使います。

Setting a Default Post-Login Destination

If a form login isn’t prompted by an attempt to access a protected resource, default-target-urlオプションが有効になります。ユーザがログイン成功時に得るURLでフォルトは"/"です。always-use-default-target属性をtrueに設定することでこのページに(ログインが必要になった段階か明示的にログインを選んだかに関わらず)常に進むように設定出来ます。アプリケーションでユーザが常に"home"ページに進む場合に役立ちます。

<http pattern="/login.htm*" security="none"/>
<http use-expressions="false">
<intercept-url pattern='/**' access='ROLE_USER' />
<form-login login-page='/login.htm' default-target-url='/home.htm'
        always-use-default-target='true' />
</http>

ログイン後のページ遷移のより細かい制御については、default-target-urlの代わりにauthentication-success-handler-ref属性を使います。このbeanはAuthenticationSuccessHandlerインスタンスになります。詳細はCore Filtersとnamespace appendix、また認証失敗時のフローのカスタマイズ方法の情報も参照してください。

6.2.4 Logout Handling

logout要素で特定URLへの遷移によるログアウト機能を設定します。デフォルトのログアウトURLは/logoutで、logout-url属性で別のURLを設定します。その他に利用可能な属性についてはnamespace appendixを参照してください。

6.2.5 Using other Authentication Providers

現実的には、ユーザ情報のソースにはアプリケーションコンテキストに少数名を追加するのではなく、よりスケーラブルなものを使うかと思います。たいていの場合、LDAPサーバやデータベースなど何らかの媒体にユーザ情報を格納しています。LDAP namespace configurationはLDAP chapterで扱うため、ここでは説明しません。いま、アプリケーションコンテキストに"myUserDetailsService"というbean名でSpring SecurityのUserDetailsService実装があると仮定すると、認証でこれを使うには以下のようにします。

<authentication-manager>
    <authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>

データベースを使う場合は以下のようにします。

<authentication-manager>
<authentication-provider>
    <jdbc-user-service data-source-ref="securityDataSource"/>
</authentication-provider>
</authentication-manager>

ここでの"securityDataSource"はアプリケーションコンテキスト内のDataSourceを指すbean名で、Spring Security決め打ちのuser data tablesを持つデータベースを指します。もしくは、Spring SecurityのJdbcDaoImpl beanを設定してuser-service-refでそのbeanを使うことも出来ます。

<authentication-manager>
<authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>

<beans:bean id="myUserDetailsService"
    class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<beans:property name="dataSource" ref="dataSource"/>
</beans:bean>

以下のようにAuthenticationProvider beanを使うこともできます。

<authentication-manager>
    <authentication-provider ref='myAuthenticationProvider'/>
</authentication-manager>

ここでのmyAuthenticationProviderはアプリケーションコンテキストでAuthenticationProviderを実装するbean名です。複数のauthentication-provider要素を使用可能で、その場合には個々のプロバイダは宣言順に実行されます。namespaceでのSpring SecurityのAuthenticationManager設定方法の詳細についてはSection 6.6, “The Authentication Manager and the Namespace”を参照してください。

Adding a Password Encoder

パスワードはいかなる場合でもセキュリティ用途(SHAやMD5のような汎用アルゴリズムでは無く)に設計されたハッシュアルゴリズムを使うべきです。<password-encoder>要素でこれを指定します。bcryptエンコードのパスワードを使う場合、大本に認証プロバイダ設定は以下のようになります。

<beans:bean name="bcryptEncoder"
    class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

<authentication-manager>
<authentication-provider>
    <password-encoder ref="bcryptEncoder"/>
    <user-service>
    <user name="jimi" password="d7e6351eaa13189a5a3641bab846c8e8c69ba39f"
            authorities="ROLE_USER, ROLE_ADMIN" />
    <user name="bob" password="4e7421b1b8765d8f9406d87e7cc6aa784c4ab97f"
            authorities="ROLE_USER" />
    </user-service>
</authentication-provider>
</authentication-manager>

bcryptはおおよそのケースに適していますが、レガシーシステムによって別のアルゴリズムを強いられる場合は除きます。今現在、単純なハッシュアルゴリズムであるとか、もっと悪い、パスワードをプレーンテキストで保存しているとかの場合、bcryptなどセキュアな方針への移行を検討すべきです。

6.3 Advanced Web Features

6.3.1 Remember-Me Authentication

remember-me namespace configurationについてはRemember-Me chapterを参照してください。

6.3.2 Adding HTTP/HTTPS Channel Security

アプリケーションがHTTPとHTTPS双方をサポートする場合、特定のURLのみHTTPSでアクセス可能にする必要があり、その場合は<intercept-url>属性のrequires-channelで設定します。

<http>
<intercept-url pattern="/secure/**" access="ROLE_USER" requires-channel="https"/>
<intercept-url pattern="/**" access="ROLE_USER" requires-channel="any"/>
...
</http>

上記の設定の場合、ユーザがHTTPで"/secure/**“にマッチするURLにアクセスしようとすると、HTTPS URL [5] にリダイレクトされます。利用可能なオプションは"http”, “https” or “any"です。"any"はHTTPかHTTPSのどちらかを使用する、という意味です。

[5] - channel-processingの実装の詳細については、ChannelProcessingFilterとその関連クラスのjavadocを参照してください。

HTTPのどちらかHTTPSまたは両方で非標準のポートを使う場合、以下のようなport mappingを指定します。

<http>
...
<port-mappings>
    <port-mapping http="9080" https="9443"/>
</port-mappings>
</http>

真にセキュアにするのであれば、アプリケーションはHTTPを全く使わなくしたり、HTTPとHTTPSの切り替えはやらない方が良いです。HTTPSで開始(ユーザは最初からHTTPS URLでアクセス)し、中間者攻撃の可能性を回避するために常にセキュアな接続を使用すべきです。

6.3.3 Session Management

Detecting Timeouts

Spring Securityでは無効なセッションIDを検出して適当なURLにユーザをリダイレクトする設定が可能です。これはsession-management要素で設定します。

<http>
...
<session-management invalid-session-url="/invalidSession.htm" />
</http>

この方法でセッションタイムアウトを検出する場合、ユーザがログアウト後にブラウザを閉じずに再ログインすると誤ったエラーを報告する可能性があります。これはセッション無効化時にセッションクッキーをクリアしないためで、たとえユーザがログアウトしていても再度サブミットします。ログアウトハンドラーで以下のようなシンタックスによって、ログアウト時にJSESSIONIDクッキーを明示的に削除できます。

<http>
<logout delete-cookies="JSESSIONID" />
</http>

残念ながらこの設定はすべてのサーブレットコンテナでの動作は保証していないため、個々の環境で動作するかどうかの確認が必要です。

プロキシを介してアプリケーションを動作させる場合、プロキシサーバの設定でセッションクッキーを削除できる場合があります。たとえば、Apache HTTPDのmod_headersでは、以下のディレクティブはログアウトリクエストのレスポンスでクッキーを期限切れにすることでJSESSIONIDを削除しています(以下の例は/tutorialパス下にアプリケーションをデプロイしていると仮定)。

<LocationMatch "/tutorial/logout">
Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 1970 00:00:00 GMT"
</LocationMatch>
Concurrent Session Control

アプリケーションでユーザのログインを単一に制約したい場合があり、Spring Securityは以下のようにちょっとした設定でそれを実現できます。まずweb.xmlファイルに以下のリスナーを追加してセッションライフサイクルイベントをSpring Securityで受け取れるように変更します。

<listener>
<listener-class>
    org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>

次にアプリケーションコンテキストに以下の行を追加します。

<http>
...
<session-management>
    <concurrency-control max-sessions="1" />
</session-management>
</http>

これによりユーザは複数回ログイン出来なくなり、二回目のログインは一回目のログインを無効化します。二回目のログインを抑止したい場合は以下のようにします。

<http>
...
<session-management>
    <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
</http>

こうすると二回目のログインは拒否されます。拒否(“rejected”)されると、フォームベースのログインを使っている場合はユーザはauthentication-failure-urlに送られます。二回目の認証は “remember-me"など対話的でない方法で行われる場合があり、"unauthorized” (401) エラーがクライアントに送られます。そうではなくエラーページにしたい場合、session-management要素にsession-authentication-error-url属性を追加します。

フォームベースのログインでカスタマイズした認証フィルタを使用する場合、明示的にconcurrent session control機能を設定する必要があります。詳細はSession Management chapterを参照してください。

Session Fixation Attack Protection

Session fixation攻撃とはサイトアクセス時に悪意のあるアタッカーがセッションを生成する可能性のある潜在的リスクで、同一のセッションを用いてログインするようユーザを仕向けます(たとえばパラメータとしてセッション識別子を含むリンクをユーザに送るなど)。Spring Securityではこれを、ユーザログイン時に新規セッションを生成するかセッションIDを変更して、自動的に防止します。これが必要でない場合、もしくは他の機能とコンフリクトする場合、<session-management>session-fixation-protection属性で振る舞いを制御します。

  • none - 何もしない。元のセッションは残り続ける。
  • newSession - 新規の"クリーンな(clean)“セッションを生成し、既存のセッションデータはコピーしない(Spring Securiy関連の属性はコピーされる)。
  • migrateSession - 新規セッションを生成してすべての既存のセッション属性を新規セッションにコピーする。Servlet 3.0もしくはそれより古いコンテナの場合のデフォルト。
  • changeSessionId - 新規セッションを生成しない。ただしServletコンテナが提供するsession fixation protectionを使用する(HttpServletRequest#changeSessionId())。このオプションはServlet 3.1 (Java EE 7)およびそれ以降のコンテナでのみ使用可能。古いコンテナで指定すると例外をスローする。Servlet 3.1およびそれ以降のコンテナの場合のデフォルト。

session fixationに対する防御が発生すると、アプリケーションコンテキストにSessionFixationProtectionEventがパブリッシュされます。また、changeSessionIdを使う場合、防御発生時にはjavax.servlet.http.HttpSessionIdListenerに通知されます。両イベントをリッスンする場合には慎重に使って下さい*6。詳細についてはSession Managementを参照してください。

6.3.4 OpenID Support

namespaceではフォームログインの他にOpenIDログインをサポートしており、設定を以下のように変更します。

<http>
<intercept-url pattern="/**" access="ROLE_USER" />
<openid-login />
</http>

OpenIDプロバイダ(myopenid.comなど)に情報を登録し、<user-service>でインメモリにユーザ情報を追加します。

<user name="http://jimi.hendrix.myopenid.com/" authorities="ROLE_USER" />

認証を行うためにmyopenid.comサイトでログイン可能な状態にしておきます。なお、openid-login要素のuser-service-ref属性にOpenIDを扱う別のUserDetailsServiceを指定可能です。詳細は前述のauthentication providersを参照してください。注意点として、上のユーザ設定ではパスワード属性を省略していますが、これはユーザデータがauthoritiesをロードするためだけに使われるからです。内部的にランダムパスワードが生成され、設定の認証ソースとしてこのユーザデータを誤って使ってしまうことを防いでいます。

Attribute Exchange

OpenIDattribute exchangeをサポートしています。例として、以下の設定はOpenIDプロバイダからemailとフルネームを取得します。

<openid-login>
<attribute-exchange>
    <openid-attribute name="email" type="http://axschema.org/contact/email" required="true"/>
    <openid-attribute name="name" type="http://axschema.org/namePerson"/>
</attribute-exchange>
</openid-login>

OpenID属性の"type"は固有のスキーマで表現されるURIで、例えばhttp://axschema.org/などです。認証を正常終了させるのにその属性の取得が必須であれば、required属性を設定します。サポートされる正確なスキーマと属性はOpenIDプロバイダに依存します。属性値は認証処理の一部として返され、以下のようなコードでアクセスします。

OpenIDAuthenticationToken token =
    (OpenIDAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();
List<OpenIDAttribute> attributes = token.getAttributes();

OpenIDAttributeには属性の型と返された値(値を複数持つ属性の場合は複数の値)が含まれます。SecurityContextHolderクラスの詳しい使い方についてはtechnical overviewのSpring Securityコアコンポーネントを参照してください。複数のidentity providersを使いたい場合向けに、複数のattribute exchange configurationsもサポートしています。identifier-matcher属性を使用して複数のattribute-exchange要素を指定できます。これにはユーザが指定するOpenID identifierにマッチする正規表現が含まれます。設定サンプルとしてはOpenIDサンプルアプリケーションの、Google, Yahoo, MyOpenIDプロバイダそれぞれで異なる属性リストを使っているのを参照してください。

6.3.5 Response Headers

ヘッダー要素のカスタマイズ方法の詳細についてはChapter 20, Security HTTP Response Headersを参照してください。

6.3.6 Adding in Your Own Filters

以前からSpring Securityを使用していれば、このフレームワークは各種サービスを適用するためのフィルターのチェーンがあるのを知っていることかとおもいます。場合によっては、このフィルタースタックの特定位置に自前のフィルタを追加したり、現行のnamespace configurationオプションには存在しない(例えばCAS)Spring Securityのフィルタを使いたい、ことがあります。また、標準のnamespaceフィルタのカスタマイズ版を使用したい場合もあります。たとえば、<form-login>要素で生成されるUsernamePasswordAuthenticationFilterなどで、このbeanを明示的に設定することで利用可能となるオプションを使いたい場合などです。フィルターチェーンが直接的に公開されていないので、namespace configurationでこれを実現する方法はあるのでしょうか?

フィルタの順序はnamespaceを使う場合では常に厳密なものが強制されています。アプリケーションコンテキストが生成されるとき、フィルターのbeanはnamespaceが処理するコードでソートされ、標準Spring Securityフィルタはそれぞれnamespaceでエイリアスとwell-knownのポジションを持ちます。

以前のバージョンでは、ソートはフィルタインスタンスが生成されたのち、アプリケーションコンテキストのpost-processing中に実行されていました。version 3.0+では、ソートは、クラスがインスタンス化される前の、bean metadata levelで行われます。この点については、<http>要素のパース中にフィルタリスト全体が明確になる必要があるため、スタックに自前のフィルタを追加する方法に影響を与えます。よって、このシンタックスは3.0で小規模な変更が行われています。

Table 6.1. Standard Filter Aliases and Ordering

Table 6.1, “Standard Filter Aliases and Ordering”は、フィルタを生成するエイリアスとnamespace要素/属性、フィルタのリストです。以下のリストはフィルタチェーンの順になっています。

Alias Filter Class Namespace Element or Attribute
CHANNEL_FILTER ChannelProcessingFilter http/intercept-url@requires-channel
SECURITY_CONTEXT_FILTER SecurityContextPersistenceFilter http
CONCURRENT_SESSION_FILTER ConcurrentSessionFilter session-management/concurrency-control
HEADERS_FILTER HeaderWriterFilter http/headers
CSRF_FILTER CsrfFilter http/csrf
LOGOUT_FILTER LogoutFilter http/logout
X509_FILTER X509AuthenticationFilter http/x509
PRE_AUTH_FILTER AbstractPreAuthenticatedProcessingFilter Subclasses N/A
CAS_FILTER CasAuthenticationFilter N/A
FORM_LOGIN_FILTER UsernamePasswordAuthenticationFilter http/form-login
BASIC_AUTH_FILTER BasicAuthenticationFilter http/http-basic
SERVLET_API_SUPPORT_FILTER SecurityContextHolderAwareRequestFilter http/@servlet-api-provision
JAAS_API_SUPPORT_FILTER JaasApiIntegrationFilter http/@jaas-api-provision
REMEMBER_ME_FILTER RememberMeAuthenticationFilter http/remember-me
ANONYMOUS_FILTER AnonymousAuthenticationFilter http/anonymous
SESSION_MANAGEMENT_FILTER SessionManagementFilter session-management
EXCEPTION_TRANSLATION_FILTER ExceptionTranslationFilter http
FILTER_SECURITY_INTERCEPTOR FilterSecurityInterceptor http
SWITCH_USER_FILTER SwitchUserFilter N/A

上のスタックに自前のフィルタを追加可能で、custom-filter要素を使用して追加するフィルタの位置には上のリストの一つを指定します。

<http>
<custom-filter position="FORM_LOGIN_FILTER" ref="myFilter" />
</http>

<beans:bean id="myFilter" class="com.mycompany.MySpecialAuthenticationFilter"/>

afterもしくはbefore属性も使用可能で、スタックのフィルタの前後どちらかに追加したい場合に使います。スタックの一番前か後ろにフィルタを置きたい場合はposition属性に"FIRST"もしくは"LAST"をそれぞれ使います。

namespaceで生成される標準フィルタと同一ポジションにカスタムフィルタを追加する場合、誤ってnamecepaceバージョンを含めないことが重要です。置き換えたい機能を持つフィルタを生成する要素を削除してください。なお、<http>要素の使用により生成されるフィルタ、SecurityContextPersistenceFilter, ExceptionTranslationFilter, FilterSecurityInterceptorは置き換えられません。他いくつかのフィルタがデフォルト追加されますが、これらは無効化できます。session-fixation protectionを無効化しない限りデフォルトでAnonymousAuthenticationFilterが追加され、フィルターチェーンにSessionManagementFilterが追加されます。

authentication entry point(例:セキュアなリソースに未認証のユーザがアクセスを試みた場合にトリガされる認証プロセス)を必要とするnamespaceフィルタを置き換える場合、custom entry point beanも追加する必要があります。

Setting a Custom AuthenticationEntryPoint

フォームログインを使わない場合、namespaceではOpenIDやベーシック認証などで、認証フィルターと伝統的なbeanシンタックスでエントリーポイントを定義、namaspaceでそれらをリンクさせたい場合があると思います。<http>要素のentry-point-refAuthenticationEntryPointを設定します。

CASサンプルアプリケーションがそのシンタックスを使用してnamespaceでカスタムbeanを使用している一例となっています。認証エントリーポイントについて知りたい場合、technical overviewを参照してください。

6.4 Method Security

バージョン2.0以降Spring Securityはサービスレイヤーのメソッドにセキュリティを付加する機能を大幅に強化しています。フレームワーク固有の@Securedと共にJSR-250アノテーションをサポートしています。また、3.0以降では新たにexpression-based annotationsを使用可能です。bean宣言のデコレートにはintercept-methods要素で単一のbaenにセキュリティを適用するか、AspectJスタイルのポイントカットでサービスレイヤー全体の複数beanをセキュアにできます。

6.4.1 The <global-method-security> Element

この要素は(要素に適切な属性を設定することで)アプリケーションでアノテーションベースのセキュリティを有効化するために使われ、また、アプリケーションコンテキスト全体に適用されるセキュリティポイントカット宣言をグループ化します。<global-method-security>要素は一つだけ宣言します。以下の宣言はSpring Securityの@Securedサポートを有効化します。

<global-method-security secured-annotations="enabled" />

(クラスあるいはインタフェースの)メソッドにアノテーションを追加すると、内容に応じてそのメソッドへのアクセスが制限されます。Spring Security固有のアノテーションはメソッドに属性を定義します。それらの属性はAccessDecisionManagerに渡されてアクセス判定に使われます。

public interface BankService {

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();

@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}

JSR-250アノテーションを有効化するには以下にします。

<global-method-security jsr250-annotations="enabled" />

上記は標準アノテーションでシンプルなロースベース制約を適用可能できますが、Spring Security固有アノテーションの強力な機能がありません。新しい式ベースシンタックス(expression-based syntax)を使うには以下のようにします。

<global-method-security pre-post-annotations="enabled" />

同等なJavaコードは以下となります。

public interface BankService {

@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);

@PreAuthorize("isAnonymous()")
public Account[] findAccounts();

@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}

式ベースアノテーションを採用するのが良い場合は、ユーザのオーソリティーリストに対してロール名をチェックする以上の、単純なルール定義が必要な時です。

アノテーションを付与するメソッドは(method-securityが有効化された同一のアプリケーションコンテキスト内の)Spring beanとして定義されたインスタンスの場合にだけセキュアになります。Springで生成していない(newなど)インスタンスをセキュアにしたい場合はAspectJを使う必要があります。

アプリケーションで複数種類のアノテーションを有効化可能ですが、インタフェースやクラスには一種類のみ使用すべきで、これはその場合の振る舞いが未定義なためです。ある特定のメソッドに適用されるアノテーションが二つある場合、一つだけが適用されます。

Adding Security Pointcuts using protect-pointcut

protect-pointcutはとりわけ強力な機能で、一つの宣言だけで複数のbaenをセキュアにできます。

<global-method-security>
<protect-pointcut expression="execution(* com.mycompany.*Service.*(..))"
    access="ROLE_USER"/>
</global-method-security>

上の例は、アプリケーションコンテキストのcom.mycompanyパッケージで"Service"で終わるクラス名のbaenのすべてのメソッドをセキュアにします。ROLE_USERロールを持つユーザのみメソッドを呼び出せます。URLマッチング同様、最も広くマッチするものをポイントカットのリストの最初に持ってくる必要があり、これは最初にマッチする式が使われるためです。Securityアノテーションはポイントカットに優先します。

6.5 The Default AccessDecisionManager

このセクションはSpring Security内におけるアクセス制御アーキテクチャ基盤の知識をいくらか必要です。知識を持ち合わせていなければスキップして後で戻ってくれば良く、その理由は、単純なロールベースセキュリティ以上のものを使うカスタマイズを必要とする開発者にのみ関連するセクションなためです。

namespace configurationの場合、AccessDecisionManagerのデフォルトインスタンスが自動的に登録されてメソッド呼び出しとURLアクセスのアクセス決定をするのに使われます。アクセス決定はintercept-urlprotect-pointcut宣言(とアノテーションでセキュアにしたメソッドがあればそのアノテーション)で指定するアクセス属性に基づいて行われます。

デフォルトの動作*7RoleVoterAuthenticatedVoterと共にAffirmativeBased, AccessDecisionManagerを使用します。これらクラスの詳細についてはauthorizationのチャプターを参照してください。

6.5.1 Customizing the AccessDecisionManager

より複雑なアクセス制御を使う必要がある場合はメソッドとwebセキュリティで別のものを設定します。

メソッドセキュリティでは、global-method-securityaccess-decision-manager-ref属性にアプリケーションコンテキストで定義したAccessDecisionManagerのbean idを設定します。

<global-method-security access-decision-manager-ref="myAccessDecisionManagerBean">
...
</global-method-security>

webセキュリティのシンタックスも同様ですが、こちらはhttp要素です。

<http access-decision-manager-ref="myAccessDecisionManagerBean">
...
</http>

6.6 The Authentication Manager and the Namespace

Spring Securityの認証サービスを提供するメインのインターフェースはAuthenticationManagerです。このインタフェースは通常Spring SecurityのProviderManagerクラスのインスタンスで、既にフレームワークを使ったことばあれば見たことがあるかもしれません。使ったことがなくても、technical overview chapterで後に解説します。このbeanのインスタンスauthentication-managerのnamespace要素を使用して登録されます。namespaceを通してHTTPもしくはメソッドセキュリティのどちらかを使う場合、カスタムのAuthenticationManagerは使えませんが、そこで使われるAuthenticationProvider経由ですべての制御が可能なので問題にならないと思われます。

ProviderManagerAuthenticationProvider beanを追加登録したい場合、<authentication-provider>要素のref属性を使用し、この属性値には追加したいプロバイダのbean名を指定します。

<authentication-manager>
<authentication-provider ref="casAuthenticationProvider"/>
</authentication-manager>

<bean id="casAuthenticationProvider"
    class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
...
</bean>

別のよくある使い方としては、コンテキストの別のbeanがAuthenticationManagerの参照を使いたい場合が挙げられます。AuthenticationManagerエイリアスを登録してアプリケーションコンテキストでそのエイリアス名を使います。

<security:authentication-manager alias="authenticationManager">
...
</security:authentication-manager>

<bean id="customizedFormLoginFilter"
    class="com.somecompany.security.web.CustomFormLoginFilter">
<property name="authenticationManager" ref="authenticationManager"/>
...
</bean>

7. Sample Applications

Spring Securityプロジェクトでは利用可能なサンプルアプリケーションがいくつか存在します。大量ダウンロードを避けるため、"tutorial"と"contacts"サンプルのみzipファイルになっています。それ以外はthe introductionにある通りソースから直接ビルドできます。ビルドするのは簡単で詳細はhttp://spring.io/spring-security/のプロジェクトwebサイトにあります。このチャプターで参照するすべてのパスはプロジェクトソースディレクトリに関連するものです。

7.1 Tutorial Sample

チュートリアルのサンプルはとりあえず始めてみるのに適しています。全体的にシンプルなnamespace configurationを使っています。コンパイル済みアプリケーションは配布しているzipファイルに含まれ、webコンテナにデプロイします(spring-security-samples-tutorial-3.1.x.war)。フォーム認証と、クッキーでログインを自動的に記憶するためのremember-me認証を組み合わせています。

XMLが最小構成でこれを基に修正を加えるのも容易なのでこのチュートリアルサンプルから使い始めることを推奨します。更に、既存アプリケーションにチュートリアルXMLファイル(とweb.xmlエントリの該当箇所)を追加するのも容易です。これら基本的な連携機能を確認できてから、メソッド認可やドメインオブジェクトセキュリティの追加を試すことを推奨します。

7.2 Contacts

Contacts Sampleは高度な機能のサンプルで、基本的なアプリケーションセキュリティに加えてdomain object access control lists(ACLs)の強力な機能の解説になっています。このアプリケーションではコンタクト(という名の)シンプルなデータベースで管理可能なユーザのインタフェースを提供しています。

デプロイするには、単にSpring Securityで配布しているzipからWARファイルをコンテナのwebappsディレクトリにコピーします。warはおおむねspring-security-samples-contacts-3.1.x.warという名前です(バージョン番号は使用するリリースに依存して変わります)。

コンテナ開始後、アプリケーションがロード出来ているかチェックしてください。http://localhost:8080/contacts がURLです(またはwebコンテナとデプロイしたwarに準じるURL)。

次に、"Debug"をクリックしてください。認証が求められ、そのページに一連のユーザ名とパスワードが表示されます。それを使用して認証すると結果ページに移り、以下のような成功メッセージが表示されます。

Security Debug Information

Authentication object is of type:
org.springframework.security.authentication.UsernamePasswordAuthenticationToken

Authentication object as a String:

org.springframework.security.authentication.UsernamePasswordAuthenticationToken@1f127853:
Principal: org.springframework.security.core.userdetails.User@b07ed00: Username: rod; \
Password: [PROTECTED]; Enabled: true; AccountNonExpired: true;
credentialsNonExpired: true; AccountNonLocked: true; \
Granted Authorities: ROLE_SUPERVISOR, ROLE_USER; \
Password: [PROTECTED]; Authenticated: true; \
Details: org.springframework.security.web.authentication.WebAuthenticationDetails@0: \
RemoteIpAddress: 127.0.0.1; SessionId: 8fkp8t83ohar; \
Granted Authorities: ROLE_SUPERVISOR, ROLE_USER

Authentication object holds the following granted authorities:

ROLE_SUPERVISOR (getAuthority(): ROLE_SUPERVISOR)
ROLE_USER (getAuthority(): ROLE_USER)

Success! Your web filters appear to be properly configured!

上記のようなメッセージが正常に表示されたのを確認したら、サンプルアプリケーションのホームページに戻り"Manage"をクリックします。アプリケーションを色々試してみてください。注意点としては、現在ログオンしているユーザに利用可能なコンタクトのみ表示され、また、ROLE_SUPERVISORを持つユーザのみコンタクトを削除する権限を持っています。これの裏側では、MethodSecurityInterceptorがビジネスオブジェクトをセキュアにしています。

このアプリケーションではコンタクトそれぞれに関連付けられているaccess control listsを修正できます。これを試してみて、アプリケーションコンテキストXMLファイルを調査することで動作の理解に役立てて下さい。

7.3 LDAP Sample

LDAPサンプルアプリケーションは基本的な設定と、namespace configurationと伝統的なbean使用による同等な設定をセットアップしており、両者は同一のアプリケーションコンテキストファイルにいます。つまり、このアプリケーションには二つの同一な認証プロバイダ設定が存在する、ということです。

7.4 OpenID Sample

OpenIDサンプルではnamespaceでのOpenIDの設定方法と、Google, Yahoo, MyOpenIDアイデンティティ・プロバイダのattribute exchangeの設定方法のデモとなっています(他のプロバイダを追加して試すことも可能)。ユーザフレンドリーなログインページを作るのにJQueryベースのopenid-selectorを使用しており、OpenIDの識別子をイチからタイプせず、プロバイダを簡単に選択できるようにしています。

このアプリケーションは一般的な認証とは異なり、任意のユーザがサイトにアクセス可能です(与えられたOpenID認証が成功すれば)。初回ログイン時には"Welcome [your name]“というメッセージが表示されます。ログアウトして(同一のOpenID識別子で)再度ログインすると"Welcome Back"に変わります。この動作はUserDetailsServiceを使用しており、そのカスタム実装ではユーザに標準ロールを割り当てて内部的にmapで識別子を保存します。ここは実際のアプリケーションではデータベースを使うと思われます。詳細はソースを確認してください。また、このクラスは異なるプロバイダから異なる属性が返される点に対応しており、場合に応じたユーザの処理をして氏名を組み立てています。

7.5 CAS Sample

CASサンプルを動かすにはCASサーバとCASクライアント両方が必要です。zip配布ファイルでは無いためthe introductionに書いてるリポジトリからプロジェクトのコードをチェックアウトします。sample/cas下に関連ファイルがあります。また、Readme.txtファイルにサーバとクライアントをソースツリーから直接起動す方法が書いてあります。これらはSSLをサポートします。

7.6 JAAS Sample

JAASサンプルはSpring SecurityでJAAS LoginModuleを使う方法の簡単なサンプルです。ここでのLoginModuleはユーザ名とパスワードが一致すれば認証成功とし、それ以外はLoginExceptionをスローします。サンプルで使われているAuthorityGranterは常にROLE_USERロールを付与します。また、jaas-api-provisionを"true"と設定することでLoginModuleが返すJAAS Subjectとして動作させる方法も含みます。

7.7 Pre-Authentication Sample

このサンプルアプリケーションはJava EEコンテナのログイン情報を使用するようにpre-authenticationフレームワークのbeanをワイヤリングする方法となっています。ユーザ名とロールはコンテナでセットアップします。

コードはsamples/preauthにあります。

*1:Afterall, if every property was exposed, users could use standard bean configuration.が原文。若干訳に自信が無いがafterallと前の文から、どうせプロパティが全部見えていたって使う場合なんてほとんど無かろう? というニュアンスだと思ったんでこういう訳文にしている。

*2:a normal role-based check should be used.が原文。書いてあるとおりごくごく原始的なロールチェックという意味だが、あんま良い日本語思いつかなかったんで英語のままにしてある。

*3:原文がfootnote~てあるくらいだからなんか記法がバグってるぽいので訳していない。

*4:Spring Security will emit a warning in the log if your login page appears to be secured. が原文。訳せても意味が分からない…

*5:It’s important to realise that these unsecured requests will be completely oblivious to any Spring Security web-related configuration or additional attributes such as requires-channelが原文。oblivious toのあたりの訳がやや自信がない。

*6:so use caution if your code listens for both events.が原文。use cautionで検索すると工事現場の注意表示が引っかかるんだが、イベントをリスナーで受け取るだけだと思うんだが、何に注意せよって言ってるのかが分からん。

*7:default strategyが原文。strategyていつも適当な日本語の名詞に困るものの一つで、戦略だと主語がデカすぎるので、ここではシンプルに「動作」としてみた。

Spring Security Reference 4.2.2のI. Prefaceをテキトーに訳した(1)

Spring Security Reference http://docs.spring.io/spring-security/site/docs/4.2.2.RELEASE/reference/htmlsingle/ のI. Prefaceをなんとなく訳した。

Spring Securityちょっと勉強したいな、と読み始めたけどI. Prefaceは長めのチュートリアルって感じで、あまり大したことは書いてなかったりした。

Spring Securityは強力で高いカスタマイズ性を備えた認証とアクセス制御のフレームワークです。SpringベースのアプリケーションをセキュアにするためのデファクトスタンダードがSpring Securityです。

Part I. Preface

Spring SecurityはJava EEベースのエンタープライズソフトウェアアプリケーションにおける包括的なセキュリティソリューションです。このリファレンスガイドを読み進めば分かる通り、我々は使いやすくて設定の小回りが利くセキュリティシステムを提供できるよう努めています。

セキュリティとは常に変化し続けるものであり、包括的でシステム全体視点からのアプローチを追及することが重要です。セキュリティ界隈としてはセキュリティレイヤー(“layers of security”)の導入を推奨しており、これにより各レイヤーを独立して出来る限りセキュアにする事が可能となり、レイヤーの層自体が付加的なセキュリティ対策となります。各レイヤのセキュリティを緊密(“tighter”)にすることで、アプリケーションはより堅牢かつ安全になります。最も低い層では中間者攻撃リスクを低減するため、たとえばtransport securityとシステム同定(system identification)などの問題を処理する必要があります。また、一般的には、ファイアウォールを設定し、許可されたシステムだけが接続を試行可能なことを保証するのにVPNとIPセキュリティを使います。企業向けシステムでは公開サーバとバックエンドのデータベースとアプリケーションを分離するのにDMZを構築します。また、OSは非特権ユーザとしてのプロセス実行やファイルシステムセキュリティの最大化などの問題に重要な役割を担います。通常、OSはファイアウォールを内蔵しています。もしかすると、読者の中にはシステムに対するサービス拒否攻撃やブルートフォースの対処経験がある人もいるかもしれません。侵入検知システムは、リアルタイムのblocking offending TCP/IP addressesなどの攻撃からシステムを保護可能となり、攻撃に対する対処とモニタリングにとりわけ有用です。そこから上位レイヤに目を移すと、JVMは幸いにもJavaの型ごとにパーミッションを最小化するような設定が可能なため、, and then your application will add its own problem domain-specific security configuration. Spring Securityは後者の問題領域、アプリケーションセキュリティ、の容易化を扱います。

なお、上に挙げたすべてのセキュリティレイヤを適切に処理する必要があり、すべてのレイヤーを網羅する管理的要因(managerial factors)も対処が必要です。管理的要因は以下ですべてではありませんが、例えば、セキュリティ情報の入手・パッチ当て・personnel vetting・監査・変更管理・engineering management systems・データバックアップ・ディザスタリカバリ・パフォーマンスベンチマーク・負荷監視・ログ集積・インシデントレスポンス、などが挙げられます。

Spring Securityを使うことでエンタープライズアプリケーションのセキュリティレイヤに集中できます。セキュリティレイヤにはビジネス分野と同じくらい様々な要件が存在します。金融アプリケーションとECサイトのセキュリティ要件は異なります。ECサイトと販売管理ツールも異なります。こうした各種の要求はアプリケーションセキュリティを、興味深く、困難で、挑戦し甲斐のあるものとしています。

まずはChapter 1, Getting Startedに目を通して下さい。フレームワーク名前空間ベースの設定(namespace-based configuration)の紹介を数行にまとめています。Spring Securityと使う必要のあるクラスの動作を理解するには、Part II, “Architecture and Implementation”があります。本ガイドのそれ以外については、必要になった段階でその章を参照してください。なお、アプリケーションセキュリティの一般的な課題について出来る限り情報を集めておくことを推奨します。Spring Securityはあらゆるセキュリティ課題を解決する万能薬ではありません。アプリケーション設計において当初からセキュリティを考慮することが重要です。後付けで改良しようとするのは良いアイデアとは言えません。特に、webアプリケーションの場合、多数の脆弱性を考慮する必要があります。XSSCSRFセッションハイジャックなどは最初から対処しておくべきです。OWASP(https://www.owasp.org/)では有益な参照情報とwebアプリケーションの脆弱性のトップテンを掲載しています。

このリファレンスガイドが役に立てれば幸いで、フィードバックと意見を歓迎します。

最後になりますが、Spring Securityのコミュニティにようこそ。

1. Getting Started

本ガイドの後半ではフレームワークアーキテクチャと実装クラスの詳細について解説しており、これらはより細かいカスタマイズをする場合には理解しておく必要があります。このパートでは、Spring Security 4.0の紹介で、プロジェクトの来歴の概要とフレームワークを使い始めるための方法を簡単にまとめてあります。とくに、すべての実装クラスを個々にワイヤリングする伝統的なSpringビーンを使う方法と比べると、かなり簡単にアプリケーションをセキュアにできるnamespace configurationを見ていきます。

また、サンプルアプリケーションも見ていきます。以降のセクションを読み進める前にサンプルを動かしておくだけの価値はあります。フレームワークの理解が深まったらサンプルを見直すのが良いでしょう。また、記事へのリンク・ビデオとチュートリアルなど開発プロジェクトに有益な情報があるproject websiteもチェックしてみてください。

2. Introduction

2.1 What is Spring Security?

JavaEEベースのエンタープライズソフトウェアアプリケーションに包括的なセキュリティサービスを提供します。Spring Frameworkエンタープライズソフトウェア開発分野のJava EEソリューションを先導しており、Spring SecurityはSpring Frameworkを使用するプロジェクトのサポートに力点が置かれています。いまエンタープライズアプリケーション開発にSpringを使っていない場合、我々としてはSpringを調査してみることを推奨します。Springの知識がある、特に依存性注入を知っていれば、Spring Securityの理解はより早くなります。

Spring Securityが使われる動機は様々ですが、Java EE Servlet仕様やEJB仕様のセキュリティ機能が一般的なエンタープライズアプリケーションの要求度合いを満たさないことが分かってからSpring Securityに流れ着いてることが多々あります。これらの標準について、WARやEARレベルでポータブルでは無い点を承知しておくことが重要です。そのため、サーバ環境を移行する場合、新しいターゲット環境用にアプリケーションセキュリティのかなりの再設定作業が基本的には発生します。Spring Securityはこの問題に対処しており、使いやすくカスタマイズ可能なセキュリティ機能を多数提供しています。

アプリケーションセキュリティには"authentication"(認証)と"authorization"(認可)(もしくは"access-control"(アクセス制御))という大きく二つの領域があります。Spring Securityの対象領域はこの二つになります。"認証"とは、彼らが誰と言ってきているかのプリンシパルを確立するプロセスです("プリンシパル"は、アプリケーション内で何らかのアクションを実行可能な、他システム・デバイス・ユーザなどを基本的には意味します)。"認可"とは、アプリケーション内のあるアクションがプリンシパルに許可されるかどうかを決定するプロセスを指します。プリンシパルのIDが認証プロセスによって確立済みになってから、認可が必要とされます。これらの概念は一般的なもので、Spring Securityに固有のものではありません。

認証において、Spring Securityは様々な認証モデルを提供しています。認証モデルの多くはサードパーティ製か、IETF(Internet Engineering Task Force)などの関連標準団体が開発しています。また、Spring Security固有の認証機能も提供しています。具体的には、現在Spring Securityは以下のすべてのテクノロジとの認証インテグレーション機能を提供しています。

  • HTTP BASIC authentication headers (an IETF RFC-based standard)
  • HTTP Digest authentication headers (an IETF RFC-based standard)
  • HTTP X.509 client certificate exchange (an IETF RFC-based standard)
  • LDAP (a very common approach to cross-platform authentication needs, especially in large environments)
  • Form-based authentication (for simple user interface needs)
  • OpenID authentication
  • Authentication based on pre-established request headers (such as Computer Associates Siteminder)
  • Jasig Central Authentication Service (otherwise known as CAS, which is a popular open source single sign-on system)
  • Transparent authentication context propagation for Remote Method Invocation (RMI) and HttpInvoker (a Spring remoting protocol)
  • Automatic “remember-me” authentication (so you can tick a box to avoid re-authentication for a predetermined period of time)
  • Anonymous authentication (allowing every unauthenticated call to automatically assume a particular security identity)
  • Run-as authentication (which is useful if one call should proceed with a different security identity)
  • Java Authentication and Authorization Service (JAAS)
  • Java EE container authentication (so you can still use Container Managed Authentication if desired)
  • Kerberos
  • Java Open Source Single Sign-On (JOSSO) *
  • OpenNMS Network Management Platform *
  • AppFuse *
  • AndroMDA *
  • Mule ESB *
  • Direct Web Request (DWR) *
  • Grails *
  • Tapestry *
  • JTrac *
  • Jasypt *
  • Roller *
  • Elastic Path *
  • Atlassian Crowd *
  • 自前の認証システム(後述)

サードパーティ製は末尾にアスタリスク(*)付与

独立系ソフトウェアベンダー(ISVs)の多くがSpring Securityを、柔軟性のある認証モデルという重要な選択肢のために採用しています。これにより、多量の開発作業やクライアントに環境入れ替えを要求することなく、クライアントが要求するものとソリューションとを速やかにインテグレーション可能になります。もし要求に合致する認証メカニズムが上述のリストに無い場合、Spring Securityはオープンプラットフォームなので自前の認証メカニズムの開発は極めてシンプルです。Spring Securityの企業ユーザーの多くは、ある特定のセキュリティ標準に従ってるわけではない、"レガシー"システムとのインテグレーションを必要としていますが、Spring Securyの幸福はそれらのシステムで"うまくやる"ことにあります。

認証メカニズムとは直接の関係がないですが、Spring Securityは多くの認可機能を提供します。大きく三つの関心領域があり、webリクエストの認可・メソッドが実行可能かどうかの認可・個々のドメインオブジェクトインスタンスへのアクセスの認可、があります。これらの違いを理解するには、例えば、Servle仕様のwebパターンセキュリティ・EJBコンテナマネージドセキュリティ・ファイルシステムセキュリティ、それぞれにおける認可機能を考えてみてください。Spring Securityはこれらの重要な領域すべてにおける豊富な機能を提供しており、詳細は本リファレンスガイドで触れていきます。

2.2 History

Spring Securityは"The Acegi Security System for Spring"として2003年後半に開始されました。Spring Developer'sメーリングリストに投じられたある質問に、Springベースのセキュリティ実装に何らかの考慮があるかどうか、がありました。当時のSpringコミュニティは今ほど大きくなく(特に規模)、また、Spring自体は2003年初頭からSourceForgeプロジェクトとしてのみ存在していました。先の質問に対する回答は、現在は調査するための時間が無いが、セキュリティは重要な領域である、というものでした。

これを受けて、シンプルなセキュリティ実装が開発されたもののリリースはされませんでした。それから数週間後、別のSpringコミュニティメンバがセキュリティに関する情報を求めたため、そのコードが彼らに提供されました。別口で同じリクエストが何回か来たため、2004年1月までに約20人がそのコードを使用しました。2004年3月に正式設立されたSourceForgeプロジェクトが問題無く進んでいると示したグループに、これらの先駆者ユーザは加わりました。

初期の頃は自前の認証モジュールを持っていませんでした。Container Managed Securityは認証プロセスに依存する一方で、Acegi Securityは認可に焦点を当てていました。これは最初は適切でしたが、ユーザが増えれば増えるほどコンテナサポートの追加を要求するようになり、コンテナ固有の認証レルムインタフェースの根本的な限界が明らかになりました。また、コンテナのクラスパスに新しいJARを追加するという関連する問題もありました。これはユーザが混乱して設定ミスをする一般的な原因でした。

その後Acegi Security固有の認証サービスが導入されました。約1年後、Acegi Securityは公式にSpring Frameworkのサブプロジェクトになりました。多数のソフトウェアプロジェクトへの導入と多数の改良とコミュニティコントリビューションで約二年半の後、1.0.0 final releaseはMay 2006に公開されました。

Acegi Securityは2007年末に公式にSpring Portfolioプロジェクトとなりそして、"Spring Security"に改められました。

今日のSpring Securityは強力でアクティブなオープンソースコミュニティを謳歌しています。サポートフォーラムにはSpring Securityに関する多数のメッセージが存在します。コードに取り組むアクティブな開発者があり、開発者は定期的にパッチを共有して互いにサポートするコミュニティになっています。

2.3 Release Numbering

Spring Securityのリリースナンバリングを知っておくことは場合により有用で、例えば将来のリリース版への移行に必要な作業(または欠如)を特定するのに役立ちます。各リリースは三組の整数でMAJOR.MINOR.PATCHとなります。MAJORバージョンは互換性が無くAPIの大規模アップグレードを意味します。MINORバージョンはソースおよびバイナリ互換性が旧マイナーバージョンと大部分保持されるであろう事を意味し、いくつかの設計変更と非互換アップデートがありうると考えられています。PATCHレベルは完全な互換性が前方・後方共にあり、なおバグフィックスでの変更は例外的に可能です。

変更による影響範囲はコードがどの程度強くSpring Securityを利用しているかに依存します。カスタマイズをしていればいるほど影響を受ける可能性があり、単純なnamespace configurationを使わない場合も同様に高くなります。

新規バージョンのロールアウト前にはアプリケーションの統合的なテストを実施してください。

2.4 Getting Spring Security

Spring Securityの入手方法は何通りかあります。

Spring Securityからパッケージディストリビューションをダウンロードするか、Maven Centralリポジトリ(スナップショットとマイルストーンリリースはSpring Mavenリポジトリ)から個々のjarをダウンロードするか、ソースからビルドしてください。

2.4.1 Usage with Maven

Spring SecurityのMaven依存性の最小構成は基本的には以下のようになります。

pom.xml

<dependencies>
<!-- ... その他の依存性 ... -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>4.2.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>4.2.2.RELEASE</version>
</dependency>
</dependencies>

LDAP, OpenIDなど追加機能を使う場合はSection 2.4.3, “Project Modules”の適切なものを追加してください。

Maven Repositories

すべてのGAリリース(.RELEASEで終わるバージョンのこと)はMaven Centralにデプロイされているため、pomに別途Mavenリポジトリを追加する必要はありません。

SNAPSHOTバージョンを使う場合は以下のようなSpring Snapshot定義をしてください。

pom.xml.

<repositories>
<!-- ... その他のリポジトリ要素 ... -->
<repository>
    <id>spring-snapshot</id>
    <name>Spring Snapshot Repository</name>
    <url>http://repo.spring.io/snapshot</url>
</repository>
</repositories>

マイルストーンもしくはリリース候補バージョンを使う場合は以下のようなSpring Snapshot定義をしてください。

pom.xml.

<repositories>
<!-- ... その他のリポジトリ要素 ... -->
<repository>
    <id>spring-milestone</id>
    <name>Spring Milestone Repository</name>
    <url>http://repo.spring.io/milestone</url>
</repository>
</repositories>
Spring Framework Bom

Spring SecurityはSpring Framework 4.3.5.RELEASEに対して開発されていますが、4.0.xでも動作すると思われます。Spring Securityでかなりのユーザがハマるものに、奇妙なクラスパス問題を引き起こす事がある、Spring Framework 4.3.5.RELEASEの推移的依存性の解決があります。

この穴をかわす方法の一つはpomのセクション内にすべてのSpring Frameworkモジュールを追加します。それとは別のアプローチは以下のようにpom.xmlセクションにspring-framework-bomを追加します。

pom.xml.

<dependencyManagement>
    <dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-framework-bom</artifactId>
        <version>4.3.5.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
    </dependencies>
</dependencyManagement>

上記を使うことでSpring Securityのすべての推移的依存性がSpring 4.3.5.RELEASEモジュールを使うことが保証されます。

上記のアプローチはMavenの"bill of materials" (BOM) を使用しておりMaven 2.0.9+で使用可能です。依存性解決がどのように行われるかについての詳細はMaven’s Introduction to the Dependency Mechanism documentationを参照してください。

2.4.2 Gradle

Spring SecurityのGradleの最小構成の依存性は基本的には以下のようになります。

build.gradle.

dependencies {
    compile 'org.springframework.security:spring-security-web:4.2.2.RELEASE'
    compile 'org.springframework.security:spring-security-config:4.2.2.RELEASE'
}

LDAP, OpenIDなどの追加機能を使う場合はSection 2.4.3, “Project Modules”の適当なものを追加してください。

Gradle Repositories

すべてのGAリリース(バージョンが.RELEASEで終わるもの)はMaven Centralにデプロイされているため、GAリリースの場合にはmavenCentral()リポジトリを使います。

build.gradle.

repositories {
    mavenCentral()
}

SNAPSHOTバージョンを使う場合は以下のようなSpring Snapshotリポジトリの定義をしてください。

build.gradle.

repositories {
    maven { url 'https://repo.spring.io/snapshot' }
}

マイルストーンもしくはリリース候補バージョンを使う場合は以下のようなSpring Milestoneリポジトリの定義をしてください。

build.gradle.

repositories {
    maven { url 'https://repo.spring.io/milestone' }
}
Using Spring 4.0.x and Gradle

デフォルトではGradleは依存性バージョンの解決には最新のバージョンを使用します。つまりSpring Framework 4.3.5.RELEASEでSpring Security 4.2.2.RELEASEを動かす場合にはそれ以上の作業が必要無い場合があるということです。とはいえ、問題が発生することもあるのでその際には以下のようにGradle’s ResolutionStrategyで回避策を取ると良いでしょう。

build.gradle.

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        if (details.requested.group == 'org.springframework') {
            details.useVersion '4.3.5.RELEASE'
        }
    }
}

上記によりSpring Securityのすべての推移的依存性がSpring 4.3.5.RELEASEモジュールを使うことが保証されます。

上記のサンプルはGradle 1.9を使用していますが、この機能はGradleのincubating feature*1なのでGradleの最新バージョンでは動かすのに修正が必要かもしれません。

2.4.3 Project Modules

Spring Security 3.0のコードベースは複数のjarに分割されており、異なる機能領域とサードパーティ依存性を分離しています。プロジェクトのビルドにMavanを使う場合、pom.xmlに追加するモジュールがそれらのjarになります。Mavanを使わない場合であっても、サードパーティ依存性とバージョンのためにpom.xmlを編集することを推奨します。もしくは、サンプルアプリケーションに含まれるライブラリを調査することもお勧めします。

Core - spring-security-core.jar

コアの認証・アクセス制御クラスとインタフェース・リモーティングサポート・基本的なプロビジョニングAPIが含まれます。Spring Securityを使用するアプリケーションすべてで必要です。スタンドアローンアプリケーション・リモートクライアント・メソッド(サービスレイヤー)セキュリティ・JDBCユーザプロビジョニングをサポートします。トップレベルパッケージは以下の通りです。

  • org.springframework.security.core
  • org.springframework.security.access
  • org.springframework.security.authentication
  • org.springframework.security.provisioning
Remoting - spring-security-remoting.jar

Spring Remotingとの連携機能を提供します。Spring Remotingを使用するリモートクライアントを作成しない限り不要です。メインパッケージはorg.springframework.security.remotingです。

Web - spring-security-web.jar

フィルタと関連webセキュリティ基盤のコードが含まれます。servlet APIへの依存性があります。Spring Securityのweb認証サービスとURLベースアクセス制御を必要とする場合に使用します。メインパッケージはorg.springframework.security.webです。

Config - spring-security-config.jar

security namespaceをパースするコードとJava configurationのコードが含まれます。設定にSpring Security XML namespceあるいはSpring Securityのava Configurationサポートを使う場合に必要となります。メインパッケージはorg.springframework.security.configです。これらのクラスはアプリケーションでの直接使用は想定していません。

LDAP - spring-security-ldap.jar

LDAP認証とプロビジョニングです。LDAP認証もしくはLDAPユーザエントリの管理を使う場合に必要です。トップレベルのパッケージはorg.springframework.security.ldapです。

ACL - spring-security-acl.jar

特殊化されたドメインオブジェクトのACL実装です。アプリケーションの特定のドメインオブジェクトインスタンスにセキュリティを適用ために使います。トップレベルのパッケージはorg.springframework.security.aclsです。

CAS - spring-security-cas.jar

Spring SecurityのCASクライアント連携機能です。CASシングルサインオンサーバでSpring Securityのweb認証を使う場合に使用します。トップレベルのパッケージはorg.springframework.security.casです。

OpenID - spring-security-openid.jar

OpenIDのweb認証サポート機能です。外部のOpenIDサーバに対してユーザを認証するのに使用します。org.springframework.security.openid。OpenID4Javaが必要です。

Test - spring-security-test.jar

Spring Securityでのテストをサポートする機能です。

2.4.4 Checking out the Source

Spring Securityはオープンソースプロジェクトなのでgitでソースコードをチェックアウトしてみることを強くお勧めします。サンプルアプリケーションのすべてのコードを入手可能で、プロジェクトの最新バージョンを手軽にビルドしてみることも可能です。また、プロジェクトのソースを見れることはデバッグの大きな助けになります。例外のスタックトレースブラックボックス内の何らかの問題を指すだけだった時代は過去となり、何かが起きている行のコードをすぐに参照して調査ができます。ソースコードはプロジェクトの究極的なドキュメントであり、実際の動作を知るのに最も適している場合があります。

プロジェクトのソースを取得するには以下のgitコマンドを使います。

git clone https://github.com/spring-projects/spring-security.git

これによりローカルマシン上でプロジェクトヒストリー(リリースおよびブランチを含む)の全体にアクセスできます。

3. What’s New in Spring Security 4.2

Spring Security 4.2はSpring Framework 5に対する早期サポートを提供しています。80以上のissueをクローズしているチェンジログを、4.2.0.M1, 4.2.0.RC1, 4.2.0.RELEASEから参照できます。これら機能の大部分はコミュニティによるものです。以下が今回リリースのハイライトになります。

(省略)

4. Samples and Guides (Start Here)

Spring Securityを使い始めようとする場合、サンプルアプリケーションから始めるのが最良です。

Table 4.1. Sample Applications

Source Description Guide
Hello Spring Security Java-based configurationを使用している既存アプリケーションとのSpring Security連携の紹介 Hello Spring Security Guide
Hello Spring Security Boot 既存のSpring BootアプリケーションとのSpring Security連携の紹介 Hello Spring Security Boot Guide
Hello Spring Security XML XML-based configurationを使用している既存アプリケーションとのSpring Security連携の紹介 Hello Spring Security XML Guide
Hello Spring MVC Security 既存のSpring MVCアプリケーションとのSpring Security連携の紹介 Hello Spring MVC Security Guide
Custom Login Form ログインフォームをカスタマイズする方法の紹介 Custom Login Form Guide

5. Java Configuration

Java ConfigurationがSpring 3.1でSpring Frameworkに追加されています。Spring Security 3.2以降でSpring Security Java Configurationは存在し、XML無しでSpring Securityを簡単に設定できます。

Chapter6, Security Namespace Configurationに慣れている場合、Security Java Configurationとほとんど同じ事に気付くしょう。

Spring SecurityではSpring Security Java Configurationの使い方のサンプルをlots of sample applicationsで多数紹介しています。

5.1 Hello Web Security Java Configuration

最初はSpring Security Java Configurationを作ることから始めます。configurationはspringSecurityFilterChainと呼ばれるServlet Filterを生成し、これはアプリケーション内のすべてのセキュリティ(アプリケーションURLの保護・サブミットされたユーザ名とパスワードの検証・ログインフォームへのリダイレクトなど)に対する責任を持ちます。以下はSpring Security Java Configurationの最も基本的な形の例です。

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.configuration.*;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public UserDetailsService userDetailsService() throws Exception {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("user").password("password").roles("USER").build());
        return manager;
    }
}

このconfigurationはそれほど分量はありませんが、多くのことを行います。それら機能のサマリは以下の通りです。

5.1.1 AbstractSecurityWebApplicationInitializer

次はwarにspringSecurityFilterChainを登録します。Servlet 3.0+環境ではSpring’s WebApplicationInitializer supportを使用してJava Configurationで設定できます。なお、Spring SecurityはベースクラスAbstractSecurityWebApplicationInitializerを提供しており、このクラスはspringSecurityFilterChainの登録をユーザの代わりに行ってくれます。AbstractSecurityWebApplicationInitializerを使う方法は、Springをすでに使用しているか、Spring Securityがアプリケーションで単なるSpringコンポーネントなのか、に依存します。

5.1.2 AbstractSecurityWebApplicationInitializer without Existing Spring

SpringやSpring MVCを使用しない場合、configurationが参照されるようにスーパークラスWebSecurityConfigを渡します。以下はその例です。

import org.springframework.security.web.context.*;

public class SecurityWebApplicationInitializer
    extends AbstractSecurityWebApplicationInitializer {

    public SecurityWebApplicationInitializer() {
        super(WebSecurityConfig.class);
    }
}

SecurityWebApplicationInitializerは以下を行います。

  • アプリケーションのすべてのURLにspringSecurityFilterChain Filterを自動的に登録。
  • WebSecurityConfigをロードするContextLoaderListenerを追加

5.1.3 AbstractSecurityWebApplicationInitializer with Spring MVC

アプリケーションで何らかの形でSpringをすでに使用している場合、Spring ConfigurationをロードするWebApplicationInitializerがおそらく存在します。すぐ前で見たconfigurationを使うとおそらくエラーになります。このconfigurationを使うのではなく、既存のApplicationContextでSpring Securityを登録して下さい。たとえば、Spring MVCを使っている場合、以下のようなSecurityWebApplicationInitializerになります。

import org.springframework.security.web.context.*;

public class SecurityWebApplicationInitializer
    extends AbstractSecurityWebApplicationInitializer {

}

この設定はアプリケーションのすべてのURLにspringSecurityFilterChain Filterを登録することのみ行います。つぎに、既存のApplicationInitializerでWebSecurityConfigがロードされるようにします。たとえば、Spring MVCを使用している場合、getRootConfigClasses()にこれを追加します。

public class MvcWebApplicationInitializer extends
        AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] { WebSecurityConfig.class };
    }

    // ... other overrides ...
}

5.2 HttpSecurity

ここまでに行ったWebSecurityConfigは単にユーザ認証の方法だけが書かれています。この設定で、Spring Securityはどのようにすべてのユーザに認証を要求する、と解釈しているのでしょうか? また、フォームベースの認証になるのはどうやっているのでしょうか? その理由はWebSecurityConfigurerAdapterがデフォルト設定をしているからで、その中のconfigure(HttpSecurity http)メソッドは以下のようになっています。

protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .anyRequest().authenticated()
            .and()
        .formLogin()
            .and()
        .httpBasic();
}

上記のデフォルト設定は以下を行います。

  • アプリケーションに対するすべてのリクエストは認証済みのユーザを要求する。
  • フォームベースのログインでユーザを認証する。
  • HTTP Basic認証でユーザを認証する。

XML Namespace configurationでの同様な設定は以下となります。

<http>
    <intercept-url pattern="/**" access="authenticated"/>
    <form-login />
    <http-basic />
</http>

XMLの閉じタグと同等な表現はJava Configurationではand()を使用し、これにより親から続けて設定を行えます。コードを読む際にはその書いてある通りに解釈します。リクエストの認証を設定andフォームログインを設定andHTTP Basic認証を設定、となります。

ただし、Java Configuratioは異なるデフォルトURLとパラメータになります。カスタムログインページを作成する場合にはこれを覚えておいてください。URLはRESTfulになります。Additionally, it is not quite so obvious we are using Spring Security which helps to prevent information leaks. For example:

5.3 Java Configuration and Form Login

ログインのプロンプトを表示したときにそのログインフォームがどこから来たのか、HTMLファイルやJSPについて何も触れていないので、不思議に思うかもしれません。

Spring Securityのデフォルト設定はログインページに明示的なURLを設定せず、Spring Securityは有効化されている機能でこれを自動生成します。ログインのサブミットを処理するURLには標準の値を使用します。ユーザログイン後にそのデフォルトターゲットURLが送信されます。

自動生成されるログインページは手っ取り早く動かす分には便利ですが、基本的には自前のログインページを作成するでしょう。これを行うには以下のように設定を書き換えます。

protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .anyRequest().authenticated()
            .and()
        .formLogin()
            .loginPage("/login") //1
            .permitAll();        //2
}
  1. この箇所でログインページの場所を指定している。
  2. ログインページのアクセスにはすべてのユーザ(つまり未認証のユーザ)を許可する。formLogin().permitAll()メソッドはフォームベースのログインで関連付けられたすべてのURLに対してすべてのユーザにアクセスを許可します。

これまでの設定でJSPでログインページを実装する例は以下となります。

以下のログインページはこれまでの設定で書かれています。デフォルトを変更したい場合には好きなように書き換えが可能です。

<c:url value="/login" var="loginUrl"/>
<form action="${loginUrl}" method="post">       1
    <c:if test="${param.error != null}">        2
        <p>
            Invalid username and password.
        </p>
    </c:if>
    <c:if test="${param.logout != null}">       3
        <p>
            You have been logged out.
        </p>
    </c:if>
    <p>
        <label for="username">Username</label>
        <input type="text" id="username" name="username"/>  4
    </p>
    <p>
        <label for="password">Password</label>
        <input type="password" id="password" name="password"/>  5
    </p>
    <input type="hidden"                        6
        name="${_csrf.parameterName}"
        value="${_csrf.token}"/>
    <button type="submit" class="btn">Log in</button>
</form>
  1. /loginURLへのPOSTでユーザ認証
  2. クエリパラメータerrorがある場合、認証を実行したが失敗
  3. クエリパラメータlogoutがある場合、ユーザが正常にログアウト
  4. ユーザ名はHTTPパラメータ名usernameにしなければならない
  5. パスワードはHTTPパラメータ名passwordにしなければならない
  6. Section 18.4.3, “Include the CSRF Token”を含めなければならない。詳細についてはChapter 18, Cross Site Request Forgery (CSRF)を参照してください。

5.4 Authorize Requests

今のところのサンプルは認証されたユーザを必須とし、アプリケーションのすべてのURLで認証を必要とします。http.authorizeRequests()に複数の子を追加することでURLにカスタム要求を定義できます。

protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()                                                                1
            .antMatchers("/resources/**", "/signup", "/about").permitAll()                  2
            .antMatchers("/admin/**").hasRole("ADMIN")                                      3
            .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")            4
            .anyRequest().authenticated()                                                   5
            .and()
        // ...
        .formLogin();
}
  1. http.authorizeRequests()メソッドに複数の子があり、各matcherの宣言順序は意味を持ちます。
  2. すべてのユーザがアクセス可能な複数のURLパターンを指定しています。具体的には、URLが"/resources/“で始まる、”/signup"もしくは"/about"と一致するものに、すべてのユーザはアクセスできます。
  3. “/admin/"で始まるすべてのURLは"ROLE_ADMIN"ロールを持つユーザに制限されます。hasRoleメソッドを使用しているため、"ROLE_"プレフィクスを指定する必要が無い点に注意してください。
  4. “/db/"で始まるすべてのURLは"ROLE_ADMIN"と"ROLE_DBA"両方を持つユーザを必要とします。hasRole句を使用しているため、"ROLE_"プレフィクスを指定する必要が無い点に注意してください。
  5. 上記いずれにもマッチしなかったURLは認証されたユーザを必要とします。

5.5 Handling Logouts

WebSecurityConfigurerAdapterを使用する場合、ログアウト機能は自動的に組み込まれます。デフォルトでは/logoutURLにアクセスするとユーザはログアウトします。このとき、以下が行われます。

  • HTTP Session無効化
  • 設定されているすべてのRememberMe認証をクリーンアップ
  • SecurityContextHolderのクリア
  • /login?logoutへリダイレクト

ログイン機能の設定と同様ですが、ログアウト要求をカスタマイズするための各種オプションがあります。

protected void configure(HttpSecurity http) throws Exception {
    http
        .logout()                                                                1
            .logoutUrl("/my/logout")                                                 2
            .logoutSuccessUrl("/my/index")                                           3
            .logoutSuccessHandler(logoutSuccessHandler)                              4
            .invalidateHttpSession(true)                                             5
            .addLogoutHandler(logoutHandler)                                         6
            .deleteCookies(cookieNamesToClear)                                       7
            .and()
        ...
}
  1. ログアウトサポートの使用。WebSecurityConfigurerAdapterを使用する場合は自動的に適用される。
  2. ログアウトをトリガするためのURL(デフォルトは/logout)。CSRFプロテクションが有効化(デフォルトで有効化)されている場合、リクエストはPOSTでなければならない。詳細についてはJavaDocを参照。
  3. ログアウト後のリダイレクト先URL。デフォルトは/login?logout。詳細についてはJavaDocを参照。
  4. カスタムのLogoutSuccessHandlerを指定します。これを指定する場合はlogoutSuccessUrl()は無視されます。詳細についてはJavaDocを参照。
  5. ログアウト時にHttpSessionを無効化するかどうかを指定します。デフォルトでtrueです。裏でSecurityContextLogoutHandlerを設定します。詳細についてはJavaDocを参照。
  6. LogoutHandlerの追加。デフォルトでLogoutHandlerの末尾にSecurityContextLogoutHandlerが追加されます。
  7. ログアウト成功時に削除するクッキーの名前を指定可能。これは明示的にCookieClearingLogoutHandlerを追加するもののショートカット版です。

XML Namespace記法でログアウトを設定することも当然出来ます。詳細についてはSpring Security XML Namespaceセクションのlogout elementを参照してください。

基本的には、ログアウト機能をカスタマイズする場合、LogoutHandlerLogoutSuccessHandlerの実装を作成します。fluent APIを使用すると、よくある使い方のためのハンドラが裏で適用されます。

5.5.1 LogoutHandler

基本的に、LogoutHandlerの実装はログアウト処理の一部として振る舞うクラスとなります。このクラスは必要なクリーンアップの実行が期待されます。そのため例外はスローしてはいけません。いくつかの実装をSpring Securityは提供しています。

詳細はSection 17.4, “Remember-Me Interfaces and Implementations”を参照してください。

LogoutHandlerの実装を直接登録するやり方とは別に、LogoutHandlerの各実装が裏で登録されるfluent APIのショートカットも存在します。例えば、deleteCookies()はログアウト正常終了時に削除するクッキー名を一つ以上指定します。このショートカットはCookieClearingLogoutHandlerの追加と同等です。

5.5.2 LogoutSuccessHandler

LogoutFilterは適当な宛先にリダイレクトあるいはフォワードする処理を行い、このフィルタがログアウトを正常処理した後にLogoutSuccessHandlerが呼ばれます。このインタフェースはおおむねLogoutHandlerと同じですが、例外をスローする可能性があります。

以下の実装があります。

上述の通り、SimpleUrlLogoutSuccessHandlerの直接指定は必要ありません。代わりに、logoutSuccessUrl()によるfluent APIのショートカットがあります。これは裏でSimpleUrlLogoutSuccessHandlerをセットアップします。与えられるURLがログアウト発生後のリダイレクト先になります。デフォルトは/login?logoutです。

HttpStatusReturningLogoutSuccessHandlerREST APIに適します。ログアウト正常終了後にURLにリダイレクトせず、LogoutSuccessHandlerには返すHTTPステータスコードを指定しまs。指定しない場合はデフォルトでステータスコード200を返します。

5.5.3 Further Logout-Related References

5.6 Authentication

ここまで最も基本的な認証の設定を見てきました。次にもう少し詳しい認証設定のオプションについて見ていきます。

5.6.1 In-Memory Authentication

これまでのサンプルで単一ユーザのインメモリ認証の設定は既に見ています。以下は複数ユーザを設定する例です。

@Bean
public UserDetailsService userDetailsService() throws Exception {
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(User.withUsername("user").password("password").roles("USER").build());
    manager.createUser(User.withUsername("admin").password("password").roles("USER","ADMIN").build());
    return manager;
}

5.6.2 JDBC Authentication

JDBCベースの認証です。以下の例はアプリケーションにDataSourceが定義済みとします。jdbc-javaconfigのサンプルでJDBCベースの認証を使用しています。

@Autowired
private DataSource dataSource;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth
        .jdbcAuthentication()
            .dataSource(dataSource)
            .withDefaultSchema()
            .withUser("user").password("password").roles("USER").and()
            .withUser("admin").password("password").roles("USER", "ADMIN");
}

5.6.3 LDAP Authentication

LDAPベースの認証です。ldap-javaconfigのサンプルでLDAPベースの認証を使用しています。

@Autowired
private DataSource dataSource;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth
        .ldapAuthentication()
            .userDnPatterns("uid={0},ou=people")
            .groupSearchBase("ou=groups");
}

上の例は以下のLDIFと組み込みApache DS LDAPインスタンスを使用します。

users.ldif

dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups

dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people

dn: uid=admin,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Rod Johnson
sn: Johnson
uid: admin
userPassword: password

dn: uid=user,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Dianne Emu
sn: Emu
uid: user
userPassword: password

dn: cn=user,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: user
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
uniqueMember: uid=user,ou=people,dc=springframework,dc=org

dn: cn=admin,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: admin
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org

5.6.4 AuthenticationProvider

ビーンとしてAuthenticationProviderを定義することでカスタムの認証を作れます。例えば、以下のようにAuthenticationProviderを実装するSpringAuthenticationProviderを作ることでカスタムの認証を行います。

これはAuthenticationManagerBuilderが処理されない場合にだけ使用します。

@Bean
public SpringAuthenticationProvider springAuthenticationProvider() {
    return new SpringAuthenticationProvider();
}

5.6.5 UserDetailsService

ビーンとしてUserDetailsServiceを定義することでカスタムの認証を作れます。例えば、以下のようにUserDetailsServiceを実装するSpringDataUserDetailsServiceを作ることでカスタムの認証を行います。

これはAuthenticationManagerBuilderが処理されないかつAuthenticationProviderBeanの定義が無い場合にだけ使用します。

@Bean
public SpringDataUserDetailsService springDataUserDetailsService() {
    return new SpringDataUserDetailsService();
}

また、ビーンとしてPasswordEncoderを定義することでパスワードのエンコード方法をカスタマイズ出来ます。例として、bcryptを使う場合は以下のようなビーン定義を追加します。

@Bean
public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

5.6.6 LDAP Authentication

5.7 Multiple HttpSecurity

複数の<http>ブロックを持つ感じで複数のHttpSecurityインスタンスを設定できます。カギはWebSecurityConfigurationAdapterの継承を複数個用意することです。例えば、以下は/api/で始まるURL用の設定とは別にもう一つ設けています。

@EnableWebSecurity
public class MultiHttpSecurityConfig {
    @Bean
    public UserDetailsService userDetailsService() throws Exception {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("user").password("password").roles("USER").build());
        manager.createUser(User.withUsername("admin").password("password").roles("USER","ADMIN").build());
        return manager;
    }

    @Configuration
    @Order(1)                                                        1
    public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        protected void configure(HttpSecurity http) throws Exception {
            http
                .antMatcher("/api/**")                               2
                .authorizeRequests()
                    .anyRequest().hasRole("ADMIN")
                    .and()
                .httpBasic();
        }
    }

    @Configuration                                                   3
    public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                .formLogin();
        }
    }
}

これまで通り認証のconfigurationを作ります。

  1. @Orderを持つWebSecurityConfigurerAdapterインスタンスを作成し、@Orderでこのクラスが先に参照されるようにしています。
  2. http.antMatcherでこのHttpSecurity/api/で始まるURLにのみ適用されるようにしています。
  3. 2.とは別のWebSecurityConfigurerAdapterを作成します。URLが/api/で始まらない場合にこちらの設定が使われます。@Order1を指定しているため、この設定はApiWebSecurityConfigurationAdapterの後になります。(@Order無しのデフォルトは末尾)

*1:試験的な開発中機能の意味。ただ、これ書いてる時点でgradleは3.5とかなのでもうincubatingでも何でもないかもしれない