Code ví dụ Spring Boot Multiple Login Page (Form Login)
(Xem lại: Spring MVC – Security dùng nhiều trang login (XML Config))
Các công nghệ sử dụng:
- Spring Boot 2.0.1
- Maven
- JDK 1.8
- Eclipse + Spring Tool Suite
Tạo Spring Boot Project
Cấu trúc Project
File Controller:
package stackjava.com.sbmultipleformlogin.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller public class BaseController { @RequestMapping("/login1") public String login1(@RequestParam(required = false) String message, final Model model) { if (message != null && !message.isEmpty()) { if (message.equals("logout")) { model.addAttribute("message", "Logout!"); } if (message.equals("error")) { model.addAttribute("message", "Login Failed!"); } } return "login1"; } @RequestMapping("/login2") public String login2(@RequestParam(required = false) String message, final Model model) { if (message != null && !message.isEmpty()) { if (message.equals("logout")) { model.addAttribute("message", "Logout!"); } if (message.equals("error")) { model.addAttribute("message", "Login Failed!"); } } return "login2"; } @RequestMapping("/") public String index() { return "index"; } @RequestMapping("/admin/admin") public String admin() { return "admin"; } @RequestMapping("/user") public String user() { return "user"; } @RequestMapping("/403") public String accessDenied403() { return "403"; } }
Các file config security:
package stackjava.com.sbmultipleformlogin.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.crypto.bcrypt.BCryptPasswordEncoder; @Configuration @EnableWebSecurity @Order(1) public class AdminSecurityConfig extends WebSecurityConfigurerAdapter { @Bean public BCryptPasswordEncoder passwordEncoder() { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); return bCryptPasswordEncoder; } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { // account kai/admin1234 auth.inMemoryAuthentication().passwordEncoder(passwordEncoder()).withUser("kai") .password("$2a$04$PQtbtSAHZgBXLN.K/gZ3/eomQtZkB8R7x03KqZJxOTAbLRkxD9jgC").roles("ADMIN"); // account sena/123456 auth.inMemoryAuthentication().passwordEncoder(passwordEncoder()).withUser("sena") .password("$2a$04$Q2Cq0k57zf2Vs/n3JXwzmerql9RzElr.J7aQd3/Sq0fw/BdDFPAj.").roles("USER"); // auth.inMemoryAuthentication().passwordEncoder(NoOpPasswordEncoder.getInstance()).withUser("sena").password("123456").roles("USER"); } protected void configure(HttpSecurity http) throws Exception { // Cấu hình security/form login cho url /admin/** // Cấu hình cho Login Form. http.antMatcher("/admin/**").authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN").and().formLogin()// .loginProcessingUrl("/admin/j_spring_security_login")// .loginPage("/login1")// .defaultSuccessUrl("/admin/admin")// .failureUrl("/login1?message=error")// .usernameParameter("username")// .passwordParameter("password") .and().exceptionHandling().accessDeniedPage("/403") .and().logout().logoutUrl("/admin/j_spring_security_logout").logoutSuccessUrl("/login1?message=logout"); } }
package stackjava.com.sbmultipleformlogin.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.crypto.bcrypt.BCryptPasswordEncoder; @Configuration @EnableWebSecurity @Order(2) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public BCryptPasswordEncoder passwordEncoder() { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); return bCryptPasswordEncoder; } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { // account kai/123456 auth.inMemoryAuthentication().passwordEncoder(passwordEncoder()).withUser("kai") .password("$2a$04$Q2Cq0k57zf2Vs/n3JXwzmerql9RzElr.J7aQd3/Sq0fw/BdDFPAj.").roles("ADMIN"); // account sena/123456 auth.inMemoryAuthentication().passwordEncoder(passwordEncoder()).withUser("sena") .password("$2a$04$Q2Cq0k57zf2Vs/n3JXwzmerql9RzElr.J7aQd3/Sq0fw/BdDFPAj.").roles("USER"); // auth.inMemoryAuthentication().passwordEncoder(NoOpPasswordEncoder.getInstance()).withUser("sena").password("123456").roles("USER"); } @Override protected void configure(HttpSecurity http) throws Exception { // Cấu hình cho Login Form. http.authorizeRequests().antMatchers("/user/**").hasRole("USER").and() .formLogin()// .loginProcessingUrl("/j_spring_security_login")// .loginPage("/login2")// .defaultSuccessUrl("/user")// .failureUrl("/login2?message=error")// .usernameParameter("username").passwordParameter("password") .and().exceptionHandling().accessDeniedPage("/403") .and().logout().logoutUrl("/j_spring_security_logout").logoutSuccessUrl("/login2?message=logout"); } }
Ở đây mình sử dụng 2 file config security để cấu hình cho 2 dạng url khác nhau.
File AdminSecurityConfig.java cấu hình security cho đường dẫn /admin/**; File SecurityConfig.java cấu hình cho các đường dẫn còn lại.
Để sử dụng nhiều file cấu hình security khác nhau ta extends class WebSecurityConfigurerAdapter và sử dụng annotation @Order ở đầu mỗi class. Tham số trong annotation @Order sẽ thể hiện thứ tự được filter khi thực hiện security. Ví dụ AdminSecurityConfig.java được đánh dấu là @Order(1) còn SecurityConfig.java được đánh dấu là @Order(2) nên khi thực hiện filter security nó sẽ kiểm tra bằng class AdminSecurityConfig.java trước.
*Lưu ý: class AdminSecurityConfig.java chỉ áp dụng cho url /admin/** nên ta sẽ dùng http.antMatcher(“/admin/**”) (antMatcher chứ không phải là antMatchers nhé) nó tương đương với thẻ <http pattern=“/admin/**”>
Các file views:
<!DOCTYPE html> <html> <head> <title>Spring Boot Security</title> </head> <body> <h2>Demo Spring Boot - Multiple Form Login</h2> <a th:href="@{/admin/admin}">admin page</a> <br /> <a th:href="@{/user}">user page</a> </body> </html>
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Spring Boot Security</title> </head> <body> <h2>Form Login1</h2> <form name='login-form' th:action="@{/admin/j_spring_security_login}" method='POST'> <table> <tr> <td>Username:</td> <td><input type='text' name='username' value=''></td> </tr> <tr> <td>Password:</td> <td><input type='password' name='password' /></td> </tr> <tr> <td><input name="submit" type="submit" value="submit" /></td> </tr> </table> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> </form> </body> </html>
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Spring Boot Security</title> </head> <body> <h2>Form Login2</h2> <form name='login-form' th:action="@{/j_spring_security_login}" method='POST'> <table> <tr> <td>Username:</td> <td><input type='text' name='username' value=''></td> </tr> <tr> <td>Password:</td> <td><input type='password' name='password' /></td> </tr> <tr> <td><input name="submit" type="submit" value="submit" /></td> </tr> </table> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> </form> </body> </html>
<!DOCTYPE html> <html> <head> <title>Spring Boot Security</title> </head> <body> <h2>User Page</h2> <h3> Welcome : <span th:utext="${#request.userPrincipal.name}"></span> </h3> <br/> <form th:action="@{/j_spring_security_logout}" method="post"> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /> <input type="submit" value="Logout" /> </form> </body> </html>
<!DOCTYPE html> <html> <head> <title>Spring Boot Security</title> </head> <body> <h2>Admin Page</h2> <h3> Welcome : <span th:utext="${#request.userPrincipal.name}"></span> </h3> <br/> <form th:action="@{/admin/j_spring_security_logout}" method="post"> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /> <input type="submit" value="Logout" /> </form> </body> </html>
<!DOCTYPE html> <html> <head> <title>Spring Boot Security</title> </head> <body> <h2>Access Denied - 403</h2> <p>You do not have permission to access this page</p> <a th:href="@{/}">Back To Home Page</a> </body> </html>
Demo:
Đăng nhập trang /admin/admin bằng tài khoản kai/123456 hoặc kai/admin1234 (hệ thống sẽ hiện trang login1)
Logout thì sẽ trở về trang login1, sau đấy mình đăng nhập bằng tài khoản sena/123456
Vì trang login1 sau khi login thành công sẽ chuyển tới trang /admin/admin mà tài khoản sena/123456 không có role admin nên sẽ không thể truy cập.
Truy cập trang /user bằng tài khoản sena/123456 và logout (sẽ trở về trang login2 chứ không phải là trang login1)
Code ví dụ Spring Boot Multiple Login Page (Form Login) stackjava.com
Okay, Done!
Download code ví dụ trên tại đây.
References:
https://docs.spring.io/spring-security/site/docs/5.0.0.RELEASE/reference/htmlsingle