- Spring 5.0 Cookbook
- Sherwin John Calleja Tragura
- 949字
- 2021-07-08 10:16:29
How to do it...
This recipe will provide proof that Spring 5 can work with the current Spring Security 4.2.2 without encountering any conflicts:
- To integrate the Spring Security 4.2.2 framework, include the following Maven dependencies into the Maven repository:
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.2.2.BUILD-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.2.2.BUILD-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>4.2.2.BUILD-SNAPSHOT</version> </dependency>
- Reuse the SpringWebInitializer, SpringDispatcherConfig, SpringDbConfig, and SpringContextConfig. Set their @ComponentScan base packages to org.packt.secured.mvc. The following is the final directory structure with these three main @Configuration files:
- Now, create another context definition which will implement the security model consisting of the Spring Security 4.2.2 APIs. Let us start with the creation of AppSecurityConfig inside org.packt.secured.mvc.core, which lists all the basic security rules for our MVC application:
@Configuration @EnableWebSecurity public class AppSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("sjctrags").password("sjctrags") .roles("USER"); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/login*").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login.html") .defaultSuccessUrl("/deptform.html") .failureUrl("/login.html?error=true") .and().logout().logoutUrl("/logout.html") .logoutSuccessUrl("/after_logout.html"); http.csrf().disable(); } @Override public void configure(WebSecurity web) throws Exception { web .ignoring() .antMatchers("/resources/**") .antMatchers("/css/**") .antMatchers("/js/**") .antMatchers("/image/**"); } }
The difference between the Spring MVC context definition and the Spring Security one is the presence of @EnableWebSecurity in the latter. This annotation enables security in the application through the creation of a servlet named springSecurityFilterChain, which is responsible for all the core and extra security features. To complete the @Configuration, the class must extend org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter to override the needed security-related methods.
- To apply AppSecurityConfig, import this context to our application context SpringContextConfig through the @Import annotation:
@Import(value = { AppSecurityConfig.class }) @Configuration @EnableWebMvc @ComponentScan(basePackages = "org.packt.secured.mvc") public class SpringContextConfig { }
- Then, create SpringSecurityInitializer inside the package org.packt.secured.mvc.core that will register the delegatingFilterproxy filter to be used by the springSecurityFilterChain previously created. So far, this strategy is the one working when compared to the direct Filter.Registration of the ServletContext:
public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer { }
- At this point, it is time to implement our MVC application starting with the model objects. Reuse Department and Employee from the ch03-jdbc project. Drop these files inside org.packt.secured.mvc.model.data.
- Reuse also our form backing objects from ch03-jdbc, namely DepartmentForm and EmployeeForm. Place them inside the package org.packt.secured.mvc.model.data.form.
- For the DAO layer, let us consider implementing a new DepartmentDao, which contains the following method signatures:
public interface DepartmentDao { public List<Department> getDepartments(); public Department getDepartmentData(Integer id); public void addDepartmentBySJI(Department dept); public void addDepartmentByJT(Department dept); public void updateDepartment(Department dept); public void delDepartment(Integer deptId); }
- Place this in the org.packt.secured.mvc.dao package.
- Implement DepartmentDaoImpl by using SimpleJdbcInsert and JdbcTemplate from ch03-jdbc, and place them in the org.packt.secured.mvc.dao.impl package. Be cautious with the use of SimpleJdbcInsert's addDepartmentBySJI(), which gives the HTTP status 500 during successive calls to the jdbcInsert.withTableName("department") line.
- Implement a new DepartmentController which contains create, delete, update record, and query database transactions after a successful login:
@Controller public class DepartmentController { @Autowired private DepartmentService departmentServiceImpl; @RequestMapping("/deptform.html") public String initForm(Model model){ DepartmentForm departmentForm = new DepartmentForm(); model.addAttribute("departmentForm", departmentForm); return "dept_form"; } @RequestMapping(value={"/deptform.html"}, method=RequestMethod.POST) public String submitForm(Model model, @ModelAttribute("departmentForm") DepartmentForm departmentForm){ departmentServiceImpl.addDepartment(departmentForm); model.addAttribute("departments", departmentServiceImpl.readDepartments()); return "dept_result"; } @RequestMapping("/deldept.html/{deptId}") public String deleteRecord(Model model, @PathVariable("deptId") Integer deptId){ departmentServiceImpl.removeDepartment(deptId); model.addAttribute("departments", departmentServiceImpl.readDepartments()); return "dept_result"; } @RequestMapping("/updatedept.html/{id}") public String updateRecord(Model model, @PathVariable("id") Integer id){ Department dept = departmentServiceImpl.getDeptId(id); DepartmentForm departmentForm = new DepartmentForm(); departmentForm.setDeptId(dept.getDeptId()); departmentForm.setName(dept.getName()); model.addAttribute("departmentForm", departmentForm); return "dept_form"; } @RequestMapping(value=/updatedept.html/{id}", method=RequestMethod.POST) public String updateRecordSubmit(Model model, @ModelAttribute("departmentForm") DepartmentForm departmentForm, @PathVariable("id") Integer id ){ departmentServiceImpl.updateDepartment( departmentForm, id); model.addAttribute("departments", departmentServiceImpl.readDepartments()); return "dept_result"; } }
After a successful authentication, the AppSecurityConfig will redirect to /deptform.html as the default success page.
- Create a LoginController that will provide all the handler methods of the AppSecurityConfig:
@Controller public class LoginController { @RequestMapping(value="/login.html", method=RequestMethod.GET) public String login(@RequestParam(name="error", required=false) String error, Model model) { try { if (error.equalsIgnoreCase("true")) { String errorMsg = "Login Error"; model.addAttribute("errorMsg", errorMsg); }else{ model.addAttribute("errorMsg", error); } } catch (NullPointerException e) { return "login_form"; } return "login_form"; } @RequestMapping("/logout.html") public String logout() { return "logout_form"; } @RequestMapping("/after_logout.html") public String afterLogout() { return "after_logout_form"; } @RequestMapping("logerr.html") public String logerr() { return "logerr_form"; } }
The custom login page /login.html must be configured and recognized by the org.springframework.security.config.annotation.web.builders.HttpSecurity API. Likewise, the /logout.html must be the AppSecurityConfig's custom logout entry which will redirect to /after_logout.html after a successful logout.
- Create the views for the /logout.html, /after_logout.html and /login.html. The login transaction must be using the POST HTTP method:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title><spring:message code="login_title" /></title> </head> <body> <c:if test="${ not empty errorMsg }"> <em><c:out value='${ errorMsg }'/></em><br/> </c:if> <form action="<c:url value='/login.html' />" method="POST"> <spring:message code="user" /> <input type="text" name="username" /><br/> <spring:message code="password" /> <input type="text" name="password" /><br/> <input type="submit" value="Login" /> </form> </body> </html>
- Next, create the physical views for the DepartmentController's /deptform.html form and the /dept_result.html result view pages. The form view must be implemented this way:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title><spring:message code="dept_title" /></title> </head> <body> <c:if test="${ not empty username }"> Username is <em><c:out value='${ username}'/> </em><br/> Password is <em><c:out value='${ password }'/> </em><br/> Role(s) is/are: <em><c:out value='userRole'/> </em><br/> </c:if> <form:form modelAttribute="departmentForm" method="POST"> <spring:message code="dept_id" /> <form:input path="deptId" /><br/> <spring:message code="dept_name" /> <form:input path="name" /><br/> <input type="submit" value="Add Department" /> </form:form> </body> </html>
While the result view looks like this snippet:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title><spring:message code="dept_title" /></title> </head> <body> <h1><spring:message code="dept_list" /></h1> <table border="1"> <c:forEach var="dept" items="${ departments }"> <tr> <td>${ dept.deptId }</td> <td>${ dept.name }</td> <c:url var="delUrl" value="/deldept.html/${dept.deptId}" /> <td> <a href="<c:out value='${ delUrl }'/>">DELETE</a></td> <c:url var="updateUrl" value="/updatedept.html/${dept.id}" /> <td><a href="<c:out value='${ updateUrl }'/>">UPDATE</a> </td> </tr> </c:forEach> </table> <br> <a href="<c:url value='/deptform.html'/>"> Add More Department</a> <br/> <em>This is for CSRF Logout</em> <c:url var="logoutUrl" value="/logout.html"/> <form action="${logoutUrl}" method="post"> <input type="submit" value="Log out" /> </form> </body> </html>
- Reuse and update the view.properties, messages_en_US.properties configuration for the view mappings and view label bundles, respectively. Also, reuse jdbc.properties for the MySQL connection details. Place all these files in the src\main\resources\config folder.
- clean, build, and deploy. Start the application through https://localhost:8443/ch04/login.html. Using the user and password registered in the org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder of AppSecurityConfig context, proceed adding a new department record and try viewing all the records stored. After clicking logout, you will be redirected to the logout success page:
- After the logout success page, any controller-defined URL will always redirect users to /login.html view; otherwise, a HTTP Status 404 will be issued.
- Try running the application using HTTP at port 8080 and the security model will always redirect it to /login.html, causing HTTP Status 404, because HTTP and port 8080 is not yet mapped to HTTPS and port 8483.