15. 統合

15.1 サーブレット API の統合

このセクションでは、 Spring Security がサーブレット API とどのように統合されるかについて説明します。 servletapi-xml: GitHub (英語) サンプルアプリケーションは、これらの各メソッドの使用方法を示しています。

15.1.1 Servlet 2.5+ 統合

HttpServletRequest.getRemoteUser()

HttpServletRequest.getRemoteUser(): Oracle (英語) は、通常は現在のユーザー名である  SecurityContextHolder.getContext().getAuthentication().getName() の結果を返します。これは、アプリケーションで現在のユーザー名を表示する場合に役立ちます。さらに、これが null であるかどうかのチェックを使用して、ユーザーが認証済みか匿名かを示すことができます。ユーザーが認証されているかどうかを知ることは、特定の UI 要素を表示するかどうかを判断できます(つまり、ユーザーが認証された場合にのみログアウトリンクを表示する必要があります)。

HttpServletRequest.getUserPrincipal()

HttpServletRequest.getUserPrincipal(): Oracle (英語) は  SecurityContextHolder.getContext().getAuthentication() の結果を返します。これは、ユーザー名とパスワードベースの認証を使用する場合、通常  UsernamePasswordAuthenticationToken のインスタンスである  Authentication であることを意味します。これは、ユーザーに関する追加情報が必要な場合に役立ちます。例: ユーザーの姓名を含むカスタム  UserDetails を返すカスタム  UserDetailsService を作成した可能性があります。次の方法でこの情報を取得できます。

Authentication auth = httpServletRequest.getUserPrincipal();
// assume integrated custom UserDetails called MyCustomUserDetails
// by default, typically instance of UserDetails
MyCustomUserDetails userDetails = (MyCustomUserDetails) auth.getPrincipal();
String firstName = userDetails.getFirstName();
String lastName = userDetails.getLastName();
[Note] メモ

通常、アプリケーション全体でこれほど多くのロジックを実行するのは悪い習慣です。代わりに、Spring Security とサーブレット API のカップリングを減らすために一元化する必要があります。

HttpServletRequest.isUserInRole(String)

HttpServletRequest.isUserInRole(String): Oracle (英語) は、 SecurityContextHolder.getContext().getAuthentication().getAuthorities() に、 isUserInRole(String) に渡されたロールを持つ  GrantedAuthority が含まれているかどうかを判別します。通常、このメソッドは自動的に追加されるため、ユーザーはこのメソッドに「ROLE_」プレフィックスを渡さないでください。例: 現在のユーザーが権限 "ROLE_ADMIN" を持っているかどうかを確認したい場合は、次を使用できます。

boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");

これは、特定の UI コンポーネントを表示する必要があるかどうかを判断できます。例: 現在のユーザーが管理者である場合にのみ、管理者リンクを表示できます。

15.1.2 Servlet 3+ 統合

次のセクションでは、Spring Security が統合される Servlet 3 メソッドについて説明します。

HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse)

HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse): Oracle (英語) メソッドを使用して、ユーザーが認証されていることを確認できます。認証されていない場合、構成された AuthenticationEntryPoint を使用して、ユーザーに認証をリクエストします(つまり、ログインページにリダイレクトします)。

HttpServletRequest.login(String,String)

HttpServletRequest.login(String,String): Oracle (英語) メソッドを使用して、現在の  AuthenticationManager でユーザーを認証できます。例: 以下は、ユーザー名「user」とパスワード「password」で認証を試みます。

try {
httpServletRequest.login("user","password");
} catch(ServletException e) {
// fail to authenticate
}
[Note] メモ

Spring Security に失敗した認証試行を処理させたい場合は、ServletException をキャッチする必要はありません。

HttpServletRequest.logout()

HttpServletRequest.logout(): Oracle (英語) メソッドを使用して、現在のユーザーをログアウトできます。

通常、これは、SecurityContextHolder がクリアされ、HttpSession が無効化され、「自分を記憶」認証がクリーンアップされることを意味します。ただし、構成された LogoutHandler 実装は、Spring Security 構成によって異なります。HttpServletRequest.logout() が呼び出された後も、レスポンスの書き込みを担当していることに注意することが重要です。通常、これにはようこそページへのリダイレクトが含まれます。

AsyncContext.start(Runnable)

資格情報を新しいスレッドに確実に伝達する AsynchContext.start(Runnable): Oracle (英語) メソッド。Spring Security の同時実行サポートを使用して、Spring Security は AsyncContext.start(Runnable)をオーバーライドして、Runnable の処理時に現在の SecurityContext が使用されるようにします。例: 以下は、現在のユーザーの認証を出力します:

final AsyncContext async = httpServletRequest.startAsync();
async.start(new Runnable() {
    public void run() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        try {
            final HttpServletResponse asyncResponse = (HttpServletResponse) async.getResponse();
            asyncResponse.setStatus(HttpServletResponse.SC_OK);
            asyncResponse.getWriter().write(String.valueOf(authentication));
            async.complete();
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }
});

非同期サーブレットのサポート

Java ベースの構成を使用している場合、準備は完了です。XML 構成を使用している場合、いくつかの更新が必要です。最初のステップは、以下に示すように、少なくとも 3.0 スキーマを使用するように web.xml を更新したことを確認することです。

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">

</web-app>

次に、springSecurityFilterChain が非同期リクエストを処理するために設定されていることを確認する必要があります。

<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
    org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ASYNC</dispatcher>
</filter-mapping>

以上です! これで、Spring Security は、SecurityContext が非同期リクエストでも伝搬されるようにします。

それはどのように機能するのでしょうか? 本当に興味がない場合は、このセクションの残りの部分をスキップして構いません。それ以外の場合は参照してください。これのほとんどはサーブレット仕様に組み込まれていますが、Spring Security が非同期リクエストを適切に処理できるようにするために微調整が少しあります。Spring Security 3.2 より前は、HttpServletResponse がコミットされるとすぐに、SecurityContextHolder からの SecurityContext が自動的に保存されました。これにより、非同期環境で問題が発生する可能性があります。例: 以下を検討してください。

httpServletRequest.startAsync();
new Thread("AsyncThread") {
    @Override
    public void run() {
        try {
            // Do work
            TimeUnit.SECONDS.sleep(1);

            // Write to and commit the httpServletResponse
            httpServletResponse.getOutputStream().flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}.start();

課題は、このスレッドが Spring Security に認識されていないため、SecurityContext がこのスレッドに伝播されないことです。これは、HttpServletResponse をコミットするときに SecuriytContext がないことを意味します。Spring Security が HttpServletResponse のコミット時に SecurityContext を自動的に保存すると、ログインしているユーザーが失われます。

バージョン 3.2 以降、Spring Security は HttpServletRequest.startAsync() が呼び出された直後に HttpServletResponse をコミットする際に SecurityContext を自動的に保存しなくなるほどスマートです。

15.1.3 Servlet 3.1+ 統合

次のセクションでは、Spring Security が統合される Servlet 3.1 メソッドについて説明します。

HttpServletRequest#changeSessionId()

HttpServletRequest.changeSessionId() (Javadoc) は、Servlet 3.1 以降でのセッション固定攻撃から保護するためのデフォルトの方法です。

15.2 Spring Data 統合

Spring Security は、Spring Data 統合を提供し、クエリ内で現在のユーザーを参照できるようにします。結果のフィルタリングはスケーリングされないため、ページ化された結果をサポートするためにクエリにユーザーを含める必要があるだけでなく、必要です。

15.2.1 Spring Data および Spring Security の構成

このサポートを使用するには、 org.springframework.security:spring-security-data 依存関係を追加し、型  SecurityEvaluationContextExtension の Bean を提供します。Java 構成では、これは次のようになります。

@Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
    return new SecurityEvaluationContextExtension();
}

XML 構成では、これは次のようになります。

<bean class="org.springframework.security.data.repository.query.SecurityEvaluationContextExtension"/>

15.2.2 @Query 内のセキュリティ表現

これで、Spring Security をクエリ内で使用できます。例:

@Repository
public interface MessageRepository extends PagingAndSortingRepository<Message,Long> {
    @Query("select m from Message m where m.to.id = ?#{ principal?.id }")
    Page<Message> findInbox(Pageable pageable);
}

これにより、 Authentication.getPrincipal().getId() が  Message の受信者と等しいかどうかが確認されます。この例では、プリンシパルが id プロパティを持つオブジェクトにカスタマイズされていることを前提としていることに注意してください。 SecurityEvaluationContextExtension Bean を公開することにより、すべての一般的なセキュリティ表現がクエリ内で利用可能になります。

15.3 並行性サポート

ほとんどの環境では、セキュリティは  Thread ごとに保存されます。これは、新しい  Thread で作業が行われると、 SecurityContext が失われることを意味します。Spring Security は、ユーザーがこれをはるかに簡単にするためのインフラストラクチャを提供します。Spring Security は、マルチスレッド環境で Spring Security を操作するための低レベルの抽象化を提供します。実際、これが Spring Security が「AsyncContext.start(Runnable)」およびセクション 15.6.4: “Spring MVC 非同期統合 ” との統合に基づいて構築しているものです。

15.3.1 DelegatingSecurityContextRunnable

Spring Security の同時実行性サポートの中で最も基本的な構成要素の 1 つは  DelegatingSecurityContextRunnable です。デリゲート用に指定された  SecurityContext で  SecurityContextHolder を初期化するために、デリゲート  Runnable をラップします。その後、 SecurityContextHolder をクリアすることを保証する Runnable デリゲートを呼び出します。 DelegatingSecurityContextRunnable は次のようになります。

public void run() {
try {
    SecurityContextHolder.setContext(securityContext);
    delegate.run();
} finally {
    SecurityContextHolder.clearContext();
}
}

非常にシンプルですが、SecurityContext をあるスレッドから別のスレッドにシームレスに転送します。ほとんどの場合、SecurityContextHolder はスレッドごとに動作するため、これは重要です。例: Spring Security の「<global-method-security>」サポート と呼ばれるセクションを使用して、サービスの 1 つを保護した可能性があります。これで、現在の  Thread の  SecurityContext を、保護されたサービスを呼び出す  Thread に簡単に転送できます。これを行う方法の例を以下に示します。

Runnable originalRunnable = new Runnable() {
public void run() {
    // invoke secured service
}
};

SecurityContext context = SecurityContextHolder.getContext();
DelegatingSecurityContextRunnable wrappedRunnable =
    new DelegatingSecurityContextRunnable(originalRunnable, context);

new Thread(wrappedRunnable).start();

上記のコードは次の手順を実行します。

  • 保護されたサービスを呼び出す  Runnable を作成します。Spring Security を認識していないことに注意してください
  • 使用したい  SecurityContext を  SecurityContextHolder から取得し、 DelegatingSecurityContextRunnable を初期化する
  • DelegatingSecurityContextRunnable を使用してスレッドを作成する
  • 作成したスレッドを開始する

SecurityContextHolder から  SecurityContext で  DelegatingSecurityContextRunnable を作成することは非常に一般的であるため、ショートカットコンストラクターがあります。次のコードは上記のコードと同じです。

Runnable originalRunnable = new Runnable() {
public void run() {
    // invoke secured service
}
};

DelegatingSecurityContextRunnable wrappedRunnable =
    new DelegatingSecurityContextRunnable(originalRunnable);

new Thread(wrappedRunnable).start();

このコードは簡単に使えますが、Spring Security を使っているという知識が必要です。次のセクションでは、Spring Security を使っているという事実を隠すために  DelegatingSecurityContextExecutor をどのように利用できるかを見ていきます。

15.3.2 DelegatingSecurityContextExecutor

前のセクションで、 DelegatingSecurityContextRunnable を使用するのは簡単であることがわかりましたが、Spring Security を使用するためには Spring Security に注意する必要があるため、理想的ではありませんでした。 DelegatingSecurityContextExecutor を使用して、Spring Security を使用しているという知識からコードを保護する方法を見てみましょう。

DelegatingSecurityContextExecutor の設計は、代理  Runnable の代わりに代理  Executor を受け入れることを除いて、 DelegatingSecurityContextRunnable の設計と非常に似ています。以下に使用方法の例を示します。

SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
    new UsernamePasswordAuthenticationToken("user","doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER"));
context.setAuthentication(authentication);

SimpleAsyncTaskExecutor delegateExecutor =
    new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
    new DelegatingSecurityContextExecutor(delegateExecutor, context);

Runnable originalRunnable = new Runnable() {
public void run() {
    // invoke secured service
}
};

executor.execute(originalRunnable);

コードは次の手順を実行します。

  • DelegatingSecurityContextExecutor に使用する  SecurityContext を作成します。この例では、 SecurityContext を手動で作成するだけです。ただし、 SecurityContext をどこでどのように取得するかは重要ではありません(つまり、必要に応じて  SecurityContextHolder から取得できます)。
  • 送信された  Runnable の実行を担当する delegateExecutor を作成する
  • 最後に、 DelegatingSecurityContextRunnable で execute メソッドに渡される Runnable をラップする  DelegatingSecurityContextExecutor を作成します。次に、ラップされた Runnable を delegateExecutor に渡します。この例では、 DelegatingSecurityContextExecutor に送信されるすべての Runnable に同じ  SecurityContext が使用されます。これは、昇格した特権を持つユーザーが実行する必要があるバックグラウンドタスクを実行している場合に便利です。
  • この時点で、「これにより、Spring Security の知識のコードがどのように保護されますか? 」独自のコードで  SecurityContext と  DelegatingSecurityContextExecutor を作成する代わりに、すでに初期化された  DelegatingSecurityContextExecutor のインスタンスを注入できます。
@Autowired
private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor

public void submitRunnable() {
Runnable originalRunnable = new Runnable() {
    public void run() {
    // invoke secured service
    }
};
executor.execute(originalRunnable);
}

これで、コードは  SecurityContext が  Thread に伝搬されていることを認識せず、次に  originalRunnable が実行され、次に  SecurityContextHolder がクリアされます。この例では、同じユーザーが各スレッドの実行に使用されています。 originalRunnable を処理するために  executor.execute(Runnable) (つまり、現在ログインしているユーザー)を呼び出したときに  SecurityContextHolder のユーザーを使用したい場合はどうなるでしょうか? これを行うには、 DelegatingSecurityContextExecutor コンストラクターから  SecurityContext 引数を削除します。例:

SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
    new DelegatingSecurityContextExecutor(delegateExecutor);

executor.execute(Runnable) が実行されると、 SecurityContext が最初に  SecurityContextHolder によって取得され、次に  SecurityContext を使用して  DelegatingSecurityContextRunnable が作成されます。これは、 executor.execute(Runnable) コードの呼び出しに使用されたのと同じユーザーで  Runnable を実行していることを意味します。

15.3.3 Spring Security 並行性クラス

Java コンカレント API と Spring タスク抽象化の両方との追加統合については、Javadoc を参照してください。前のコードを理解すると、それらは非常に自明です。

  • DelegatingSecurityContextCallable
  • DelegatingSecurityContextExecutor
  • DelegatingSecurityContextExecutorService
  • DelegatingSecurityContextRunnable
  • DelegatingSecurityContextScheduledExecutorService
  • DelegatingSecurityContextSchedulingTaskExecutor
  • DelegatingSecurityContextAsyncTaskExecutor
  • DelegatingSecurityContextTaskExecutor
  • DelegatingSecurityContextTaskScheduler

15.4 Jackson サポート

Spring Security は、Spring Security 関連クラスを永続化するための Jackson サポートを追加しました。これにより、分散セッション(つまり、セッションレプリケーション、Spring Session など)で作業する際に Spring Security 関連クラスを直列化するパフォーマンスを向上させることができます。

使用するには、 SecurityJackson2Modules.getModules(ClassLoader) を Jackson モジュール (英語) として登録します。

ObjectMapper mapper = new ObjectMapper();
ClassLoader loader = getClass().getClassLoader();
List<Module> modules = SecurityJackson2Modules.getModules(loader);
mapper.registerModules(modules);

// ... use ObjectMapper as normally ...
SecurityContext context = new SecurityContextImpl();
// ...
String json = mapper.writeValueAsString(context);

15.5 ローカライゼーション

Spring Security は、エンドユーザーに表示される可能性が高い例外メッセージのローカライズをサポートしています。アプリケーションが英語を話すユーザー向けに設計されている場合、デフォルトではすべてのセキュリティメッセージは英語であるため、何もする必要はありません。他のロケールをサポートする必要がある場合、知る必要があるすべてがこのセクションに含まれています。

認証の失敗や拒否されたアクセスに関連するメッセージ(認可の失敗)など、すべての例外メッセージをローカライズできます。開発者またはシステムデプロイヤに焦点を当てた例外とログメッセージ(不適切な属性、インターフェース契約違反、不適切なコンストラクターの使用、起動時間検証、デバッグレベルのログ記録を含む)はローカライズされず、代わりに Spring Security のコード内に英語でハードコードされています

spring-security-core-xx.jar に同梱されている  org.springframework.security パッケージには、 messages.properties ファイルと、いくつかの共通言語のローカライズバージョンが含まれています。Spring Security クラスは Spring の  MessageSourceAware インターフェースを実装し、アプリケーションコンテキストの起動時にメッセージリゾルバーが依存性注入されることを想定しているため、これは  ApplicationContext によって参照される必要があります。通常、アプリケーションコンテキスト内で Bean を登録して、メッセージを参照するだけです。以下に例を示します。

<bean id="messageSource"
    class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:org/springframework/security/messages"/>
</bean>

messages.properties は、標準のリソースバンドルに従って名前が付けられ、Spring Security メッセージでサポートされるデフォルト言語を表します。このデフォルトファイルは英語です。

messages.properties ファイルをカスタマイズする場合、または他の言語をサポートする場合は、ファイルをコピーし、それに応じて名前を変更し、上記の Bean 定義内に登録する必要があります。このファイル内には多数のメッセージキーがないため、ローカライズは主要なイニシアチブと見なされるべきではありません。このファイルのローカライズを実行する場合は、JIRA タスクをログに記録し、適切な名前のローカライズされたバージョンの  messages.properties を添付して、コミュニティと作業を共有することを検討してください。

Spring Security は、実際に適切なメッセージを検索するために、Spring のローカライゼーションサポートに依存しています。これが機能するためには、受信リクエストのロケールが Spring の  org.springframework.context.i18n.LocaleContextHolder に保存されていることを確認する必要があります。 Spring MVC の  DispatcherServlet はアプリケーションに対してこれを自動的に行いますが、Spring Security のフィルターはこの前に呼び出されるため、 LocaleContextHolder はフィルターが呼び出される前に正しい  Locale を含むように設定する必要があります。これを自分でフィルターで行うことができます( web.xml で Spring Security フィルターの前に来る必要があります)か、Spring の  RequestContextFilter を使用できます。Spring でのローカライズの使用の詳細については、 Spring Framework のドキュメントを参照してください。

「contacts」サンプルアプリケーションは、ローカライズされたメッセージを使用するように設定されています。

15.6 Spring MVC 統合

Spring Security は、Spring MVC とのオプションの統合をいくつか提供します。このセクションでは、統合についてさらに詳しく説明します。

15.6.1 @EnableWebMvcSecurity

[Note] メモ

Spring Security 4.0 以降、 @EnableWebMvcSecurity は非推奨になりました。代替は  @EnableWebSecurity であり、クラスパスに基づいて Spring MVC 機能の追加を決定します。

Spring Security と Spring MVC の統合を有効にするには、 @EnableWebSecurity アノテーションを構成に追加します。

[Note] メモ

Spring Security は、Spring MVC の WebMvcConfigurer を使用した構成を提供します。つまり、 WebMvcConfigurationSupport と直接統合するなど、より高度なオプションを使用している場合は、Spring Security 構成を手動で提供する必要があります。

15.6.2 MvcRequestMatcher

Spring Security は、 MvcRequestMatcher を使用した URL で Spring MVC がどのように一致するかを深く統合します。これは、セキュリティルールがリクエストの処理に使用されるロジックと一致することを確認できます。

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

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

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

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

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

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

protected configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests(authorizeRequests ->
            authorizeRequests
                .antMatchers("/admin").hasRole("ADMIN")
        );
}

または 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 で照合することにより、Spring MVC が照合する URL と同じ URL を保護します。

protected configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests(authorizeRequests ->
            authorizeRequests
                .mvcMatchers("/admin").hasRole("ADMIN")
        );
}

または XML

<http request-matcher="mvc">
    <intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
</http>

15.6.3 @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 messags 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 という名前のアノテーションでこれを行う方法を示します。

[Note] メモ

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

15.6.4 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";
    }
};
}
[Note] SecurityContext と Callable の関連付け

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

コントローラーによって返される  DeferredResult との自動統合はありません。これは、 DeferredResult がユーザーによって処理され、自動的に統合する方法がないためです。ただし、並行性サポートを使用して、Spring Security との透過的な統合を提供できます。

15.6.5 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) を使用している場合、 CsrfToken を外部ドメインに公開しないでください。

15.7 WebSocket セキュリティ

Spring Security 4 は、Spring の WebSocket サポートを保護するためのサポートを追加しました。このセクションでは、Spring Security の WebSocket サポートの使用方法について説明します。

[Note] メモ

https://github.com/spring-projects/spring-session/tree/master/samples/boot/websocket (英語) で WebSocket セキュリティの完全な実用サンプルを見つけることができます。

15.7.1 WebSocket の設定

Spring Security 4.0 は、Spring メッセージング抽象化による WebSockets の認可サポートを導入しました。Java 構成を使用して認証を設定するには、 AbstractSecurityWebSocketMessageBrokerConfigurer を継承して  MessageSecurityMetadataSourceRegistry を設定するだけです。例:

@Configuration
public class WebSocketSecurityConfig
      extends AbstractSecurityWebSocketMessageBrokerConfigurer { 1 2

    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
        messages
                .simpDestMatchers("/user/**").authenticated() 3
    }
}

これにより、次のことが保証されます。

1

受信 CONNECT メッセージには、同一生成元ポリシーを実施するための有効な CSRF トークンが必要です

2

SecurityContextHolder には、受信リクエストの simpUser ヘッダー属性内のユーザーが入力されます。

3

メッセージには適切な認可が必要です。具体的には、「/user/」で始まる受信メッセージには ROLE_USER が必要です。認可の詳細については、セクション 15.7.3: “WebSocket 認証 ” を参照してください。

Spring Security は、WebSockets を保護するための XML 名前空間サポートも提供します。比較可能な XML ベースの構成は次のようになります。

<websocket-message-broker> 1 2
    3
    <intercept-message pattern="/user/**" access="hasRole('USER')" />
</websocket-message-broker>

これにより、次のことが保証されます。

1

受信 CONNECT メッセージには、同一生成元ポリシーを実施するための有効な CSRF トークンが必要です

2

SecurityContextHolder には、受信リクエストの simpUser ヘッダー属性内のユーザーが入力されます。

3

メッセージには適切な認可が必要です。具体的には、「/user/」で始まる受信メッセージには ROLE_USER が必要です。認可の詳細については、セクション 15.7.3: “WebSocket 認証 ” を参照してください。

15.7.2 WebSocket 認証

WebSockets は、WebSocket 接続が確立されたときに HTTP リクエストで見つかった同じ認証情報を再利用します。これは、 HttpServletRequest 上の  Principal が WebSockets に引き渡されることを意味します。Spring Security を使用している場合、 HttpServletRequest の  Principal は自動的にオーバーライドされます。

より具体的には、ユーザーが WebSocket アプリケーションに対して認証されたことを確認するには、HTTP ベースの Web アプリケーションを認証するように Spring Security をセットアップすることだけが必要です。

15.7.3 WebSocket 認証

Spring Security 4.0 は、Spring メッセージング抽象化による WebSockets の認可サポートを導入しました。Java 構成を使用して認証を設定するには、 AbstractSecurityWebSocketMessageBrokerConfigurer を継承して  MessageSecurityMetadataSourceRegistry を設定するだけです。例:

@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

    @Override
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
        messages
                .nullDestMatcher().authenticated() 1
                .simpSubscribeDestMatchers("/user/queue/errors").permitAll() 2
                .simpDestMatchers("/app/**").hasRole("USER") 3
                .simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") 4
                .simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll() 5
                .anyMessage().denyAll(); 6

    }
}

これにより、次のことが保証されます。

1

宛先のないメッセージ(つまり、メッセージタイプが MESSAGE または SUBSCRIBE 以外のもの)では、ユーザーの認証が必要になります

2

誰でも /user/queue/errors にサブスクライブできます

3

「/app/」で始まる宛先を持つすべてのメッセージには、ロール ROLE_USER が必要です。

4

SUBSCRIBE タイプの「/user/」または「/topic/friends/」で始まるメッセージには、ROLE_USER が必要です

5

タイプ MESSAGE または SUBSCRIBE の他のメッセージは拒否されます。6 のため、この手順は必要ありませんが、特定のメッセージタイプでどのように一致するかを示しています。

6

その他のメッセージは拒否されます。これは、メッセージを見逃さないようにするための良いアイデアです。

Spring Security は、WebSockets を保護するための XML 名前空間サポートも提供します。比較可能な XML ベースの構成は次のようになります。

<websocket-message-broker>
    1
    <intercept-message type="CONNECT" access="permitAll" />
    <intercept-message type="UNSUBSCRIBE" access="permitAll" />
    <intercept-message type="DISCONNECT" access="permitAll" />

    <intercept-message pattern="/user/queue/errors" type="SUBSCRIBE" access="permitAll" /> 2
    <intercept-message pattern="/app/**" access="hasRole('USER')" />      3

    4
    <intercept-message pattern="/user/**" access="hasRole('USER')" />
    <intercept-message pattern="/topic/friends/*" access="hasRole('USER')" />

    5
    <intercept-message type="MESSAGE" access="denyAll" />
    <intercept-message type="SUBSCRIBE" access="denyAll" />

    <intercept-message pattern="/**" access="denyAll" /> 6
</websocket-message-broker>

これにより、次のことが保証されます。

1

タイプ CONNECT、UNSUBSCRIBE、または DISCONNECT のメッセージでは、ユーザーの認証が必要です。

2

誰でも /user/queue/errors にサブスクライブできます

3

「/app/」で始まる宛先を持つすべてのメッセージには、ロール ROLE_USER が必要です。

4

SUBSCRIBE タイプの「/user/」または「/topic/friends/」で始まるメッセージには、ROLE_USER が必要です

5

タイプ MESSAGE または SUBSCRIBE の他のメッセージは拒否されます。6 のため、この手順は必要ありませんが、特定のメッセージタイプでどのように一致するかを示しています。

6

宛先を持つ他のメッセージは拒否されます。これは、メッセージを見逃さないようにするための良いアイデアです。

WebSocket 認可に関する注意

アプリケーションを適切に保護するには、Spring の WebSocket サポートを理解することが重要です。

メッセージタイプの WebSocket 認可

メッセージの SUBSCRIBE タイプと MESSAGE タイプの違いと、Spring 内での動作方法を理解することが重要です。

チャットアプリケーションを検討してください。

  • システムは、「/topic/system/notifications」の宛先を介してすべてのユーザーに通知 MESSAGE を送信できます。
  • クライアントは、SUBSCRIBE によって「/topic/system/notifications」への通知を受信できます。

クライアントが「/topic/system/notifications」にサブスクライブできるようにしたいのですが、その宛先に MESSAGE を送信できるようにしたくありません。「/topic/system/notifications」への MESSAGE の送信を許可した場合、クライアントはそのエンドポイントにメッセージを直接送信し、システムになりすますことができます。

一般的に、アプリケーションでは、ブローカープレフィックス(「/topic/」または「/queue/」)で始まる宛先に送信される MESSAGE を拒否するのが一般的です。

宛先での WebSocket 認可

宛先がどのように変換されるかを理解することも重要です。

チャットアプリケーションを検討してください。

  • ユーザーは、「/app/chat」の宛先にメッセージを送信することにより、特定のユーザーにメッセージを送信できます。
  • アプリケーションはメッセージを見て、"from" 属性が現在のユーザーとして指定されていることを確認します(クライアントを信頼できません)。
  • 次に、アプリケーションは  SimpMessageSendingOperations.convertAndSendToUser("toUser", "/queue/messages", message) を使用して受信者にメッセージを送信します。
  • メッセージは「/queue/user/messages-<sessionid>」の送信先になります

上記のアプリケーションでは、クライアントが「/queue/user/messages-<sessionid>」に変換される「/user/queue」をリッスンできるようにしたいと考えています。ただし、クライアントが「/queue/*」をリッスンできるようにしたくないため、クライアントはすべてのユーザーのメッセージを見ることができます。

一般に、ブローカープレフィックス(「/topic/」または「/queue/」)で始まるメッセージに送信された SUBSCRIBE をアプリケーションが拒否するのが一般的です。もちろん、例外を提供

送信メッセージ

Spring には、メッセージがシステムをどのように流れるかを説明する Flow のメッセージというタイトルのセクションが含まれています。Spring Security は  clientInboundChannel のみを保護することに注意することが重要です。Spring Security は  clientOutboundChannel を保護しようとしません。

これの最も重要な理由はパフォーマンスです。受信するすべてのメッセージには、通常、さらに多くのメッセージが送信されます。発信メッセージを保護する代わりに、エンドポイントへのサブスクリプションを保護することをお勧めします。

15.7.4 同一生成元ポリシーの強制

ブラウザーが WebSocket 接続に対して同一生成元ポリシー (英語) を強制しないことを強調することが重要です。これは非常に重要な考慮事項です。

なぜ同一生成元なのでしょうか?

次のシナリオを検討してください。ユーザーが bank.com にアクセスして、アカウントの認証を行います。同じユーザーがブラウザーで別のタブを開き、evil.com にアクセスします。Same Origin Policy は、evil.com が bank.com に対してデータを読み書きできないようにします。

WebSockets では、同一生成元ポリシーは適用されません。実際、bank.com が明示的に禁止しない限り、evil.com はユーザーに代わってデータの読み取りと書き込みを行うことができます。つまり、ユーザーが webSocket でできること(つまり、送金)はすべて、evil.com がそのユーザーに代わって行うことができます。

SockJS は WebSockets をエミュレートしようとするため、Same Origin Policy もバイパスします。これは、SockJS を使用する場合、開発者がアプリケーションを外部ドメインから明示的に保護する必要があることを意味します。

Spring WebSocket 許可されたオリジン

幸い、Spring 4.1.5 Spring の WebSocket および SockJS のサポートにより、現在のドメインへのアクセスが制限されてい ます。Spring Security が提供する追加の保護レイヤー追加多層防御 (英語) を。

CSRF を Stomp ヘッダーに追加する

デフォルトでは、Spring Security は、すべての CONNECT メッセージタイプで CSRF トークンを必要とします。これにより、CSRF トークンにアクセスできるサイトのみが接続できるようになります。 同じ起源のみが CSRF トークンにアクセスできるため、外部ドメインは接続を確立できません。

通常、CSRF トークンを HTTP ヘッダーまたは HTTP パラメーターに含める必要があります。ただし、SockJS はこれらのオプションを許可しません。代わりに、トークンを Stomp ヘッダーに含める必要があります

アプリケーションは 、 _csrf という名前のリクエスト属性にアクセスすること により、CSRF トークン取得できます。例: 以下は、JSP の  CsrfToken へのアクセスを許可します。

var headerName = "${_csrf.headerName}";
var token = "${_csrf.token}";

静的 HTML を使用している場合、REST エンドポイントで  CsrfToken を公開できます。例: 以下は、URL /csrf で  CsrfToken を公開します

@RestController
public class CsrfController {

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

JavaScript は、エンドポイントに対して REST 呼び出しを行い、レスポンスを使用して headerName とトークンを設定できます。

これで、トークンを Stomp クライアントに含めることができます。例:

...
var headers = {};
headers[headerName] = token;
stompClient.connect(headers, function(frame) {
  ...

}

WebSockets 内で CSRF を無効にする

他のドメインからサイトへのアクセスを許可する場合は、Spring Security の保護を無効にできます。例: Java 構成では、次を使用できます。

@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

    ...

    @Override
    protected boolean sameOriginDisabled() {
        return true;
    }
}

15.7.5 SockJS での作業

SockJS は、古いブラウザーをサポートするフォールバックトランスポートを提供します。フォールバックオプションを使用する場合、SockJS が Spring Security と連携できるように、いくつかのセキュリティ制約を緩和する必要があります。

SockJS とフレームオプション

SockJS は 、iframe を活用: GitHub (英語) するトランスポート: GitHub (英語) を使用する場合があります。デフォルトでは、Spring Security はクリックジャック攻撃を防ぐためにサイトのフレーム化を拒否します。SockJS フレームベースのトランスポートを機能させるには、Spring Security を構成して、同じオリジンがコンテンツをフレーム化できるようにする必要があります。

frame-options 要素を使用して X-Frame-Options をカスタマイズでき ます。例: 以下は、Spring Security に同じドメイン内の iframe を許可する「X-Frame-Options:SAMEORIGIN」を使用するよう指示します。

<http>
    <!-- ... -->

    <headers>
        <frame-options
          policy="SAMEORIGIN" />
    </headers>
</http>

同様に、フレームオプションをカスタマイズして、次を使用して Java 構成内で同じオリジンを使用できます。

@EnableWebSecurity
public class WebSecurityConfig extends
   WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      // ...
      .headers(headers ->
        headers
          .frameOptions(frameOptions ->
             frameOptions
               .sameOrigin()
          )
      );
  }
}

SockJS とリラックスした CSRF

SockJS は、HTTP ベースのトランスポートの CONNECT メッセージで POST を使用します。通常、CSRF トークンを HTTP ヘッダーまたは HTTP パラメーターに含める必要があります。ただし、SockJS はこれらのオプションを許可しません。代わりに、「CSRF を Stomp ヘッダーに追加する」で説明され ているように、トークンを Stomp ヘッダーに含める必要があります。

また、Web レイヤーで CSRF 保護を緩和する必要があることも意味します。具体的には、接続 URL の CSRF 保護を無効にします。すべての URL の CSRF 保護を無効にする必要はありません。そうしないと、サイトは CSRF 攻撃に対して脆弱になります。

CSRF RequestMatcher を提供することにより、これを簡単に実現できます。Java 構成により、これは非常に簡単になります。例: ストンプエンドポイントが「/chat」の場合、次の構成を使用して「/chat/」で始まる URL のみの CSRF 保護を無効にできます。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig
    extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf(csrf ->
                csrf
                    // ignore our stomp endpoints since they are protected using Stomp headers
                    .ignoringAntMatchers("/chat/**")
            )
            .headers(headers ->
                headers
                    // allow same origin to frame our site to support iframe SockJS
                    .frameOptions(frameOptions ->
                        frameOptions
                            .sameOrigin()
                    )
            )
            .authorizeRequests(authorizeRequests ->
                ...
            )
            ...

XML ベースの構成を使用している場合、csr [ メール保護 ] を使用できます。例:

<http ...>
    <csrf request-matcher-ref="csrfMatcher"/>

    <headers>
        <frame-options policy="SAMEORIGIN"/>
    </headers>

    ...
</http>

<b:bean id="csrfMatcher"
    class="AndRequestMatcher">
    <b:constructor-arg value="#{T(org.springframework.security.web.csrf.CsrfFilter).DEFAULT_CSRF_MATCHER}"/>
    <b:constructor-arg>
        <b:bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher">
          <b:bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
            <b:constructor-arg value="/chat/**"/>
          </b:bean>
        </b:bean>
    </b:constructor-arg>
</b:bean>

15.8 CORS

Spring Framework は 、CORS のファーストクラスサポートを提供します。プリフライトリクエストには Cookie(つまり  JSESSIONID)が含まれないため、CORS は Spring Security の前に処理する必要があります。リクエストに Cookie が含まれておらず、Spring Security が最初の場合、リクエストは(リクエストに Cookie がないため)ユーザーが認証されていないと判断し、拒否します。

CORS が最初に処理されるようにする最も簡単な方法は、 CorsFilter を使用することです。ユーザーは、以下を使用して  CorsConfigurationSource を提供することにより、 CorsFilter と Spring Security を統合できます。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // by default uses a Bean by the name of corsConfigurationSource
            .cors(withDefaults())
            ...
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

または XML

<http>
    <cors configuration-source-ref="corsSource"/>
    ...
</http>
<b:bean id="corsSource" class="org.springframework.web.cors.UrlBasedCorsConfigurationSource">
    ...
</b:bean>

Spring MVC の CORS サポートを使用している場合、 CorsConfigurationSource の指定を省略することができ、Spring Security は Spring MVC に提供された CORS 構成を活用します。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // if Spring MVC is on classpath and no CorsConfigurationSource is provided,
            // Spring Security will use CORS configuration provided to Spring MVC
            .cors(withDefaults())
            ...
    }
}

または XML

<http>
    <!-- Default to Spring MVC's CORS configuration -->
    <cors />
    ...
</http>

15.9 JSP タグライブラリ

Spring Security には独自の taglib があり、JSP でセキュリティ情報にアクセスし、セキュリティ制約を適用するための基本的なサポートを提供します。

15.9.1 Taglib の宣言

タグを使用するには、JSP でセキュリティ taglib を宣言する必要があります。

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

15.9.2 承認タグ

このタグは、その内容を評価する必要があるかどうかを決定するために使用されます。Spring Security 3.0 では、[10] の 2 つの方法で使用できます。最初のアプローチでは、タグの  access 属性で指定された Web セキュリティ式を使用し ます。式の評価は、アプリケーションコンテキストで定義された  SecurityExpressionHandler<FilterInvocation> に委譲されます(このサービスが利用可能であることを確認するには、 <http> 名前空間構成で Web 式を有効にする必要があります)。たとえば

<sec:authorize access="hasRole('supervisor')">

This content will only be visible to users who have the "supervisor" authority in their list of <tt>GrantedAuthority</tt>s.

</sec:authorize>

Spring Security の PermissionEvaluator と組み合わせて使用すると、タグを使用して権限を確認することもできます。例:

<sec:authorize access="hasPermission(#domain,'read') or hasPermission(#domain,'write')">

This content will only be visible to users who have read or write permission to the Object found as a request attribute named "domain".

</sec:authorize>

一般的な要件は、ユーザーが実際にクリックすることを許可されている場合にのみ特定のリンクを表示することです。何かが許可されるかどうかを事前に判断するにはどうすればよいですか? このタグは、特定の URL を属性として定義できる代替モードで動作することもできます。ユーザーがその URL の呼び出しを許可されている場合、タグの本文が評価され、そうでない場合はスキップされます。次のようなものを持っているかもしれません

<sec:authorize url="/admin">

This content will only be visible to users who are authorized to send requests to the "/admin" URL.

</sec:authorize>

このタグを使用するには、アプリケーションコンテキストに  WebInvocationPrivilegeEvaluator のインスタンスも存在する必要があります。名前空間を使用している場合、名前空間は自動的に登録されます。これは  DefaultWebInvocationPrivilegeEvaluator のインスタンスで、指定された URL のダミー Web リクエストを作成し、セキュリティインターセプターを呼び出して、リクエストが成功するか失敗するかを確認します。これにより、 <http> 名前空間構成内の  intercept-url 宣言を使用して定義したアクセス制御セットアップに委譲でき、JSP 内で情報(必要なロールなど)を複製する必要がなくなります。このアプローチは、より具体的な一致のために、HTTP メソッドを提供する  method 属性と組み合わせることもできます。

タグの評価のブール結果(アクセスの許可または拒否)は、 var 属性を変数名に設定することにより、ページコンテキストスコープ変数に格納でき、他の場所で条件を複製および再評価する必要がありません。ページ。

テストのためのタグ認証の無効化

認可されていないユーザーのためにページ内のリンクを非表示にしても、ユーザーが URL にアクセスすることを妨げません。たとえば、ブラウザーに直接入力するだけです。テストプロセスの一環として、バックエンドでリンクが本当に保護されていることを確認するために、非表示の領域を明らかにすることができます。システムプロパティ  spring.security.disableUISecurity を  true に設定すると、 authorize タグは引き続き実行されますが、その内容は非表示になりません。デフォルトでは、 <span class="securityHiddenUI">…​</span> タグでコンテンツを囲みます。これにより、異なる背景色などの特定の CSS スタイルで「隠された」コンテンツを表示できます。たとえば、このプロパティを有効にして「チュートリアル」サンプルアプリケーションを実行してみてください。

デフォルトの  span タグから周囲のテキストを変更する場合(または空の文字列を使用して完全に削除する場合)、プロパティ  spring.security.securedUIPrefix および  spring.security.securedUISuffix を設定することもできます。

15.9.3 認証タグ

このタグは、セキュリティコンテキストに保存されている現在の  Authentication オブジェクトへのアクセスを許可します。オブジェクトのプロパティを JSP で直接レンダリングします。そのため、たとえば、 Authentication の  principal プロパティが Spring Security の  UserDetails オブジェクトのインスタンスである場合、 <sec:authentication property="principal.username" /> を使用すると現在のユーザーの名前がレンダリングされます。

もちろん、この種のことのために JSP タグを使用する必要はなく、ビューの中にできるだけ少ないロジックを保持することを好む人もいます。MVC コントローラーの  Authentication オブジェクトにアクセスし( SecurityContextHolder.getContext().getAuthentication() を呼び出すことにより)、データをモデルに直接追加して、ビューでレンダリングすることができます。

15.9.4 accesscontrollist タグ

このタグは、Spring Security の ACL モジュールで使用する場合にのみ有効です。指定されたドメインオブジェクトに必要なアクセス許可のコンマ区切りリストをチェックします。現在のユーザーがこれらすべての権限を持っている場合、タグの本文が評価されます。そうでない場合はスキップされます。例は

[Caution] 注意

一般に、このタグは非推奨と見なされる必要があります。代わりにセクション 15.9.2: “ 承認タグ ” を使用してください。

<sec:accesscontrollist hasPermission="1,2" domainObject="${someObject}">

This will be shown if the user has all of the permissions represented by the values "1" or "2" on the given object.

</sec:accesscontrollist>

許可は、アプリケーションコンテキストで定義された  PermissionFactory に渡され、ACL  Permission インスタンスに変換されます。そのため、ファクトリでサポートされている任意の形式を使用できます。整数である必要はなく、 READ や  WRITE などの文字列でもかまいません。 PermissionFactory が見つからない場合、 DefaultPermissionFactory のインスタンスが使用されます。アプリケーションコンテキストからの  AclService は、提供されたオブジェクトの  Acl インスタンスをロードするために使用されます。 Acl は、すべてが許可されているかどうかを確認するために必要な権限で呼び出されます。

このタグは、 authorize タグと同様に、 var 属性もサポートします。

15.9.5 csrfInput タグ

CSRF 保護が有効になっている場合、このタグは、CSRF 保護トークンの正しい名前と値を持つ非表示フォームフィールドを挿入します。CSRF 保護が有効になっていない場合、このタグは何も出力しません。

通常、Spring Security は、使用する  <form:form> タグに対して CSRF フォームフィールドを自動的に挿入しますが、何らかの理由で  <form:form> を使用できない場合、 csrfInput は便利な代替です。

このタグは、通常は他の入力フィールドを配置する HTML  <form></form> ブロック内に配置する必要があります。このタグを Spring  <form:form></form:form> ブロック内に配置しないでください。Spring Security は Spring フォームを自動的に処理します。

<form method="post" action="/do/something">
    <sec:csrfInput />
    Name:<br />
    <input type="text" name="name" />
    ...
</form>

15.9.6 csrfMetaTags タグ

CSRF 保護が有効になっている場合、このタグは、CSRF 保護トークンフォームフィールドとヘッダー名、および CSRF 保護トークン値を含むメタタグを挿入します。これらのメタタグは、アプリケーションの JavaScript 内で CSRF 保護を採用できます。

csrfMetaTags は、通常は他のメタタグを配置する HTML  <head></head> ブロック内に配置する必要があります。このタグを使用すると、JavaScript を使用してフォームフィールド名、ヘッダー名、トークン値に簡単にアクセスできます。この例では、タスクを簡単にするために JQuery が使用されています。

<!DOCTYPE html>
<html>
    <head>
        <title>CSRF Protected JavaScript Page</title>
        <meta name="description" content="This is the description for this page" />
        <sec:csrfMetaTags />
        <script type="text/javascript" language="javascript">

            var csrfParameter = $("meta[name='_csrf_parameter']").attr("content");
            var csrfHeader = $("meta[name='_csrf_header']").attr("content");
            var csrfToken = $("meta[name='_csrf']").attr("content");

            // using XMLHttpRequest directly to send an x-www-form-urlencoded request
            var ajax = new XMLHttpRequest();
            ajax.open("POST", "https://www.example.org/do/something", true);
            ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded data");
            ajax.send(csrfParameter + "=" + csrfToken + "&name=John&...");

            // using XMLHttpRequest directly to send a non-x-www-form-urlencoded request
            var ajax = new XMLHttpRequest();
            ajax.open("POST", "https://www.example.org/do/something", true);
            ajax.setRequestHeader(csrfHeader, csrfToken);
            ajax.send("...");

            // using JQuery to send an x-www-form-urlencoded request
            var data = {};
            data[csrfParameter] = csrfToken;
            data["name"] = "John";
            ...
            $.ajax({
                url: "https://www.example.org/do/something",
                type: "POST",
                data: data,
                ...
            });

            // using JQuery to send a non-x-www-form-urlencoded request
            var headers = {};
            headers[csrfHeader] = csrfToken;
            $.ajax({
                url: "https://www.example.org/do/something",
                type: "POST",
                headers: headers,
                ...
            });

        <script>
    </head>
    <body>
        ...
    </body>
</html>

CSRF 保護が有効になっていない場合、 csrfMetaTags は何も出力しません。



[10] Spring Security 2.0 のレガシーオプションもサポートされていますが、お勧めしません。

現行バージョンへ切り替える