Spring Web Security
Here is how we do it step-by-step. Code are written in Groovy, not JAVA.
Dependency
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' } }
Extends WebSecurityConfigurerAdapter
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); } }
Login Form
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>
Logout Form
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>
Mapping URL to Thymeleaf Template
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…)
WebMvcConfigurer
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") } }
@Controller For Controller Class
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" } }
userDetailsService
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.