1. 概要

SpringBootアプリケーションのさまざまなパス内に複数のセキュリティフィルターを適用したい場合があります。

このチュートリアルでは、セキュリティをカスタマイズするための2つのアプローチを見ていきます。@EnableWebSecurity@EnableGlobalMethodSecurityを使用します。

違いを説明するために、いくつかの管理リソース、認証されたユーザーリソースを持つ単純なアプリケーションを使用します。 また、誰でもダウンロードできる公開リソースのセクションを提供します。

2. スプリングブートセキュリティ

2.1. Mavenの依存関係

どちらのアプローチを採用する場合でも、最初にセキュリティのためにスプリングブートスターターを追加する必要があります。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2.2. スプリングブートの自動構成

クラスパスにSpringSecurityを使用すると、 Spring Boot SecurityAuto-ConfigurationWebSecurityEnablerConfiguration@EnableWebSecurityをアクティブにします。

これにより、Springのデフォルトのセキュリティ構成がアプリケーションに適用されます。

デフォルトのセキュリティは、HTTPセキュリティフィルターとセキュリティフィルターチェーンの両方をアクティブにし、エンドポイントに基本認証を適用します。

3. エンドポイントの保護

最初のアプローチでは、WebSecurityConfigurerAdapterを拡張するMySecurityConfigurerクラスを作成し、@EnableWebSecurity。で注釈を付けることから始めましょう。

@EnableWebSecurity
public class MySecurityConfigurer extends WebSecurityConfigurerAdapter {
}

アダプターを拡張することにより、Spring Securityの他の防御の利点を享受しながら、カスタマイズを追加することもできます。

3.1. デフォルトのWebセキュリティの概要

まず、WebSecurityConfigurerAdapterのデフォルトのconfigureメソッドを見てみましょう。これで、オーバーライドしようとしているものがわかります。

@Override
protected void configure(HttpSecurity http) {
    http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
    http.formLogin();
    http.httpBasic();
}

ここでは、受け取ったすべてのリクエストが認証されており、資格情報の入力を求める基本的なフォームログインがあります。

HttpSecurity DSLを使用する場合は、次のように記述します。

http.authorizeRequests().anyRequest().authenticated()
  .and().formLogin()
  .and().httpBasic()

3.2. ユーザーに適切な役割を要求する

次に、ADMINロールを持つユーザーのみが/adminエンドポイントにアクセスできるようにセキュリティを構成しましょう。 また、USERロールを持つユーザーのみが/protectedエンドポイントにアクセスできるようにします。

これは、configureHttpSecurityオーバーロードをオーバーライドすることで実現されます。

@Override
protected void configure(HttpSecurity http) {
    http.authorizeRequests()
      .antMatchers("/admin/**")
      .hasRole("ADMIN")
      .antMatchers("/protected/**")
      .hasRole("USER");
}

3.3. 公共資源のセキュリティを緩和する

パブリック/hello リソースの認証は必要ないため、WebSecurityがそれらに対して何もしないように構成します。

前と同じように、WebSecurityConfigurerAdapterconfigureメソッドの1つをオーバーライドしましょう。ただし、今回はWebSecurityのオーバーロードです。

@Override
public void configure(WebSecurity web) {
    web.ignoring()
      .antMatchers("/hello/*");
}

3.4. Springのデフォルトのセキュリティを置き換える

WebSecurityConfigurerAdapter を拡張することでほとんどの要件を満たすことができますが、Springのデフォルトのセキュリティ構成を完全に置き換えたい場合があります。 これを行うには、 WebSecurityConfigurerAdapter を拡張するのではなく、WebSecurityConfigurerを実装できます。

WebSecurityConfigurerを実装すると、Springの標準的なセキュリティ防御が失われることに注意する必要があります。したがって、この方法をとる前に、慎重に検討する必要があります。

4. アノテーションを使用してエンドポイントを保護する

アノテーション駆動型アプローチを使用してセキュリティを適用するには、@EnableGlobalMethodSecurity。を使用できます。

4.1. セキュリティアノテーションを使用してユーザーに適切な役割を要求する

次に、メソッドアノテーションを使用して、ADMINユーザーのみが/admin エンドポイントにアクセスし、USERユーザーが/protectedにアクセスできるようにセキュリティを構成しましょう。 エンドポイント。

EnableGlobalMethodSecurityアノテーションでjsr250Enabled= true を設定して、JSR-250アノテーションを有効にしましょう。

@EnableGlobalMethodSecurity(jsr250Enabled = true)
@Controller
public class AnnotationSecuredController {
    @RolesAllowed("ADMIN")
    @RequestMapping("/admin")
    public String adminHello() {
        return "Hello Admin";
    }

    @RolesAllowed("USER")
    @RequestMapping("/protected")
    public String jsr250Hello() {
        return "Hello Jsr250";
    }
}

4.2. すべてのパブリックメソッドにセキュリティを適用する

セキュリティを実装する方法としてアノテーションを使用する場合、メソッドにアノテーションを付けるのを忘れることがあります。 これにより、誤ってセキュリティホールが作成されます。

これを防ぐために、承認アノテーションがないすべてのメソッドへのアクセスを拒否する必要があります。

4.3. パブリックリソースへのアクセスを許可する

Springのデフォルトのセキュリティは、役割ベースのセキュリティを追加するかどうかに関係なく、すべてのエンドポイントに認証を適用します。

前の例では、 /adminおよび/protected エンドポイントにセキュリティを適用していますが、 /helloのファイルベースのリソースへのアクセスを許可する必要があります。

WebSecurityAdapter を再度拡張することもできますが、Springはより簡単な代替手段を提供します。

メソッドをアノテーションで保護したので、 WebSecurityCustomizer を追加して、 / hello /*リソースを開くことができます。

public class MyPublicPermitter implements WebSecurityCustomizer {
    public void customize(WebSecurity webSecurity) {
        webSecurity.ignoring()
          .antMatchers("/hello/*");
    }
}

または、構成クラス内にそれを実装するBeanを作成することもできます。

@Configuration
public class MyWebConfig {
    @Bean
    public WebSecurityCustomizer ignoreResources() {
        return (webSecurity) -> webSecurity
          .ignoring()
          .antMatchers("/hello/*");
    }
}

Spring Securityが初期化されると、検出したWebSecurityCustomizerを呼び出します。これには私たちも含まれます。

5. セキュリティのテスト

セキュリティを設定したので、意図したとおりに動作することを確認する必要があります。

セキュリティのために選択したアプローチに応じて、自動テストには1つまたは2つのオプションがあります。 アプリケーションにWebリクエストを送信するか、コントローラーメソッドを直接呼び出すことができます。

5.1. Webリクエストによるテスト

最初のオプションでは、@TestRestTemplateを使用して@SpringBootTestテストクラスを作成します。

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class WebSecuritySpringBootIntegrationTest {
    @Autowired
    private TestRestTemplate template;
}

それでは、公開リソースが利用可能であることを確認するためのテストを追加しましょう。

@Test
public void givenPublicResource_whenGetViaWeb_thenOk() {
    ResponseEntity<String> result = template.getForEntity("/hello/baeldung.txt", String.class);
    assertEquals("Hello From Baeldung", result.getBody());
}

また、保護されたリソースの1つにアクセスしようとするとどうなるかを確認できます。

@Test
public void whenGetProtectedViaWeb_thenForbidden() {
    ResponseEntity<String> result = template.getForEntity("/protected", String.class);
    assertEquals(HttpStatus.FORBIDDEN, result.getStatusCode());
}

ここでは、匿名のリクエストに必要な役割がないため、FORBIDDENの応答が返されます。

したがって、この方法を使用して、選択したセキュリティアプローチに関係なく、セキュリティで保護されたアプリケーションをテストできます。

5.2. 自動配線と注釈によるテスト

次に、2番目のオプションを見てみましょう。 @SpringBootTest を設定し、 AnnotationSecuredController:を自動配線してみましょう。

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class GlobalMethodSpringBootIntegrationTest {
    @Autowired
    private AnnotationSecuredController api;
}

@WithAnonymousUser を使用して、公的にアクセス可能なメソッドをテストすることから始めましょう。

@Test
@WithAnonymousUser
public void givenAnonymousUser_whenPublic_thenOk() {
    assertThat(api.publicHello()).isEqualTo(HELLO_PUBLIC);
}

パブリックリソースにアクセスしたので、@WithMockUserアノテーションを使用して保護されたメソッドにアクセスしましょう。

まず、「USER」ロールを持つユーザーを使用して、JSR-250で保護されたメソッドをテストしてみましょう。

@WithMockUser(username="baeldung", roles = "USER")
@Test
public void givenUserWithRole_whenJsr250_thenOk() {
    assertThat(api.jsr250Hello()).isEqualTo("Hello Jsr250");
}

そして今、私たちのユーザーが適切な役割を持っていないときに同じメソッドにアクセスしてみましょう:

@WithMockUser(username="baeldung", roles = "NOT-USER")
@Test(expected = AccessDeniedException.class)
public void givenWrongRole_whenJsr250_thenAccessDenied() {
    api.jsr250Hello();
}

私たちのリクエストはSpringSecurityによって傍受され、AccessDeniedExceptionがスローされました。

このアプローチは、注釈ベースのセキュリティを選択した場合にのみ使用できます。

6. 注釈に関する注意

注釈ベースのアプローチを選択する場合、注意すべき重要な点がいくつかあります。

注釈付きのセキュリティは、パブリックメソッドを介してクラスに入るときにのみ適用されます。

6.1. 間接的なメソッド呼び出し

以前、注釈付きメソッドを呼び出したときに、セキュリティが正常に適用されていることを確認しました。 ただし、同じクラスにセキュリティアノテーションを付けずにパブリックメソッドを作成しましょう。 注釈付きのjsr250Helloメソッドを呼び出すようにします。

@GetMapping("/indirect")
public String indirectHello() {
    return jsr250Hello();
}

次に、匿名アクセスを使用して「/indirect」エンドポイントを呼び出しましょう。

@Test
@WithAnonymousUser
public void givenAnonymousUser_whenIndirectCall_thenNoSecurity() {
    assertThat(api.indirectHello()).isEqualTo(HELLO_JSR_250);
}

‘secured’メソッドがセキュリティをトリガーせずに呼び出されたため、テストに合格しました。 つまり、同じクラス内の内部呼び出しにはセキュリティが適用されません。

6.2. 別のクラスへの間接的なメソッド呼び出し

次に、保護されていないメソッドが別のクラスでアノテーション付きメソッドを呼び出すとどうなるかを見てみましょう。

まず、注釈付きメソッドdifferentJsr250Helloを使用してDifferentClassを作成しましょう。

@Component
public class DifferentClass {
    @RolesAllowed("USER")
    public String differentJsr250Hello() {
        return "Hello Jsr250";
    }
}

次に、 DifferentClass をコントローラーに自動配線し、保護されていないdifferentClassHelloパブリックメソッドを追加して呼び出します。

@Autowired
DifferentClass differentClass;

@GetMapping("/differentclass")
public String differentClassHello() {
    return differentClass.differentJsr250Hello();
}

最後に、呼び出しをテストして、セキュリティが適用されていることを確認しましょう。

@Test(expected = AccessDeniedException.class)
@WithAnonymousUser
public void givenAnonymousUser_whenIndirectToDifferentClass_thenAccessDenied() {
    api.differentClassHello();
}

したがって、同じクラスの別のメソッドを呼び出すときにセキュリティアノテーションが尊重されない場合でも、別のクラスのアノテーション付きメソッドを呼び出すと、セキュリティアノテーションが尊重されることがわかります。

6.3. 注意の最後の注意

@EnableGlobalMethodSecurityが正しく構成されていることを確認する必要があります。 そうしないと、すべてのセキュリティアノテーションにもかかわらず、まったく効果がない可能性があります。

たとえば、JSR-250アノテーションを使用しているが、 jsr250Enabled = trueの代わりにprePostEnabled= true を指定した場合、JSR-250アノテーションは何もしません。

@EnableGlobalMethodSecurity(prePostEnabled = true)

もちろん、 @EnableGlobalMethodSecurity アノテーションに両方を追加することで、複数のアノテーションタイプを使用することを宣言できます。

@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)

7. もっと必要なとき

JSR-250と比較して、 Spring MethodSecurityも使用できます。 これには、より高度な認証シナリオのためのより強力な Spring Security Expression 言語(SpEL)の使用が含まれます。 prePostEnabled = true:を設定することにより、EnableGlobalMethodSecurityアノテーションでSpELを有効にできます。

@EnableGlobalMethodSecurity(prePostEnabled = true)

さらに、ドメインオブジェクトがユーザーによって所有されているかどうかに基づいてセキュリティを強化する場合は、 Spring Security Access ControlListsを使用できます。

また、リアクティブアプリケーションを作成する場合は、代わりに@EnableWebFluxSecurity@EnableReactiveMethodSecurityを使用することにも注意してください。

8. 結論

このチュートリアルでは、最初に@EnableWebSecurity。で一元化されたセキュリティルールアプローチを使用してアプリケーションを保護する方法を確認しました。

次に、これらのルールを影響を受けるコードに近づけるようにセキュリティを構成することで、これに基づいて構築しました。 これは、 @EnableGlobalMethodSecurity を使用し、保護したいメソッドにアノテーションを付けることで行いました。

最後に、セキュリティを必要としない公共リソースのセキュリティを緩和する別の方法を紹介しました。

いつものように、サンプルコードはGitHubから入手できます。