1. 概要

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

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

2. Mavenの依存関係

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.6.1</version>
</dependency>
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-web</artifactId> 
    <version>2.6.1</version> 
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.6.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>2.6.1</version>
</dependency>    
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>5.4.0</version>
</dependency>

spring-boot-starter-security spring-boot-starter-web spring-boot-starter-thymeleaf springの最新バージョン-boot-starter-test spring-security-testはMavenCentralからダウンロードできます。

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セキュリティが構成で複数のHTTP要素の定義をサポートしているという事実をうまく利用します。

Java構成を使用する場合、複数のセキュリティレルムを定義する方法は、WebSecurityConfigurerAdapter基本クラスを拡張する複数の@Configurationクラスを持つことです。それぞれに独自のセキュリティ構成があります。 これらのクラスは静的で、メイン構成内に配置できます。

1つのアプリケーションに複数のエントリポイントを含める主な動機は、アプリケーションのさまざまな部分にアクセスできるさまざまなタイプのユーザーがいる場合です。

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

  • 1つはHTTP基本認証を使用する管理ユーザー用です
  • フォーム認証を使用する通常のユーザー用に1つ
  • 1つは認証を必要としないゲストユーザー用です

管理ユーザー用に定義されたエントリポイントは、 / admin / ** の形式のURLを保護して、ADMINの役割を持つユーザーのみを許可し、タイプBasicAuthenticationEntryPointのエントリポイントでHTTP基本認証を要求します。これは、 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に一致する構成を見つけるために構成が考慮される順序を示します。 各クラスの注文値は一意である必要があります。

タイプ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()メソッド以外に、エントリポイントを定義する別の方法は、 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 / user/プライベートURLにアクセスしようとしたときにも警告を表示するログインページ。

この構成では、 /userLoginおよび/userLoginWithWarningMVCマッピングと標準のログインフォームを使用した2つのページも定義する必要があります。

フォーム認証の場合、ログイン処理URLなどの構成に必要なURLも、 / user / ** 形式に従うか、アクセスできるように構成する必要があることを覚えておくことが非常に重要です。

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

異なる静的クラスにある場合でも、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が含まれますブロック。

/ 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構成を使用する場合、役割は次の形式である必要があるということです。 役割_

/ user / ** URLの構成は、 defaultAuthenticationEntryPointFor()[X197Xに直接相当するものがないため、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 ブロックは/**パターンと一致する必要があります。

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を要求する場合、未承認のステータスコードを受け取ることを期待する必要があり、認証を追加した後、ステータスコードは200OKになります。

adminユーザーで/user / userPage URLにアクセスしようとすると、ステータス302Forbiddenを受け取るはずです。

@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ではなく302Foundになることがわかります。

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

@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. 結論

このチュートリアルでは、SpringSecurityを使用するときに複数のエントリポイントを構成する方法を示しました。

例の完全なソースコードは、GitHubにあります。 アプリケーションを実行するには、コメントを外します MultipleEntryPointsApplication スタートクラスのタグ pom.xml コマンドを実行します mvn spring-boot:run 、次にアクセスします / multipleHttpLinks URL 。

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

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

mvn clean install -PentryPoints