最新の安定バージョンについては、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 の下。

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[] { "/" };
  }
}

HttpServletRequest とメソッドセキュリティを照合することにより、常に認可規則を提供することをお勧めします。

HttpServletRequest での照合による認可ルールの提供は、コードパスの非常に早い段階で行われ、攻撃対象領域 [Wikipedia] を減らすのに役立つため、優れています。メソッドセキュリティにより、誰かが Web 認証ルールをバイパスした場合でも、アプリケーションは引き続き保護されます。これは多層防御 [Wikipedia] として知られているものです

次のようにマップされているコントローラーを考えます。

@RequestMapping("/admin")
public String admin() {

このコントローラーメソッドへのアクセスを管理ユーザーに制限したい場合、開発者は HttpServletRequest で次と照合することで認可ルールを提供できます。

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
	http
		.authorizeHttpRequests(authorize -> authorize
			.antMatchers("/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 を照合します。

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
	http
		.authorizeHttpRequests(authorize -> authorize
			.mvcMatchers("/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 には、次のコードを使用してアクセスできます。

@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 ...
}

Spring Security 3.2 以降、アノテーションを追加することで、引数をより直接解決できます。例:

import org.springframework.security.core.annotation.AuthenticationPrincipal;

// ...

@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {

	// .. find messages for this user and return them ...
}

場合によっては、何らかの方法でプリンシパルを変換する必要があります。例: CustomUser をファイナルにする必要がある場合、拡張できませんでした。この状況では、UserDetailsService は UserDetails を実装する Object を返し、CustomUser にアクセスするために getCustomUser という名前のメソッドを提供します。例: 次のようになります。

public class CustomUserUserDetails extends User {
		// ...
		public CustomUser getCustomUser() {
				return customUser;
		}
}

次に、Authentication.getPrincipal() をルートオブジェクトとして使用する SpEL 式を使用して、CustomUser にアクセスできます。

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 ...
}

SpEL 式で Bean を参照することもできます。例: JPA を使用してユーザーを管理しており、現在のユーザーのプロパティを変更して保存する場合は、以下を使用できます。

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);

	// ...
}

@AuthenticationPrincipal を独自のアノテーションのメタアノテーションにすることで、Spring Security への依存をさらに削除できます。以下に、@CurrentUser という名前のアノテーションでこれを行う方法を示します。

Spring Security への依存を削除するために、@CurrentUser を作成するのは消費アプリケーションであることを認識することが重要です。このステップは厳密には必要ありませんが、Spring Security への依存関係をより主要な場所に分離できます。
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal
public @interface CurrentUser {}

@CurrentUser が指定されたため、それを使用して、現在認証されているユーザーの CustomUser を解決するためのシグナルを送ることができます。また、Spring Security への依存関係を単一のファイルに分離しました。

@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) {

	// .. find messages for this user and return them ...
}

Spring MVC 非同期統合

Spring Web MVC 3.2 + は、非同期リクエスト処理に優れたサポートを提供します。追加構成なしで、Spring Security は SecurityContext を Thread に自動的にセットアップし、コントローラーから返される Callable を呼び出します。例: 次のメソッドは、Callable が作成されたときに使用可能であった SecurityContext で呼び出される Callable を自動的に持ちます。

@RequestMapping(method=RequestMethod.POST)
public Callable<String> processUpload(final MultipartFile file) {

return new Callable<String>() {
	public Object call() throws Exception {
	// ...
	return "someView";
	}
};
}
SecurityContext と Callable の関連付け

技術的に言えば、Spring Security は WebAsyncManager と統合されます。Callable の処理に使用される SecurityContext は、startCallableProcessing が呼び出された時点で SecurityContextHolder に存在する SecurityContext です。

コントローラーによって返される 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 ベースのアプリケーションに公開できます。

@RestController
public class CsrfController {

	@RequestMapping("/csrf")
	public CsrfToken csrf(CsrfToken token) {
		return token;
	}
}

CsrfToken を他のドメインから秘密にしておくことが重要です。つまり、クロスオリジン共有 (CORS) [Mozilla] を使用している場合、CsrfToken を外部ドメインに公開しないでください。