The Price of all “Learn Spring Security” course packages will permanently increase by $50 on the 8th of December:

>>> GET ACCESS NOW

1. Overview

In this quick tutorial, we’re going to take a look at how to define multiple entry points in a Spring Security application.

This mainly entails defining multiple http blocks in an XML configuration file or multiple HttpSecurity instances by extending the WebSecurityConfigurerAdapter class multiple times.

2. Maven Dependencies

For development, we will need the following dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>1.5.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>1.5.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>1.5.2.RELEASE</version>
</dependency>    
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>4.2.2.RELEASE</version>
</dependency>

The latest versions of spring-boot-starter-security, spring-boot-starter-thymeleaf, spring-boot-starter-test, spring-security-test can be downloaded from Maven Central.

3. Multiple Entry Points

3.1. Multiple Entry Points with Multiple HTTP Elements

Let’s define the main configuration class that will hold a user source:

@Configuration
@EnableWebSecurity
public class MultipleEntryPointsSecurityConfig {

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

Now, let’s look at how we can define multiple entry points in our security config.

We’re going to use an example driven by Basic Authentication here, and we’re going to make good use of the fact that Spring Security supports the definition of multiple HTTP elements in our configurations.

When using Java configuration, the way to define multiple security realms is to have multiple @Configuration classes that extend the WebSecurityConfigurerAdapter base class – each with its own security configuration. These classes can be static and placed inside the main config.

The main motivation for having multiple entry points in one application is if there are different types of users that can access different portions of the application.

Let’s define a configuration with three entry points, each with different permissions and authentication modes:

  • one for administrative users using HTTP Basic Authentication
  • one for regular users that use form authentication
  • and one for guest users that do not require authentication

The entry point defined for administrative users secures URLs of the form /admin/** to only allow users with a role of ADMIN and requires HTTP Basic Authentication with an entry point of type BasicAuthenticationEntryPoint that is set using the authenticationEntryPoint() method:

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

The @Order annotation on each static class indicates the order in which the configurations will be considered to find one that matches the requested URL. The order value for each class must be unique.

The bean of type BasicAuthenticationEntryPoint requires the property realName be set.

3.2. Multiple Entry Points, Same HTTP Element

Next, let’s define the configuration for URLs of the form /user/** that can be accessed by regular users with a USER role using form authentication:

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

As we can see, another way of defining entry points, besides the authenticationEntryPoint() method, is to use the defaultAuthenticationEntryPointFor() method. This can define multiple entry points that match different conditions based on a RequestMatcher object.

The RequestMatcher interface has implementations based on different types of conditions, such as matching path, media type or regexp. In our example, we have used the AntPathRequestMatch to set two different entry points for URLs of the forms /user/private/** and /user/general/**.

Next, we need to define the entry points beans in the same static configuration class:

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

The main point here is how to set up these multiple entry points – not necessarily the implementation details of each one.

In this case, the entry points are both of type LoginUrlAuthenticationEntryPoint, and use different login page URL: /userLogin for a simple login page and /userLoginWithWarning for a login page that also displays a warning when attempting to access the /user/ private URLs.

This configuration will also require defining the /userLogin and /userLoginWithWarning MVC mappings and two pages with a standard login form.

For the form authentication, it’s very important to remember that any URL necessary for the configuration, such as the login processing URL also needs to follow the /user/** format or be otherwise configured to be accessible.

Both of the above configurations will redirect to a /403 URL if a user without the appropriate role attempts to access a protected URL.

Be careful to use unique names for the beans even if they are in different static classes, otherwise one will override the other.

3.3. New HTTP Element, no Entry Point

Finally, let’s define the third configuration for URLs of the form /guest/** that will allow all types of users, including unauthenticated ones:

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

Let’s take a look at the equivalent XML configuration for the three HttpSecurity instances in the previous section.

As expected, this will contain three separate XML <http> blocks.

For the /admin/** URLs the XML configuration will use the entry-point-ref attribute of http-basic element:

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

Of note here is that if using XML configuration, the roles have to be of the form ROLE_<ROLE_NAME>.

The configuration for the /user/** URLs will have to be broken up into two http blocks in xml because there is no direct equivalent to the defaultAuthenticationEntryPointFor() method.

The configuration for URLs /user/general/** is:

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

For the /user/private/** URLs we can define a similar configuration:

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

For the /guest/** URLs we will have the http element:

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

Also important here is that at least one XML <http> block must match the /** pattern.

4. Accessing Protected URLs

4.1. MVC Configuration

Let’s create request mappings that match the URL patterns we have secured:

@Controller
public class PagesController {

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

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

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

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

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

The /multipleHttpLinks mapping will return a simple HTML page with links to the protected URLs:

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

Each of the HTML pages corresponding to the protected URLs will have a simple text and a back link:

Welcome admin!

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

4.2. Initializing the Application

We will run our example as a Spring Boot application, so let’s define a class with the main method:

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

If we want to use the XML configuration, we also need to add the @ImportResource({“classpath*:spring-security-multiple-entry.xml”}) annotation to our main class.

4.3. Testing the Security Configuration

Let’s set up a JUnit test class that we can use to test our protected URLs:

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

Next, let’s test the URLs using the admin user.

When requesting the /admin/adminPage URL without an HTTP Basic Authentication, we should expect to receive an Unauthorized status code, and after adding the authentication the status code should be 200 OK.

If attempting to access the /user/userPage URL with the admin user, we should receive status 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());
}

Let’s create a similar test using the regular user credentials to access the URLs:

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

In the second test, we can see that missing the form authentication will result in a status of 302 Found instead of Unauthorized, as Spring Security will redirect to the login form.

Finally, let’s create a test in which we access the /guest/guestPage URL will all three types of authentication and verify we receive a status of 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. Conclusion

In this tutorial, we have demonstrated how to configure multiple entry points when using Spring Security.

The complete source code for the examples can be found over on GitHub. To run the application, uncomment the MultipleEntryPointsApplication start-class tag in the pom.xml and run the command mvn spring-boot:run, then accesses the /multipleHttpLinks URL

Note that it is not possible to log out when using HTTP Basic Authentication, so you will have to close and reopen the browser to remove this authentication.

To run the JUnit test, use the defined Maven profile entryPoints with the following command:

mvn clean install -PentryPoints

The Price of all “Learn Spring Security” course packages will permanently increase by $50 on the 8th of December:

>>> GET ACCESS NOW

Sort by:   newest | oldest | most voted
Rahul
Guest
I have a web application which I build using spring boot, front end is react js front end consuming rest api exposed through spring boot which are stateless. The problem, I am facing is I have to implement spring security for authentication. What I have done so far, 1.) extend WebSecurityConfigurerAdapter 2.) implement UserDetailsService, as we have access to username and password as it is stored in our db. In configure(HttpSecurity http) method I have done java config for formlogin and custom form, also for successurl and failureurl. so, Now, when I go to localhost:8080, then it redirects to custom… Read more »
Eugen Paraschiv
Guest

Hey Rahul,
That’s an interesting question, but it’s unfortunately going to be difficult to answer without looking at the code. The way to go here would be a PR on Github (one the repo of this article), or at least an example project on Github – and I’d be happy to have a look.
Hope that helps. Cheers,
Eugen.

Don Nandipinto
Guest
Hi, this is interesting. I’m wondering if I can us this for my project. I’m developing a Spring MVC + Spring Security web application. There’s a requirement that if it’s accessed from certain external application client and/or IP addresses, it should not be showing the login page. I’m thinking of using some kind of TOKEN in HTTP header, but not sure how to implement it. The login page should still be displayed if accessed from a normal browser. After successful authentication, both token-based and form-based login should take the user to the same page, e.g.: his/her profile page. Any advice… Read more »
Eugen Paraschiv
Guest

Hey Don,
It sounds like this may be applicable, yes. But, not necessarily.
So, to clarify – do you need to have 2 alternative ways to authenticate? Or do you actually not to block certain visitors from seeing your standard login page?

Don Nandipinto
Guest

Hi Eugen,

Yes, thanks for the prompt response.
I do need to have 2 alternative ways to authenticate, one is for user to login from normal browser, and another one is to allow external application, let’s a mobile app with a Mobile View opening the web page — the later, the don’t want to see the login page.

I tried to add custom Authentication Filter to WebSecurityConfigurerAdapter but doesn’t seem to be firing.
I’d like to paste my code here, but it seems messy.

Eugen Paraschiv
Guest

Hey Don – sure, happy to help. Yes, if you need 2 alternative authentication mechanisms, this is a good place to start. As for the code, yeah, email is definitely better, or a Github issues right on the repo of this article.
Cheers,
Eugen.

Don Nandipinto
Guest

Hi Eegun,

I’ll have a look at your sample codes at GitHub, hopefully I can get a better picture about the idea. Will likely get back to you with another questions 🙂

Cheers.

wpDiscuz