====== 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. Spring Security Example
Invalid username and password.
You have been logged out.
===== 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.
===== 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.