DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Component Tests for Spring Cloud Microservices
  • Authentication With Remote LDAP Server in Spring WebFlux
  • Authentication With Remote LDAP Server in Spring Web MVC
  • How to Implement Two-Factor Authentication in A Spring Boot OAuth Server? Part 2: Under the Hood

Trending

  • Introducing Graph Concepts in Java With Eclipse JNoSQL, Part 3: Understanding Janus
  • Scaling Azure Microservices for Holiday Peak Traffic Using Automated CI/CD Pipelines and Cost Optimization
  • Useful System Table Queries in Relational Databases
  • Bridging UI, DevOps, and AI: A Full-Stack Engineer’s Approach to Resilient Systems
  1. DZone
  2. Coding
  3. Frameworks
  4. Example of Multiple Login Pages With Spring Security and Spring Boot

Example of Multiple Login Pages With Spring Security and Spring Boot

Read on to learn how to create a secure, Java-based login platform using the Spring Security and Spring Boot frameworks.

By 
Bartłomiej Słota user avatar
Bartłomiej Słota
·
Jul. 10, 17 · Tutorial
Likes (14)
Comment
Save
Tweet
Share
68.0K Views

Join the DZone community and get the full member experience.

Join For Free

I just finished preparing a Spring Security configuration for a Zuul proxy in my company when a new requirement in this area came in from the business. We needed to have different login pages for different URLs being accessed within the same application. I'm not a front-end guy, so my first thought was to enhance my existing security config. I found this topic interesting enough to investigate broader possibilities of defining separate security constraints for different URL path patterns. In this article, I will describe an example of how to achieve this and how to test your configuration using Spring Boot and Thymeleaf as a templating engine. You can find my complete project here. I will mainly focus on form-based login, but at the end, you will also see how to provide various HTTP security types within a single application. Enjoy!

Imagine we have two home pages, that should be accessible under following paths: /regular/home and /special/home. We would like to have them secured with corresponding login forms: /regular/login, and /special/login. By default, Thymeleaf templates are supposed to be located in the /templates directory:

├───src
│   ├───main
│   │   ├───java
│   │   │   └───com
│   │   │       └───bslota
│   │   │           │   MultiLoginApplication.java
│   │   │           │
│   │   │           └───config
│   │   │                   MvcConfig.java
│   │   │                   SecurityConfig.java
│   │   │
│   │   └───resources
│   │       │   application.yml
│   │       │
│   │       ├───static
│   │       │   └───css
│   │       │           styles.css
│   │       │
│   │       └───templates
│   │           ├───regular
│   │           │       home.html
│   │           │       login.html
│   │           │
│   │           └───special
│   │                   home.html
│   │                   login.html
│   │
│   └───test
│       └───java
│           └───com
│               └───bslota
│                       WebSecurityTest.java
│
│   .gitignore
│   mvnw
│   mvnw.cmd
│   pom.xml

Now, apart from HTML templates, we need to configure our application, so that it resolves all views properly. To keep it simple, this should be enough for our example:

@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("regular/home");
        registry.addViewController("/regular/home").setViewName("regular/home");
        registry.addViewController("/special/home").setViewName("special/home");
        registry.addViewController("/regular/login").setViewName("regular/login");
        registry.addViewController("/special/login").setViewName("special/login");
    }
}

In order to introduce security into the application, we need to declare the following dependency:

<dependency>
  <groupid>org.springframework.boot</groupid>
  <artifactid>spring-boot-starter-security</artifactid>
</dependency>

Hey, hold on, we are not going to do anything without testing (I hope you already have a habit of writing tests, don't you?). In order to test our security configuration, we need this library:

<dependency>
  <groupid>org.springframework.boot</groupid>
  <artifactid>spring-boot-starter-test</artifactid>
  <scope>test</scope>
</dependency>

Right now, what we have out of the box due to the spring-boot-starter-security dependency on the classpath is:

  • HTTP-Basic security setup for all endpoint.
  • Randomly generated password logged into the console during startup for a user named 'user.'

As we mentioned at the beginning, we want to have a form-based login. Let's define our requirements in a couple of simple tests, then:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
public class WebSecurityTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testIfRegularHomePageIsSecured() throws Exception {
        final ResultActions resultActions = mockMvc.perform(get("/regular/home"));
        resultActions
                .andExpect(status().is3xxRedirection())
                .andExpect(redirectedUrl("http://localhost/regular/login"));
    }

    @Test
    @WithMockUser
    public void testIfLoggedUserHasAccessToRegularHomePage() throws Exception {
        final ResultActions resultActions = mockMvc.perform(get("/regular/home"));
        resultActions
                .andExpect(status().isOk())
                .andExpect(view().name("regular/home"));
    }

    @Test
    @WithMockUser
    public void testIfLoggedUserHasAccessToSpecialHomePage() throws Exception {
        final ResultActions resultActions = mockMvc.perform(get("/special/home"));
        resultActions
                .andExpect(status().isOk())
                .andExpect(view().name("special/home"));
    }

}

What you should know from this code is that we are creating a mock servlet environment (webEnvironment = SpringBootTest.WebEnvironment.MOCK) and auto-configuring MockMvc  (@AutoConfigureMockMvc) - a neat and powerful tool for web controller testing. With this setup you don't need to build and configure MockMvc  on your own - you can simply inject it as a regular bean dependency. Spring Boot never stops fascinating me.

We have three tests written here. The first one says:

given:

  anonymous user

when:

  trying to access /regular/home URL

then:

  I get 302 HTTP response

and:

  I'm redirected to /regular/login page

And the second one:

given:

  a principal with username "user" and password "password"

when:

  trying to access /regular/home URL

then:

  I get 200 HTTP response

and:

  I access regular/home view

The third one is almost the same as the second (the only difference is the URL that is being accessed with MockMvc). As long as when-then parts should be rather clear, the given part might be a bit confusing. In the first test we have not defined any security constraints, so the MockMvc call will be considered as one from an anonymous user. In the second test, though, we are using the @WithMockUser  annotation which emulates a call as if it is performed by an authenticated user. The SecurityContext within such an annotated test will contain an implementation of the authentication interface UsernamePasswordAuthenticationToken . With this annotation, you can adjust username, roles/authorities, and password as well. By default, we will have a principal with following details:

  • username: user
  • password: password
  • roles: USER 

It would be sufficient to stick to defaults here. Okay, we have some tests, but they fail. Sure they do! Here is what we need to do now:

  • Define AuthenticationManager (an in-memory one will be enough for this example) and create the appropriate user.
  • Configure HttpSecurity so that all resources are secured with form-based login under /regular/login.
 @Configuration
public class SecurityConfig {

    @Configuration
    public static class RegularSecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //@formatter:off
            http
                .authorizeRequests()
                    .antMatchers("/css/**").permitAll()
                    .anyRequest().authenticated()
                    .and()
                .formLogin()
                    .loginPage("/regular/login")
                    .defaultSuccessUrl("/regular/home")
                    .permitAll()
                    .and()
                .logout()
                    .logoutUrl("/regular/logout")
                    .permitAll();
            //@formatter:on
        }
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user")
                .password("password")
                .roles("USER");
    }
}

Good, now within theconfigureGlobal method, we configured an in-memory AuthenticationManager and created a user, as desired. We also declared a RegularSecurityConfig class that extends WebSecurityConfigurerAdapter and overrides configure( HttpSecurity ) method - this gives us the ability to change default pre-configured security behavior. Within the configure (HttpSecurity) method, we:

  • Decided to authenticate any request (apart from accessing CSS files - never mind that part).
  • Set the login page URL to /regular/login
  • Set the default success login URL to /regular/home - the place where users will be directed after authenticating, not having visited a secured page.
  • Set the logout URL to /regular/logout.

Of course, we don't need log out or default success URL details being defined to complete this example, but you will appreciate it when you download and run the whole app, I hope. All right, now all the tests have passed. What's next? Tests again:

    @Test
    public void testIfSpecialHomePageIsSecured() throws Exception {
        final ResultActions resultActions = mockMvc.perform(get("/special/home"));
        resultActions
                .andExpect(status().is3xxRedirection())
                .andExpect(redirectedUrl("http://localhost/special/login"));
    }

This test is very similar to the previously written one. We are checking whether the anonymous user is trying to access the /special/home page gets redirected to/special/login. Surely this test won't pass. The remedy is to add the following code into the SecurityConfig class:

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

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //@formatter:off
            http
                .antMatcher("/special/**")
                .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                .formLogin()
                    .loginPage("/special/login")
                    .defaultSuccessUrl("/special/home")
                    .permitAll()
                    .and()
                .logout()
                    .logoutUrl("/special/logout")
                    .permitAll();
            //@formatter:on
        }
    }

We just created yet another WebSecurityConfigurerAdapter extension. When you look at the body of the configure method, then you will see that it is pretty similar to the one that we had defined previously. The difference is that we have an antMatcher, that enables us to filter and apply these settings only for the URLs that begin with the /special/  prefix. And how does Spring know which configure method (and from which WebSecurityConfigurerAdapter  extension) should be invoked first? It is because of the @Order(1)  annotation.

Yep, we got it! We have implemented and tested our Spring MVC configuration with two separate login pages for different URLs being accessed. This example should show you how many possibilities Spring Security gives you. Do not think that this kind of configuration is limited to form-based login; you could declare HTTP-Basic security for all requests that concern /special/** URLs as well or set any security constraints you like for whatever path templates.

Spring Framework Spring Security Spring Boot

Published at DZone with permission of Bartłomiej Słota, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Component Tests for Spring Cloud Microservices
  • Authentication With Remote LDAP Server in Spring WebFlux
  • Authentication With Remote LDAP Server in Spring Web MVC
  • How to Implement Two-Factor Authentication in A Spring Boot OAuth Server? Part 2: Under the Hood

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!