Spring Securityの複数のエントリポイント

1概要

このクイックチュートリアルでは、Spring Securityアプリケーションで複数のエントリポイントを定義する方法を見ていきます。

これは主に、 WebSecurityConfigurerAdapter クラスを複数回拡張することによって、XML構成ファイルまたは複数の HttpSecurity インスタンスに複数の http ブロックを定義することを伴います。

2 Mavenの依存関係

開発には、以下の依存関係が必要になります。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.0.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.0.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.0.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>2.0.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>5.0.4.RELEASE</version>
</dependency>

spring-boot-starter-security 、https://の最新バージョンsearch.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22spring-boot-starter-web%22%20AND%20g%3A%22org.springframework.boot%22[spring-boot-starter-web]、https://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22spring-boot-starter-thymeleaf%22[spring-boot-starter-thymeleaf]、https://検索。 maven.org/classic/#search%7Cga%7C1%7Ca%3A%22spring-boot-starter-test%22[spring-boot-starter-test]、https://search.maven.org/classic/#search %7Cga%7C1%7Ca%3A%22スプリングセキュリティテスト%22[スプリングセキュリティテスト]はMaven Centralからダウンロードできます。

3複数のエントリーポイント

** 3.1. 複数のHTTP要素を持つ複数のエントリポイント

**

ユーザーソースを保持するメインの設定クラスを定義しましょう。

@Configuration
@EnableWebSecurity
public class MultipleEntryPointsSecurityConfig {

    @Bean
    public UserDetailsService userDetailsService() throws Exception {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User
          .withUsername("user")
          .password(encoder().encode("userPass"))
          .roles("USER").build());
        manager.createUser(User
          .withUsername("admin")
          .password(encoder().encode("adminPass"))
          .roles("ADMIN").build());
        return manager;
    }

    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }
}

それでは、セキュリティ設定で** 複数のエントリポイントを定義する方法を見てみましょう。

ここでは基本認証を使用した例を使用します。また、設定では Spring Securityが複数のHTTP要素の定義 をサポートしていることを活用します。

Java設定を使用する場合、複数のセキュリティレルムを定義する方法は、 WebSecurityConfigurerAdapter 基本クラスを拡張する複数の @ Configuration クラスを作成することです。これらのクラスは静的にすることができ、メイン設定の中に置くことができます。

1つのアプリケーションに複数のエントリポイントがある主な動機は、アプリケーションのさまざまな部分にアクセスできるさまざまな種類のユーザーがいるかどうかです。

それぞれ異なる権限と認証モードを持つ3つのエントリポイントを持つ構成を定義しましょう。

  • HTTP基本認証を使用している管理ユーザー用

  • フォーム認証を使用する一般ユーザー向け

  • 認証を必要としないゲストユーザ用

管理ユーザー用に定義されたエントリポイントは、ADMINのロールを持つユーザーのみを許可するように /admin/ の形式のURLを保護し、 authenticationEntryPoint() メソッドを使用して設定される:

@Configuration
@Order(1)
public static class App1ConfigurationAdapter extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/admin/** ** ")
            .authorizeRequests().anyRequest().hasRole("ADMIN")
            .and().httpBasic().authenticationEntryPoint(authenticationEntryPoint());
    }

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint(){
        BasicAuthenticationEntryPoint entryPoint =
          new BasicAuthenticationEntryPoint();
        entryPoint.setRealmName("admin realm");
        return entryPoint;
    }
}

各静的クラスの @ Order アノテーションは、要求されたURLに一致するものを見つけるために構成が考慮される順序を示します。 ** 各クラスの order 値は一意である必要があります。

タイプ BasicAuthenticationEntryPoint のBeanには、プロパティー realName を設定する必要があります。

3.2. 複数のエントリポイント、同じHTTP要素

次に、フォーム認証を使用してUSERロールを持つ一般ユーザーがアクセスできる /user/ という形式のURLの設定を定義しましょう。

@Configuration
@Order(2)
public static class App2ConfigurationAdapter extends WebSecurityConfigurerAdapter {

    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/user/** ** ")
            .authorizeRequests().anyRequest().hasRole("USER")
            .and()
           //formLogin configuration
            .and()
            .exceptionHandling()
            .defaultAuthenticationEntryPointFor(
              loginUrlauthenticationEntryPointWithWarning(),
              new AntPathRequestMatcher("/user/private/** ** "))
            .defaultAuthenticationEntryPointFor(
              loginUrlauthenticationEntryPoint(),
              new AntPathRequestMatcher("/user/general/** ** "));
    }
}

ご覧のとおり、authenticationEntryPoint()メソッド以外のエントリポイントを定義するもう1つの方法は、 defaultAuthenticationEntryPointFor() メソッドを使用することです。これは、 RequestMatcher オブジェクトに基づいてさまざまな条件に一致する複数のエントリポイントを定義できます。

RequestMatcher インタフェースには、パスの一致、メディアの種類、正規表現など、さまざまな種類の条件に基づく実装があります。この例では、AntPathRequestMatchを使用して、 /user/private/ /user/general/ の形式のURLに2つの異なるエントリポイントを設定しました。

次に、エントリポイントBeanを同じ静的設定クラスに定義する必要があります。

@Bean
public AuthenticationEntryPoint loginUrlauthenticationEntryPoint(){
    return new LoginUrlAuthenticationEntryPoint("/userLogin");
}

@Bean
public AuthenticationEntryPoint loginUrlauthenticationEntryPointWithWarning(){
    return new LoginUrlAuthenticationEntryPoint("/userLoginWithWarning");
}

ここでの主なポイントは、これらの複数のエントリポイントを設定する方法です。必ずしもそれぞれの実装の詳細ではありません。

この場合、エントリポイントは両方とも LoginUrlAuthenticationEntryPoint タイプであり、異なるログインページURLを使用します。

単純なログインページの場合は /userLogin 、ログインページの場合は /userLoginWithWarning で、 /user/ プライベートURLにアクセスしようとすると警告も表示されます。

この設定では、 /userLogin および /userLoginWithWarning MVCマッピングと、標準ログインフォームを使用した2ページの定義も必要になります。

フォーム認証では、設定に必要なURL(ログイン処理URLなど)も /user/ の形式に従う必要があるか、またはその他の方法でアクセスできるように設定する必要があることを忘れないでください。

適切な役割を持たないユーザーが保護されたURLにアクセスしようとすると、上記の両方の設定が /403 URLにリダイレクトされます。

  • それらが異なる静的クラスに属している場合であっても、Beanに固有の名前を使用するように注意してください。** そうでない場合、一方が他方をオーバーライドします。

** 3.3. 新しいHTTP要素、エントリポイントなし

最後に、認証されていないユーザーも含め、すべてのタイプのユーザーを許可する /guest/ という形式のURLの3番目の設定を定義しましょう。

@Configuration
@Order(3)
public static class App3ConfigurationAdapter extends WebSecurityConfigurerAdapter {

    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/guest/** ** ").authorizeRequests().anyRequest().permitAll();
    }
}

3.4. XML設定

前のセクションの3つの HttpSecurity インスタンスの同等のXML構成を見てみましょう。

予想通り、これには3つの別々のXML <http> ブロックが含まれます。

/admin/ URLの場合、XML構成は http-basic 要素の entry-point-ref 属性を使用します。

<security:http pattern="/admin/** ** " use-expressions="true" auto-config="true">
    <security:intercept-url pattern="/** ** " access="hasRole('ROLE__ADMIN')"/>
    <security:http-basic entry-point-ref="authenticationEntryPoint"/>
</security:http>

<bean id="authenticationEntryPoint"
  class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
     <property name="realmName" value="admin realm"/>
</bean>

ここで注目に値するのは、XML設定を使用する場合、ロールは ROLE <ROLE NAME> の形式でなければならないということです。

defaultAuthenticationEntryPointFor() メソッドに直接相当するものがないため、 /user/ URLの設定はxmlで2つの http ブロックに分割する必要があります。

URL/user/general/ の設定は次のとおりです。

<security:http pattern="/user/general/** ** " use-expressions="true" auto-config="true"
  entry-point-ref="loginUrlAuthenticationEntryPoint">
    <security:intercept-url pattern="/** ** " access="hasRole('ROLE__USER')"/>
   //form-login configuration
</security:http>

<bean id="loginUrlAuthenticationEntryPoint"
  class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
  <constructor-arg name="loginFormUrl" value="/userLogin"/>
</bean>

/user/private/ URLについても、同様の設定を定義できます。

<security:http pattern="/user/private/** ** " use-expressions="true" auto-config="true"
  entry-point-ref="loginUrlAuthenticationEntryPointWithWarning">
    <security:intercept-url pattern="/** ** " access="hasRole('ROLE__USER')"/>
   //form-login configuration
</security:http>

<bean id="loginUrlAuthenticationEntryPointWithWarning"
  class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
    <constructor-arg name="loginFormUrl" value="/userLoginWithWarning"/>
</bean>

/guest/ URLの場合、 http 要素があります。

<security:http pattern="/** ** " use-expressions="true" auto-config="true">
    <security:intercept-url pattern="/guest/** ** " access="permitAll()"/>
</security:http>

ここでも重要なのは、少なくとも1つのXML <http> ブロックが/ パターンと一致する必要があるということです。

4保護されたURLへのアクセス

4.1. MVC設定

保護したURLパターンと一致するリクエストマッピングを作成しましょう。

@Controller
public class PagesController {

    @GetMapping("/admin/myAdminPage")
    public String getAdminPage() {
        return "multipleHttpElems/myAdminPage";
    }

    @GetMapping("/user/general/myUserPage")
    public String getUserPage() {
        return "multipleHttpElems/myUserPage";
    }

    @GetMapping("/user/private/myPrivateUserPage")
    public String getPrivateUserPage() {
        return "multipleHttpElems/myPrivateUserPage";
    }

    @GetMapping("/guest/myGuestPage")
    public String getGuestPage() {
        return "multipleHttpElems/myGuestPage";
    }

    @GetMapping("/multipleHttpLinks")
    public String getMultipleHttpLinksPage() {
        return "multipleHttpElems/multipleHttpLinks";
    }
}

/multipleHttpLinks マッピングは、保護されたURLへのリンクを含む単純なHTMLページを返します。

<a th:href="@{/admin/myAdminPage}">Admin page</a>
<a th:href="@{/user/general/myUserPage}">User page</a>
<a th:href="@{/user/private/myPrivateUserPage}">Private user page</a>
<a th:href="@{/guest/myGuestPage}">Guest page</a>

保護されたURLに対応する各HTMLページには、単純なテキストとバックリンクがあります。

Welcome admin!

<a th:href="@{/multipleHttpLinks}" >Back to links</a>

** 4.2. アプリケーションの初期化

この例はSpring Bootアプリケーションとして実行しますので、mainメソッドでクラスを定義しましょう。

@SpringBootApplication
public class MultipleEntryPointsApplication {
    public static void main(String[]args) {
        SpringApplication.run(MultipleEntryPointsApplication.class, args);
    }
}

XML設定を使用したい場合は、 @ ImportResource(\ {“ classpath ** :spring-security-multiple-entry.xml”}) アノテーションをメインクラスに追加する必要もあります。

4.3. セキュリティ構成のテスト

保護されたURLをテストするために使用できるJUnitテストクラスを設定しましょう。

@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = MultipleEntryPointsApplication.class)
public class MultipleEntryPointsTest {

    @Autowired
    private WebApplicationContext wac;

    @Autowired
    private FilterChainProxy springSecurityFilterChain;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
          .addFilter(springSecurityFilterChain).build();
    }
}

次に、 admin ユーザーを使用してURLをテストしましょう。

HTTP基本認証なしで /admin/adminPage URLを要求すると、Unauthorizedステータスコードを受け取ることになります。認証を追加した後、ステータスコードは200 OKになります。

adminユーザーで /user/userPage URLにアクセスしようとすると、ステータス302 Forbiddenが表示されます。

@Test
public void whenTestAdminCredentials__thenOk() throws Exception {
    mockMvc.perform(get("/admin/myAdminPage")).andExpect(status().isUnauthorized());

    mockMvc.perform(get("/admin/myAdminPage")
      .with(httpBasic("admin", "adminPass"))).andExpect(status().isOk());

    mockMvc.perform(get("/user/myUserPage")
      .with(user("admin").password("adminPass").roles("ADMIN")))
      .andExpect(status().isForbidden());
}

通常のユーザー認証情報を使用してURLにアクセスする同様のテストを作成しましょう。

@Test
public void whenTestUserCredentials__thenOk() throws Exception {
    mockMvc.perform(get("/user/general/myUserPage")).andExpect(status().isFound());

    mockMvc.perform(get("/user/general/myUserPage")
      .with(user("user").password("userPass").roles("USER")))
      .andExpect(status().isOk());

    mockMvc.perform(get("/admin/myAdminPage")
      .with(user("user").password("userPass").roles("USER")))
      .andExpect(status().isForbidden());
}

2番目のテストでは、Spring Securityがログインフォームにリダイレクトするため、フォーム認証がないとステータスがUnauthorizedではなく302 Foundになります。

最後に、 /guest/guestPage URLにアクセスして3種類の認証すべてにアクセスし、ステータスが200 OKになることを確認するテストを作成しましょう。

@Test
public void givenAnyUser__whenGetGuestPage__thenOk() throws Exception {
    mockMvc.perform(get("/guest/myGuestPage")).andExpect(status().isOk());

    mockMvc.perform(get("/guest/myGuestPage")
      .with(user("user").password("userPass").roles("USER")))
      .andExpect(status().isOk());

    mockMvc.perform(get("/guest/myGuestPage")
      .with(httpBasic("admin", "adminPass")))
      .andExpect(status().isOk());
}

5結論

このチュートリアルでは、Spring Securityを使用するときに複数のエントリポイントを設定する方法を説明しました。

例の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/spring-security-mvc-boot[GitHubに載っています]。アプリケーションを実行するには、 pom.xml内の MultipleEntryPointsApplication start-class タグのコメントを外して mvn spring-boot:run コマンドを実行し、 /multipleHttpLinks URL . __にアクセスします。

HTTP基本認証を使用しているときはログアウトできないため、この認証を削除するにはブラウザを閉じて再度開く必要があります。

JUnitテストを実行するには、次のコマンドで定義済みのMavenプロファイル entryPoints を使用します。

mvn clean install -PentryPoints__