springboot:spring_web_security

Spring Web Security

Here is how we do it step-by-step. Code are written in Groovy, not JAVA.

For build.gradle, add spring-boot-starter-security in the dependencies.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.security:spring-security-test'
    implementation 'org.codehaus.groovy:groovy'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

Create a class that extends WebSecurityConfigurerAdapter with @Configuration, and @EnableWebSecurity. Override the method configure. Use anyMatchers to match the URLs, and use permitAll, hasRole(“…”), or has AnyRole(“…”,“…”,…) to control the access rights.

For login, and logout, use formLogin(), and logout() for the setting.

Here “/”, “/home”, “/myhome”, “/login”, and “/logout” can be access without authorize, and “/hello” require a user with “ROLE_USER” to access.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "/home", "/myhome").permitAll()
//                .antMatchers("/hello").hasRole("USER")
                .antMatchers("/hello").hasAnyRole("USER")
                .anyRequest().authenticated()
        http.formLogin()
                .loginPage("/login")
                .permitAll()
        http.logout()
                .permitAll();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user =
                User.withDefaultPasswordEncoder()
                        .username("user")
                        .password("password")
                        .roles("USER")
                        .build();

        return new InMemoryUserDetailsManager(user);
    }
}

For setting up the login page, we use thymeleaf in this example. Create a templates directory under the resources directory, and create a file call login.html. Naming is important here, we will talk more in WebMvcConfigurer. The following form is a basic HTTP POST form, and it is copy from Spring site.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
    <title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
    Invalid username and password.
</div>
<div th:if="${param.logout}">
    You have been logged out.
</div>
<form th:action="@{/login}" method="post">
    <div><label> User Name : <input type="text" name="username"/> </label></div>
    <div><label> Password: <input type="password" name="password"/> </label></div>
    <div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>

Any basic HTTP POST form in anywhere will do. By default, Spring Web Security only take post form for logout. It can be change though.

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

There are two way to do so, and they are WebMvcConfigurer, and declaring @Controller with @…Mapping for a controller class. (You can also do it in XML…)

Here we will talk about why naming of the template is important. It is because we will use the name in setViewName(…) method. Create a class that implements WebMvcConfigurer with @Configuration

Note how to map the login page to login.html [registry.addViewController(“/login”).setViewName(“login”)]

@Configuration
class MvcConfig implements WebMvcConfigurer {
    @Override
    void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home").setViewName("home")
        registry.addViewController("/").setViewName("home")
        registry.addViewController("/hello").setViewName("hello")
        registry.addViewController("/login").setViewName("login")
    }
}

Create a class with @Controller. Create a method with @…Mapping(“/PATH_NAME”) that return a String. The string that return will automatically map to the template with the same name (without the .html path)

import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping

@Controller
class PageController {
    @GetMapping("/myhome")
    public String myHome() {
        return "home"
    }
}

Up to this point, we need to add user to out website. A lazy way for testing/debugging is to create a bean UserDetailsService that uses InMemoryUserDetailsManager. Spring will take it, and integrate it with our site. This is not mean to be use in production. To make this topic simple, we will talk about how to do it in production in other topic.

Here we have create a user with login:user, and password:password, and it has a role “ROLE_USER”. role is useful for us to decide how to access to which URLs or methods in controller/service. A user can have multiple roles.

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user =
                User.withDefaultPasswordEncoder()
                        .username("user")
                        .password("password")
                        .roles("ROLE_USER")
                        .build();

        return new InMemoryUserDetailsManager(user);
    }

Now that when we access the page that require user to be logged in, the site will automatically redirect the user to the login page. If user does not have the access right to access the page, a http 403 will response.

  • springboot/spring_web_security.txt
  • Last modified: 2020/05/25 10:21
  • by chongtin