Spring Security - Redirect based on User Roles

So far we’ve built a basic spring boot application, enabled spring security and built a basic login form. In the last lesson, we expanded on the first lesson by adding different user roles and the ability to show and hide front-end content based on these roles (User Roles and Thymeleaf Extras).

Today, we’ll be looking at redirecting users with different roles to different pages after they log in.

Source code for this example can be found on github:
codenerve-com/spring-security
An introduction to spring security. Contribute to codenerve-com/spring-security development by creating an account on GitHub.

Some files are already set up for you from the previous lesson: Spring Security – User Roles and Thymeleaf Extras. Please start here or check out the complete code from the link above.

Admin.html

Following on from our previous example, we have now created a new HTML file called Admin.html. This is the page we will redirect admins to when they log in.

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">

<head>
    <title>codenerve.com - Welcome!</title>
    <meta charset="UTF-8">
    <title>Admin</title>
    <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700" rel="stylesheet">
    <link rel="stylesheet" href="css/style.css">
</head>
    <body>
        <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>

        <div>
             Custom administrator page.
        </div>

        <br/>
        <form th:action="@{/logout}" method="post">
            <input type="submit" value="Sign Out"/>
        </form>
    </body>
</html>
admin.html

MvcConfig

Now, to serve the new admin.html page, we must add this page to our MvcConfig.

As with the previous examples, this is done by creating a class, extending WebMvcConfigurerAdapter and overriding the addViewControllers method. This time adding all the earlier pages of our app and the new admin page:

@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("login");
        registry.addViewController("/index").setViewName("index");
        registry.addViewController("/admin").setViewName("admin");
        registry.addViewController("/login").setViewName("login");
    }
}
MvcConfig.java

WebSecurityConfig

The Constructor

In order to decide what to do when different user roles login. We have created a new field of type AuthenticationSuccessHandler. We’re setting this new configuration bean via constructor injection.

configure method

This method is in charge of overriding and configuring HttpSecurity explicitly. From the last example, we have added two lines.

First, we’ve added a new antMatcher under the authorizeRequests section, and we’ve told spring security only to allow a user with the ‘ADMIN’ role access to all endpoints starting with ‘/admin’:

.antMatchers("/admin").hasRole("ADMIN")

Secondly, we’ve added our CustomAuthenticationSuccessHandler under the formLogin section to tell spring security to ask this CustomAuthenticationSuccessHandler what to do when a successful login occurs:

.successHandler(authenticationSuccessHandler)

configureGlobal method

The configureGlobal method is our in-memory registry of users. We’ve added two users. One with the primary ‘USER’ role and the other with the ‘ADMIN’ role.

Full example:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private AuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    public WebSecurityConfig(AuthenticationSuccessHandler authenticationSuccessHandler) {
        this.authenticationSuccessHandler = authenticationSuccessHandler;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers( "/css/**").permitAll()
                .antMatchers("/admin").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .successHandler(authenticationSuccessHandler)
                .permitAll()
                .and()
                .logout()
                .permitAll()
                .and().csrf().disable(); // we'll enable this in a later blog post
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("user").password("pass").roles("USER")
                .and()
                .withUser("admin").password("pass").roles("ADMIN");
    }
}
WebSecurityConfig.java

CustomAuthenticationSuccessHandler

As you can see from our sample code below this class implements springs AuthenticationSuccessHandler class and overrides the onAuthenticationSuccess method.

Once a user is successfully logged in, this method is called and within this method, the user’s role is checked. If the user’s role is admin we redirect to the ‘/admin’ HTTP endpoint otherwise we redirect them to the ‘/index’ endpoint.

At this point, our MvcConfig takes over and serves the correct HTML page based on the viewController we created previously.

@Configuration
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {


    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {

        Set<String> roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities());

        if (roles.contains("ROLE_ADMIN")) {
            httpServletResponse.sendRedirect("/admin");
        } else {
            httpServletResponse.sendRedirect("/index");
        }
    }
}
CustomAuthenticationSuccessHandler.java

Demo

To run the demo, open the Application class and right-click run. To start the example, port 8080 will need to be available on your machine. If it is not, you can change this default in the application.properties file using:

server.port=8081

Set this to whatever value you wish.

Testing

As always we have amended an added some additonal tests to conver a new functionality

@Test
@WithMockUser(roles = "USER")
public void loginWithRoleUserThenExpectAdminPageForbidden() throws Exception {
	mockMvc.perform(get("/admin"))
			.andExpect(status().isForbidden());
}

@Test
@WithMockUser(roles = "ADMIN")
public void loginWithRoleAdminThenExpectAdminContent() throws Exception {
	mockMvc.perform(get("/admin"))
			.andExpect(status().isOk())
			.andExpect(content().string(containsString("Custom administrator page.")));
}

@Test
public void loginWithRoleUserThenExpectIndexPageRedirect() throws Exception {
	FormLoginRequestBuilder login = formLogin()
			.user("user")
			.password("pass");

	mockMvc.perform(login)
			.andExpect(authenticated().withUsername("user"))
			.andExpect(redirectedUrl("/index"));
}

@Test
public void loginWithRoleAdminThenExpectAdminPageRedirect() throws Exception {
	FormLoginRequestBuilder login = formLogin()
			.user("admin")
			.password("pass");

	mockMvc.perform(login)
			.andExpect(authenticated().withUsername("admin"))
			.andExpect(redirectedUrl("/admin"));
}
ApplicationTests.java

Next

Next up, we will be covering spring security’s Cross Site Request Forgery (CSRF) protection.

Michael Whyte

Michael Whyte