最新の安定バージョンについては、Spring Security 6.4.3 を使用してください! |
Spring MVC 統合
Spring Security は、Spring MVC とのオプションの統合を多数提供します。このセクションでは、統合についてさらに詳しく説明します。
@EnableWebMvcSecurity
Spring Security 4.0 以降、@EnableWebMvcSecurity は非推奨です。置き換えは、クラスパスに基づいて Spring MVC 機能の追加を決定する @EnableWebSecurity です。 |
Spring Security と Spring MVC の統合を有効にするには、@EnableWebSecurity
アノテーションを構成に追加します。
Spring Security は、Spring MVC の WebMvcConfigurer を使用した構成を提供します。つまり、WebMvcConfigurationSupport と直接統合するなど、より高度なオプションを使用している場合は、手動で Spring Security 構成を指定する必要があります。 |
MvcRequestMatcher
Spring Security は、Spring MVC が MvcRequestMatcher
を使用して URL を照合する方法との緊密な統合を提供します。これは、セキュリティルールがリクエストの処理に使用されるロジックと一致していることを確認できます。
MvcRequestMatcher
を使用するには、Spring Security 構成を DispatcherServlet
と同じ ApplicationContext
に配置する必要があります。これは、Spring Security の MvcRequestMatcher
が mvcHandlerMappingIntrospector
という名前の HandlerMappingIntrospector
Bean がマッチングの実行に使用される Spring MVC 構成によって登録されることを期待しているためです。
web.xml
の場合、これは設定を DispatcherServlet.xml
に配置する必要があることを意味します。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- All Spring Configuration (both MVC and Security) are in /WEB-INF/spring/ -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/*.xml</param-value>
</context-param>
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- Load from the ContextLoaderListener -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
DispatcherServlet
の ApplicationContext
に配置された WebSecurityConfiguration
の下。
Java
Kotlin
public class SecurityInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { RootConfiguration.class,
WebMvcConfiguration.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
class SecurityInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
override fun getRootConfigClasses(): Array<Class<*>>? {
return null
}
override fun getServletConfigClasses(): Array<Class<*>> {
return arrayOf(
RootConfiguration::class.java,
WebMvcConfiguration::class.java
)
}
override fun getServletMappings(): Array<String> {
return arrayOf("/")
}
}
|
次のようにマップされているコントローラーを考えます。
Java
Kotlin
@RequestMapping("/admin")
public String admin() {
@RequestMapping("/admin")
fun admin(): String {
このコントローラーメソッドへのアクセスを管理ユーザーに制限したい場合、開発者は HttpServletRequest
で次と照合することで認可ルールを提供できます。
Java
Kotlin
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.antMatchers("/admin").hasRole("ADMIN")
);
return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize(AntPathRequestMatcher("/admin"), hasRole("ADMIN"))
}
}
return http.build()
}
または XML
<http>
<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
</http>
どちらの構成でも、URL /admin
では、認証されたユーザーが管理者ユーザーである必要があります。ただし、Spring MVC の構成によっては、URL /admin.html
も admin()
メソッドにマップされます。さらに、Spring MVC 構成に応じて、URL /admin/
も admin()
メソッドにマッピングされます。
問題は、セキュリティルールが /admin
のみを保護していることです。Spring MVC のすべての順列にルールを追加することもできますが、これは非常に冗長で退屈です。
代わりに、Spring Security の MvcRequestMatcher
を活用できます。次の構成は、Spring MVC が一致する URL と同じ URL を保護するために、Spring MVC を使用して URL を照合します。
Java
Kotlin
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.mvcMatchers("/admin").hasRole("ADMIN")
);
// ...
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize("/admin", hasRole("ADMIN"))
}
}
// ...
}
または XML
<http request-matcher="mvc">
<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
</http>
@AuthenticationPrincipal
Spring Security は、Spring MVC 引数の現在の Authentication.getPrincipal()
を自動的に解決できる AuthenticationPrincipalArgumentResolver
を提供します。@EnableWebSecurity
を使用すると、これが自動的に Spring MVC 構成に追加されます。XML ベースの構成を使用する場合は、これを自分で追加する必要があります。例:
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
AuthenticationPrincipalArgumentResolver
が適切に構成されたら、Spring MVC レイヤーで Spring Security から完全に切り離すことができます。
UserDetails
を実装する Object
を返すカスタム UserDetailsService
と独自の CustomUser
Object
がある状況を考えてみます。現在認証されているユーザーの CustomUser
には、次のコードを使用してアクセスできます。
Java
Kotlin
@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser() {
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
CustomUser custom = (CustomUser) authentication == null ? null : authentication.getPrincipal();
// .. find messages for this user and return them ...
}
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(): ModelAndView {
val authentication: Authentication = SecurityContextHolder.getContext().authentication
val custom: CustomUser? = if (authentication as CustomUser == null) null else authentication.principal
// .. find messages for this user and return them ...
}
Spring Security 3.2 以降、アノテーションを追加することで、引数をより直接解決できます。例:
Java
Kotlin
import org.springframework.security.core.annotation.AuthenticationPrincipal;
// ...
@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {
// .. find messages for this user and return them ...
}
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(@AuthenticationPrincipal customUser: CustomUser?): ModelAndView {
// .. find messages for this user and return them ...
}
場合によっては、何らかの方法でプリンシパルを変換する必要があります。例: CustomUser
をファイナルにする必要がある場合、拡張できませんでした。この状況では、UserDetailsService
は UserDetails
を実装する Object
を返し、CustomUser
にアクセスするために getCustomUser
という名前のメソッドを提供します。例: 次のようになります。
Java
Kotlin
public class CustomUserUserDetails extends User {
// ...
public CustomUser getCustomUser() {
return customUser;
}
}
class CustomUserUserDetails(
username: String?,
password: String?,
authorities: MutableCollection<out GrantedAuthority>?
) : User(username, password, authorities) {
// ...
val customUser: CustomUser? = null
}
次に、Authentication.getPrincipal()
をルートオブジェクトとして使用する SpEL 式を使用して、CustomUser
にアクセスできます。
Java
Kotlin
import org.springframework.security.core.annotation.AuthenticationPrincipal;
// ...
@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal(expression = "customUser") CustomUser customUser) {
// .. find messages for this user and return them ...
}
import org.springframework.security.core.annotation.AuthenticationPrincipal
// ...
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(@AuthenticationPrincipal(expression = "customUser") customUser: CustomUser?): ModelAndView {
// .. find messages for this user and return them ...
}
SpEL 式で Bean を参照することもできます。例: JPA を使用してユーザーを管理しており、現在のユーザーのプロパティを変更して保存する場合は、以下を使用できます。
Java
Kotlin
import org.springframework.security.core.annotation.AuthenticationPrincipal;
// ...
@PutMapping("/users/self")
public ModelAndView updateName(@AuthenticationPrincipal(expression = "@jpaEntityManager.merge(#this)") CustomUser attachedCustomUser,
@RequestParam String firstName) {
// change the firstName on an attached instance which will be persisted to the database
attachedCustomUser.setFirstName(firstName);
// ...
}
import org.springframework.security.core.annotation.AuthenticationPrincipal
// ...
@PutMapping("/users/self")
open fun updateName(
@AuthenticationPrincipal(expression = "@jpaEntityManager.merge(#this)") attachedCustomUser: CustomUser,
@RequestParam firstName: String?
): ModelAndView {
// change the firstName on an attached instance which will be persisted to the database
attachedCustomUser.setFirstName(firstName)
// ...
}
@AuthenticationPrincipal
を独自のアノテーションのメタアノテーションにすることで、Spring Security への依存をさらに削除できます。以下に、@CurrentUser
という名前のアノテーションでこれを行う方法を示します。
Spring Security への依存を削除するために、@CurrentUser を作成するのは消費アプリケーションであることを認識することが重要です。このステップは厳密には必要ありませんが、Spring Security への依存関係をより主要な場所に分離できます。 |
Java
Kotlin
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal
public @interface CurrentUser {}
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@AuthenticationPrincipal
annotation class CurrentUser
@CurrentUser
が指定されたため、それを使用して、現在認証されているユーザーの CustomUser
を解決するためのシグナルを送ることができます。また、Spring Security への依存関係を単一のファイルに分離しました。
Java
Kotlin
@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) {
// .. find messages for this user and return them ...
}
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(@CurrentUser customUser: CustomUser?): ModelAndView {
// .. find messages for this user and return them ...
}
Spring MVC 非同期統合
Spring Web MVC 3.2 + は、非同期リクエスト処理に優れたサポートを提供します。追加構成なしで、Spring Security は SecurityContext
を Thread
に自動的にセットアップし、コントローラーから返される Callable
を呼び出します。例: 次のメソッドは、Callable
が作成されたときに使用可能であった SecurityContext
で呼び出される Callable
を自動的に持ちます。
Java
Kotlin
@RequestMapping(method=RequestMethod.POST)
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public Object call() throws Exception {
// ...
return "someView";
}
};
}
@RequestMapping(method = [RequestMethod.POST])
open fun processUpload(file: MultipartFile?): Callable<String> {
return Callable {
// ...
"someView"
}
}
SecurityContext と Callable の関連付け 技術的に言えば、Spring Security は |
コントローラーによって返される DeferredResult
との自動統合はありません。これは、DeferredResult
がユーザーによって処理され、自動的に統合する方法がないためです。ただし、並行性サポートを使用して、Spring Security との透過的な統合を提供できます。
Spring MVC と CSRF の統合
自動トークンインクルージョン
Spring Security は、Spring MVC フォームタグを使用するフォーム内に CSRF トークンを自動的に含めます。例: 次の JSP:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:form="http://www.springframework.org/tags/form" version="2.0">
<jsp:directive.page language="java" contentType="text/html" />
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<!-- ... -->
<c:url var="logoutUrl" value="/logout"/>
<form:form action="${logoutUrl}"
method="post">
<input type="submit"
value="Log out" />
<input type="hidden"
name="${_csrf.parameterName}"
value="${_csrf.token}"/>
</form:form>
<!-- ... -->
</html>
</jsp:root>
次のような HTML を出力します。
<!-- ... -->
<form action="/context/logout" method="post">
<input type="submit" value="Log out"/>
<input type="hidden" name="_csrf" value="f81d4fae-7dec-11d0-a765-00a0c91e6bf6"/>
</form>
<!-- ... -->
CsrfToken の解決
Spring Security は、Spring MVC 引数の現在の CsrfToken
を自動的に解決できる CsrfTokenArgumentResolver
を提供します。@EnableWebSecurity を使用すると、これが自動的に Spring MVC 構成に追加されます。XML ベースの構成を使用する場合は、これを自分で追加する必要があります。
CsrfTokenArgumentResolver
が適切に構成されたら、CsrfToken
を静的な HTML ベースのアプリケーションに公開できます。
Java
Kotlin
@RestController
public class CsrfController {
@RequestMapping("/csrf")
public CsrfToken csrf(CsrfToken token) {
return token;
}
}
@RestController
class CsrfController {
@RequestMapping("/csrf")
fun csrf(token: CsrfToken): CsrfToken {
return token
}
}
CsrfToken
を他のドメインから秘密にしておくことが重要です。つまり、クロスオリジン共有 (CORS) [Mozilla] を使用している場合、CsrfToken
を外部ドメインに公開しないでください。