Mehrere Einstiegspunkte in der Frühlingssicherheit

Mehrere Einstiegspunkte in Spring Security

1. Überblick

In diesem kurzen Tutorial werden wir uns ansehen, wie mandefine multiple entry points in a Spring Security application macht.

Dies umfasst hauptsächlich das Definieren mehrererhttp-Blöcke in einer XML-Konfigurationsdatei oder mehrererHttpSecurity-Instanzen durch mehrfaches Erweitern derWebSecurityConfigurerAdapter-Klasse.

2. Maven-Abhängigkeiten

Für die Entwicklung benötigen wir folgende Abhängigkeiten:


    org.springframework.boot
    spring-boot-starter-security
    2.0.0.RELEASE


    org.springframework.boot
    spring-boot-starter-web
    2.0.0.RELEASE


    org.springframework.boot
    spring-boot-starter-thymeleaf
    2.0.0.RELEASE


    org.springframework.boot
    spring-boot-starter-test
    2.0.0.RELEASE


    org.springframework.security
    spring-security-test
    5.0.4.RELEASE

3. Mehrere Einstiegspunkte

3.1. Mehrere Einstiegspunkte mit mehreren HTTP-Elementen

Definieren wir die Hauptkonfigurationsklasse, die eine Benutzerquelle enthält:

@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();
    }
}

Schauen wir uns nunhow we can define multiple entry points in unserer Sicherheitskonfiguration an.

Wir werden hier ein Beispiel verwenden, das von der Basisauthentifizierung gesteuert wird, und wir werden die Tatsache, dassSpring Security supports the definition of multiple HTTP elementsin unseren Konfigurationen enthalten ist, gut nutzen.

Wenn Sie die Java-Konfiguration verwenden, können Sie mehrere Sicherheitsbereiche definieren, indem Sie mehrere@Configuration-Klassen haben, die dieWebSecurityConfigurerAdapter-Basisklasse erweitern - jede mit ihrer eigenen Sicherheitskonfiguration. Diese Klassen können statisch sein und sich in der Hauptkonfiguration befinden.

Die Hauptmotivation für mehrere Einstiegspunkte in einer Anwendung besteht darin, dass es verschiedene Benutzertypen gibt, die auf verschiedene Teile der Anwendung zugreifen können.

Definieren wir eine Konfiguration mit drei Einstiegspunkten mit jeweils unterschiedlichen Berechtigungen und Authentifizierungsmodi:

  • eine für administrative Benutzer, die die HTTP-Basisauthentifizierung verwenden

  • eine für normale Benutzer, die die Formularauthentifizierung verwenden

  • und eine für Gastbenutzer, die keine Authentifizierung benötigen

Der für administrative Benutzer definierte Einstiegspunkt sichert URLs der Form/admin/** so, dass nur Benutzer mit der Rolle ADMIN zugelassen werden, und erfordert eine HTTP-Basisauthentifizierung mit einem Einstiegspunkt vom TypBasicAuthenticationEntryPoint, der mitauthenticationEntryPoint()festgelegt wird ) s Methode:

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

Die Annotation@Order für jede statische Klasse gibt die Reihenfolge an, in der die Konfigurationen berücksichtigt werden, um eine zu finden, die der angeforderten URL entspricht. The order value for each class must be unique.

Für die Bean vom TypBasicAuthenticationEntryPoint muss die EigenschaftrealName festgelegt werden.

3.2. Mehrere Einstiegspunkte, gleiches HTTP-Element

Als Nächstes definieren wir die Konfiguration für URLs des Formulars/user/**, auf die normale Benutzer mit einer USER-Rolle mithilfe der Formularauthentifizierung zugreifen können:

@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/**"));
    }
}

Wie wir sehen können, besteht eine andere Möglichkeit zum Definieren von Einstiegspunkten neben der Methode authenticationEntryPoint () darin, die MethodedefaultAuthenticationEntryPointFor()zu verwenden. Dies kann mehrere Einstiegspunkte definieren, die unterschiedlichen Bedingungen basierend auf einemRequestMatcher-Objekt entsprechen.

DieRequestMatcher-Schnittstelle verfügt über Implementierungen, die auf verschiedenen Arten von Bedingungen basieren, z. B. Übereinstimmungspfad, Medientyp oder regulärer Ausdruck. In unserem Beispiel haben wir mit AntPathRequestMatch zwei verschiedene Einstiegspunkte für URLs der Formulare/user/private/ und/user/general/ festgelegt.

Als Nächstes müssen wir die Einstiegspunkt-Beans in derselben statischen Konfigurationsklasse definieren:

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

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

Der wichtigste Punkt hierbei ist, wie diese mehreren Einstiegspunkte eingerichtet werden - nicht unbedingt die Implementierungsdetails jedes einzelnen.

In diesem Fall sind die Einstiegspunkte beide vom TypLoginUrlAuthenticationEntryPoint und verwenden unterschiedliche Anmeldeseiten-URL:/userLogin für eine einfache Anmeldeseite und/userLoginWithWarning für eine Anmeldeseite, auf der auch eine Warnung angezeigt wird, wenn Versuch, auf die privaten URLs von/user/zuzugreifen.

Für diese Konfiguration müssen auch die MVC-Zuordnungen/userLogin und/userLoginWithWarning sowie zwei Seiten mit einem Standard-Anmeldeformular definiert werden.

Für die Formularauthentifizierung ist es sehr wichtig zu beachten, dass alle für die Konfiguration erforderlichen URLs, z. B. die URL für die Anmeldeverarbeitung, auch dem Format/user/**entsprechen oder anderweitig konfiguriert sein müssen, um zugänglich zu sein.

Beide oben genannten Konfigurationen werden zu einer/403-URL umgeleitet, wenn ein Benutzer ohne die entsprechende Rolle versucht, auf eine geschützte URL zuzugreifen.

Be careful to use unique names for the beans even if they are in different static classes, andernfalls überschreibt einer den anderen.

3.3. Neues HTTP-Element, kein Einstiegspunkt

Definieren wir abschließend die dritte Konfiguration für URLs der Form/guest/**, die alle Benutzertypen zulässt, auch nicht authentifizierte:

@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-Konfiguration

Schauen wir uns die entsprechende XML-Konfiguration für die dreiHttpSecurity-Instanzen im vorherigen Abschnitt an.

Wie erwartet enthält dies drei separate XML<http>-Blöcke.

Für die URLs von/admin/**verwendet die XML-Konfiguration das Attributentry-point-refdes Elementshttp-basic:


    
    



     

Hierbei ist zu beachten, dass bei Verwendung der XML-Konfiguration die Rollen die FormROLE _ haben müssen.

Die Konfiguration für die/user/**-URLs muss in XML in zweihttp-Blöcke aufgeteilt werden, da es keine direkte Entsprechung zurdefaultAuthenticationEntryPointFor()-Methode gibt.

Die Konfiguration für URLs / user / general / ** lautet:


    
    //form-login configuration



  

Für die/user/private/** URLs können wir eine ähnliche Konfiguration definieren:


    
    //form-login configuration



    

Für die/guest/** URLs haben wir dashttp Element:


    

Hierbei ist auch wichtig, dass mindestens ein XML<http>-Block mit dem / ** -Muster übereinstimmt.

4. Zugriff auf geschützte URLs

4.1. MVC-Konfiguration

Erstellen wir Anforderungszuordnungen, die den von uns gesicherten URL-Mustern entsprechen:

@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";
    }
}

Die Zuordnung von/multipleHttpLinksgibt eine einfache HTML-Seite mit Links zu den geschützten URLs zurück:

Jede der HTML-Seiten, die den geschützten URLs entsprechen, hat einen einfachen Text und einen Backlink:

Welcome admin!

Back to links

4.2. Initialisierung der Anwendung

Wir werden unser Beispiel als Spring Boot-Anwendung ausführen. Definieren wir also eine Klasse mit der Hauptmethode:

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

Wenn wir die XML-Konfiguration verwenden möchten, müssen wir unserer Hauptklasse auch die Annotation@ImportResource(\{“classpath*:spring-security-multiple-entry.xml”})hinzufügen.

4.3. Testen der Sicherheitskonfiguration

Richten wir eine JUnit-Testklasse ein, mit der wir unsere geschützten URLs testen können:

@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();
    }
}

Testen Sie als Nächstes die URLs mit dem Benutzeradmin.

Wenn Sie die URL von/admin/adminPageohne HTTP-Basisauthentifizierung anfordern, sollten Sie mit einem nicht autorisierten Statuscode rechnen. Nach dem Hinzufügen der Authentifizierung sollte der Statuscode 200 OK sein.

Wenn wir versuchen, mit dem Administrator auf die URL von/user/userPagezuzugreifen, sollten wir den Status 302 Verboten erhalten:

@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());
}

Erstellen wir einen ähnlichen Test mit den regulären Benutzeranmeldeinformationen, um auf die URLs zuzugreifen:

@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());
}

Im zweiten Test können wir feststellen, dass das Fehlen der Formularauthentifizierung zu einem Status von 302 Gefunden statt Nicht autorisiert führt, da Spring Security zum Anmeldeformular umleitet.

Erstellen wir abschließend einen Test, bei dem wir auf die URL von/guest/guestPagezugreifen, um alle drei Authentifizierungstypen zu überprüfen und sicherzustellen, dass wir den Status 200 OK erhalten:

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

In diesem Tutorial haben wir gezeigt, wie Sie mit Spring Security mehrere Einstiegspunkte konfigurieren.

Den vollständigen Quellcode für die Beispiele finden Sie inover on GitHub. Um die Anwendung auszuführen, kommentieren Sie dasMultipleEntryPointsApplicationstart-class-Tag inpom.xml aus, führen Sie den Befehlmvn spring-boot:run aus und greifen Sie dann auf die/multipleHttpLinks-URL. zu

Beachten Sie, dass es nicht möglich ist, sich bei Verwendung der HTTP-Basisauthentifizierung abzumelden. Sie müssen daher den Browser schließen und erneut öffnen, um diese Authentifizierung zu entfernen.

Verwenden Sie zum Ausführen des JUnit-Tests das definierte Maven-ProfilentryPoints mit dem folgenden Befehl:

mvn clean install -PentryPoints