Java 構成
Java 構成の一般的なサポートが Spring 3.1 の Spring Framework に追加されました。Spring Security 3.2 は、ユーザーが XML を使用せずに Spring Security を構成できるようにする Java 構成を導入しました。
セキュリティ名前空間の構成に精通している場合は、セキュリティ名前空間の構成と Spring Security Java 構成の間にかなりの類似点があるはずです。
Spring Security は、Spring Security Java 構成の使用法を示すための多くのサンプルアプリケーションを提供 [GitHub] (英語) します。 |
Hello Web セキュリティ Java 構成
最初のステップは、Spring Security Java 構成を作成することです。この構成により、springSecurityFilterChain
と呼ばれるサーブレットフィルターが作成されます。これは、アプリケーション内のすべてのセキュリティ(アプリケーションの URL の保護、送信されたユーザー名とパスワードの検証、ログインフォームへのリダイレクトなど)を担当します。次の例は、Spring Security Java 構成の最も基本的な例を示しています。
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.*;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build());
return manager;
}
}
この構成は複雑でも大規模でもありませんが、多くのことを行います。
アプリケーションのすべての URL に認証を要求する
ログインフォームを生成します
user
のユーザー名とpassword
のパスワードを持つユーザーにフォームベース認証で認証させますユーザーにログアウトさせます
セキュリティヘッダーの統合:
セキュアリクエストの HTTP Strict Transport Security [Wikipedia]
キャッシュ制御 (これは、アプリケーションの後半でオーバーライドして、静的リソースのキャッシュを許可できます。)
クリックジャッキング [Wikipedia] の防止に役立つ X-Frame-Options の統合
次のサーブレット API メソッドとの統合:
AbstractSecurityWebApplicationInitializer
次のステップは、springSecurityFilterChain
を WAR ファイルに登録することです。これは、Servlet 3.0+ 環境の Spring の WebApplicationInitializer
サポートを使用した Java 構成で行うことができます。当然のことながら、Spring Security は、springSecurityFilterChain
が確実に登録されるようにするための基本クラス(AbstractSecurityWebApplicationInitializer
)を提供します。AbstractSecurityWebApplicationInitializer
の使用方法は、すでに Spring を使用しているかどうか、Spring Security がアプリケーションで唯一の Spring コンポーネントであるかどうかによって異なります。
Spring が存在しない AbstractSecurityWebApplicationInitializer - Spring をまだ使用していない場合は、これらの手順を使用してください
Spring MVC を使用した AbstractSecurityWebApplicationInitializer - すでに Spring を使用している場合は、これらの手順を使用してください
Spring が存在しない AbstractSecurityWebApplicationInitializer
Spring または Spring MVC を使用していない場合は、WebSecurityConfig
をスーパークラスに渡して、構成が確実に取得されるようにする必要があります。
import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(WebSecurityConfig.class);
}
}
SecurityWebApplicationInitializer
:
アプリケーション内のすべての URL に対して
springSecurityFilterChain
フィルターを自動的に登録します。WebSecurityConfig をロードする
ContextLoaderListener
を追加します。
Spring MVC を使用した AbstractSecurityWebApplicationInitializer
アプリケーションの他の場所で Spring を使用している場合は、Spring 構成をロードしている WebApplicationInitializer
がすでに存在している可能性があります。以前の構成を使用すると、エラーが発生します。代わりに、Spring Security を既存の ApplicationContext
に登録する必要があります。例: Spring MVC を使用する場合、SecurityWebApplicationInitializer
は次のようになります。
import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
}
これにより、アプリケーション内のすべての URL に対して springSecurityFilterChain
が登録されるだけです。その後、WebSecurityConfig
が既存の ApplicationInitializer
にロードされていることを確認する必要があります。例: Spring MVC を使用する場合、getServletConfigClasses()
に追加されます。
public class MvcWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
}
// ... other overrides ...
}
その理由は、基盤となるリクエストマッチャーを適切に構成するために、Spring Security が一部の Spring MVC 構成をインスペクションできる必要があるため、それらは同じアプリケーションコンテキスト内にある必要があるためです。Spring Security を getRootConfigClasses
に配置すると、Spring MVC の HandlerMappingIntrospector
を見つけられない可能性がある親アプリケーションコンテキストに配置されます。
複数の Spring MVC ディスパッチャーの構成
必要に応じて、Spring MVC に関係のない Spring Security 構成を次のように別の構成クラスに配置できます。
public class MvcWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { NonWebSecurityConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
}
// ... other overrides ...
}
これは、AbstractAnnotationConfigDispatcherServletInitializer
の複数のインスタンスがあり、それらの両方で一般的なセキュリティ構成を複製したくない場合に役立ちます。
HttpSecurity
これまでのところ、WebSecurityConfig
には、ユーザーを認証する方法に関する情報のみが含まれています。Spring Security は、すべてのユーザーに認証を要求することをどのように認識しますか? Spring Security は、フォームベース認証をサポートすることをどのように認識していますか? 実際には、バックグラウンドで呼び出されている構成クラス(SecurityFilterChain
と呼ばれる)があります。これは、次のデフォルトの実装で構成されています。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults());
return http.build();
}
デフォルトの構成(前の例に示されています):
アプリケーションへのリクエストでは、ユーザーの認証が必要であることを保証します
ユーザーがフォームベースのログインで認証できるようにします
ユーザーが HTTP 基本認証で認証できるようにします
この構成は XML 名前空間の構成と類似していることに注意してください。
<http>
<intercept-url pattern="/**" access="authenticated"/>
<form-login />
<http-basic />
</http>
複数の HttpSecurity インスタンス
特定の領域に異なる保護が必要なアプリケーションのセキュリティを効果的に管理するには、securityMatcher
DSL メソッドと並行して複数のフィルターチェーンを使用します。このアプローチにより、アプリケーションの特定の部分に合わせて個別のセキュリティ構成を定義し、アプリケーション全体のセキュリティと制御を強化できます。
XML で複数の <http>
ブロックを持つことができるのと同じように、複数の HttpSecurity
インスタンスを構成できます。重要なのは、複数の SecurityFilterChain
または @Bean
を登録することです。次の例では、/api/
で始まる URL に対して異なる構成が使用されています。
@Configuration
@EnableWebSecurity
public class MultiHttpSecurityConfig {
@Bean (1)
public UserDetailsService userDetailsService() throws Exception {
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build());
return manager;
}
@Bean
@Order(1) (2)
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**") (3)
.authorizeHttpRequests(authorize -> authorize
.anyRequest().hasRole("ADMIN")
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean (4)
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
}
1 | 通常どおり認証を構成します。 |
2 | @Order を含む SecurityFilterChain のインスタンスを作成して、どの SecurityFilterChain を最初に考慮するかを指定します。 |
3 | http.securityMatcher() では、この HttpSecurity は /api/ で始まる URL にのみ適用されることが規定されています。 |
4 | SecurityFilterChain の別のインスタンスを作成します。URL が /api/ で始まらない場合は、この構成が使用されます。この構成は、1 の後に @Order 値があるため、apiFilterChain の後に考慮されます (@Order はデフォルトで最後にはなりません)。 |
securityMatcher
または requestMatchers
の選択
よくある質問は次のとおりです。
リクエストの認可に使用される
http.securityMatcher()
メソッドとrequestMatchers()
(つまり、http.authorizeHttpRequests()
内部) の違いは何ですか ?
この質問に答えるには、SecurityFilterChain
の構築に使用される各 HttpSecurity
インスタンスに、受信リクエストに一致する RequestMatcher
が含まれていることを理解すると役立ちます。リクエストが優先度の高い SecurityFilterChain
(例: @Order(1)
) と一致しない場合、リクエストは優先度の低いフィルターチェーン (例: @Order
なし) に対して試行されます。
複数のフィルターチェーンのマッチングロジックは、 |
デフォルトの RequestMatcher
はすべてのリクエストに一致し、Spring Security がデフォルトですべてのリクエストを保護するようにします。
|
特定のリクエストに一致するフィルターチェーンがない場合、そのリクエストは Spring Security によって保護されません。 |
次の例は、/secured/
で始まるリクエストのみを保護する単一のフィルターチェーンを示しています。
@Configuration
@EnableWebSecurity
public class PartialSecurityConfig {
@Bean
public UserDetailsService userDetailsService() throws Exception {
// ...
}
@Bean
public SecurityFilterChain securedFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/secured/**") (1)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/secured/user").hasRole("USER") (2)
.requestMatchers("/secured/admin").hasRole("ADMIN") (3)
.anyRequest().authenticated() (4)
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults());
return http.build();
}
}
1 | /secured/ で始まるリクエストは保護されますが、その他のリクエストは保護されません。 |
2 | /secured/user へのリクエストには ROLE_USER 権限が必要です。 |
3 | /secured/admin へのリクエストには ROLE_ADMIN 権限が必要です。 |
4 | その他のリクエスト (/secured/other など) では、認証されたユーザーのみが必要です。 |
前の例で示したように、アプリケーション全体が保護されるように、 |
requestMatchers
メソッドは個々の認可ルールにのみ適用されることに注意してください。そこにリストされている各リクエストは、SecurityFilterChain
の作成に使用されたこの特定の HttpSecurity
インスタンスの全体的な securityMatcher
とも一致する必要があります。この例で anyRequest()
を使用すると、この特定の SecurityFilterChain
( /secured/
で始まる必要があります) 内の他のすべてのリクエストと一致します。
|
SecurityFilterChain
エンドポイント
SecurityFilterChain
のいくつかのフィルターは、http.formLogin()
によってセットアップされ、POST /login
エンドポイントを提供する UsernamePasswordAuthenticationFilter
など、エンドポイントを直接提供します。上記の例では、/login
エンドポイントは http.securityMatcher("/secured/**")
と一致しないため、そのアプリケーションには GET /login
または POST /login
エンドポイントがありません。このようなリクエストは 404 Not Found
を返します。これはユーザーにとってしばしば驚きです。
http.securityMatcher()
を指定すると、その SecurityFilterChain
に一致するリクエストに影響します。ただし、フィルターチェーンによって提供されるエンドポイントには自動的に影響しません。このような場合は、フィルターチェーンが提供するエンドポイントの URL をカスタマイズする必要がある場合があります。
次の例は、/secured/
で始まるリクエストを保護し、他のすべてのリクエストを拒否するとともに、SecurityFilterChain
によって提供されるエンドポイントをカスタマイズする構成を示しています。
@Configuration
@EnableWebSecurity
public class SecuredSecurityConfig {
@Bean
public UserDetailsService userDetailsService() throws Exception {
// ...
}
@Bean
@Order(1)
public SecurityFilterChain securedFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/secured/**") (1)
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated() (2)
)
.formLogin(formLogin -> formLogin (3)
.loginPage("/secured/login")
.loginProcessingUrl("/secured/login")
.permitAll()
)
.logout(logout -> logout (4)
.logoutUrl("/secured/logout")
.logoutSuccessUrl("/secured/login?logout")
.permitAll()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().denyAll() (5)
);
return http.build();
}
}
1 | /secured/ で始まるリクエストは、このフィルターチェーンによって保護されます。 |
2 | /secured/ で始まるリクエストには認証されたユーザーが必要です。 |
3 | フォームログインをカスタマイズして、URL の先頭に /secured/ を付けます。 |
4 | ログアウトをカスタマイズして、URL のプレフィックスに /secured/ を付けます。 |
5 | その他のリクエストはすべて拒否されます。 |
この例では、ログインページとログアウトページをカスタマイズし、Spring Security の生成されたページを無効にします。 |
実世界の例
次の例は、これらすべての要素を組み合わせた、もう少し現実的な構成を示しています。
@Configuration
@EnableWebSecurity
public class BankingSecurityConfig {
@Bean (1)
public UserDetailsService userDetailsService() {
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user1").password("password").roles("USER", "VIEW_BALANCE").build());
manager.createUser(users.username("user2").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("ADMIN").build());
return manager;
}
@Bean
@Order(1) (2)
public SecurityFilterChain approvalsSecurityFilterChain(HttpSecurity http) throws Exception {
String[] approvalsPaths = { "/accounts/approvals/**", "/loans/approvals/**", "/credit-cards/approvals/**" };
http
.securityMatcher(approvalsPaths)
.authorizeHttpRequests(authorize -> authorize
.anyRequest().hasRole("ADMIN")
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
@Order(2) (3)
public SecurityFilterChain bankingSecurityFilterChain(HttpSecurity http) throws Exception {
String[] bankingPaths = { "/accounts/**", "/loans/**", "/credit-cards/**", "/balances/**" };
String[] viewBalancePaths = { "/balances/**" };
http
.securityMatcher(bankingPaths)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(viewBalancePaths).hasRole("VIEW_BALANCE")
.anyRequest().hasRole("USER")
);
return http.build();
}
@Bean (4)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
String[] allowedPaths = { "/", "/user-login", "/user-logout", "/notices", "/contact", "/register" };
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(allowedPaths).permitAll()
.anyRequest().authenticated()
)
.formLogin(formLogin -> formLogin
.loginPage("/user-login")
.loginProcessingUrl("/user-login")
)
.logout(logout -> logout
.logoutUrl("/user-logout")
.logoutSuccessUrl("/?logout")
);
return http.build();
}
}
1 | まず認証設定を構成します。 |
2 | @Order(1) を使用して SecurityFilterChain インスタンスを定義します。つまり、このフィルターチェーンの優先度が最も高くなります。このフィルターチェーンは、/accounts/approvals/ 、/loans/approvals/ 、または /credit-cards/approvals/ で始まるリクエストにのみ適用されます。このフィルターチェーンへのリクエストには ROLE_ADMIN 権限が必要であり、HTTP 基本認証が許可されます。 |
3 | 次に、2 番目とみなされる @Order(2) を使用して、別の SecurityFilterChain インスタンスを作成します。このフィルターチェーンは、/accounts/ 、/loans/ 、/credit-cards/ または /balances/ で始まるリクエストにのみ適用されます。このフィルターチェーンは 2 番目であるため、/approvals/ を含むすべてのリクエストは前のフィルターチェーンと一致し、このフィルターチェーンとは一致しないことに注意してください。このフィルターチェーンへのリクエストには、ROLE_USER 権限が必要です。このフィルターチェーンは、次の (デフォルトの) フィルターチェーンにその構成が含まれているため、認証を定義しません。 |
4 | 最後に、@Order アノテーションなしで追加の SecurityFilterChain インスタンスを作成します。この構成は、他のフィルターチェーンでカバーされていないリクエストを処理し、最後に処理されます (@Order がない場合は最後にデフォルトで処理されます)。/ 、/user-login 、/user-logout 、/notices 、/contact 、/register に一致するリクエストは、認証なしでアクセスできます。その他のリクエストでは、他のフィルターチェーンによって明示的に許可または保護されていない URL にアクセスするには、ユーザーの認証が必要です。 |
カスタム DSL
Spring Security で独自のカスタム DSL を提供できます。
Java
Kotlin
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
private boolean flag;
@Override
public void init(HttpSecurity http) throws Exception {
// any method that adds another configurer
// must be done in the init method
http.csrf().disable();
}
@Override
public void configure(HttpSecurity http) throws Exception {
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
// here we lookup from the ApplicationContext. You can also just create a new instance.
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();
}
}
class MyCustomDsl : AbstractHttpConfigurer<MyCustomDsl, HttpSecurity>() {
var flag: Boolean = false
override fun init(http: HttpSecurity) {
// any method that adds another configurer
// must be done in the init method
http.csrf().disable()
}
override fun configure(http: HttpSecurity) {
val context: ApplicationContext = http.getSharedObject(ApplicationContext::class.java)
// here we lookup from the ApplicationContext. You can also just create a new instance.
val myFilter: MyFilter = context.getBean(MyFilter::class.java)
myFilter.setFlag(flag)
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter::class.java)
}
companion object {
@JvmStatic
fun customDsl(): MyCustomDsl {
return MyCustomDsl()
}
}
}
これは、実際に |
その後、カスタム DSL を使用できます。
Java
Kotlin
@Configuration
@EnableWebSecurity
public class Config {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.with(MyCustomDsl.customDsl(), (dsl) -> dsl
.flag(true)
)
// ...
return http.build();
}
}
@Configuration
@EnableWebSecurity
class Config {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.with(MyCustomDsl.customDsl()) {
flag = true
}
// ...
return http.build()
}
}
コードは次の順序で呼び出されます。
Config.filterChain
メソッドのコードが呼び出されますMyCustomDsl.init
メソッドのコードが呼び出されますMyCustomDsl.configure
メソッドのコードが呼び出されます
必要に応じて、SpringFactories
を使用して、デフォルトで HttpSecurity
に MyCustomDsl
を追加させることができます。例: META-INF/spring.factories
という名前のクラスパスに、次の内容のリソースを作成できます。
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl
デフォルトを明示的に無効にすることもできます。
Java
Kotlin
@Configuration
@EnableWebSecurity
public class Config {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.with(MyCustomDsl.customDsl(), (dsl) -> dsl
.disable()
)
...;
return http.build();
}
}
@Configuration
@EnableWebSecurity
class Config {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.with(MyCustomDsl.customDsl()) {
disable()
}
// ...
return http.build()
}
}
構成済みオブジェクトの後処理
Spring Security の Java 構成は、構成するすべてのオブジェクトのすべてのプロパティを公開するわけではありません。これにより、大多数のユーザーの構成が簡素化されます。結局のところ、すべてのプロパティが公開されている場合、ユーザーは標準の Bean 構成を使用できます。
すべてのプロパティを直接公開しないのには十分な理由がありますが、ユーザーはさらに高度な構成オプションを必要とする場合があります。この課題に対処するために、Spring Security は ObjectPostProcessor
の概念を導入します。これは、Java 構成によって作成された Object
インスタンスの多くを変更または置換するために使用できます。例: FilterSecurityInterceptor
で filterSecurityPublishAuthorizationSuccess
プロパティを構成するには、次を使用できます。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
public <O extends FilterSecurityInterceptor> O postProcess(
O fsi) {
fsi.setPublishAuthorizationSuccess(true);
return fsi;
}
})
);
return http.build();
}