Filter Security

Modify pom.xml as shown below.

<properties>
	<spring.version>5.1.5.RELEASE</spring.version>
	<spring.security.version>5.1.3.RELEASE</spring.security.version>
	<jdk.version>1.8</jdk.version>
</properties>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-web</artifactId>
	<version>${spring.security.version}</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-taglibs</artifactId>
	<version>${spring.security.version}</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-config</artifactId>
	<version>${spring.security.version}</version>
</dependency>

Create authorities table to java account

You need the users and the authorities table to use Spring Security.
You can use the member instead of the users table.
Create a authorities table and insert test data as follows.

CREATE TABLE authorities (
  email VARCHAR2(60) NOT NULL,
  authority VARCHAR2(20) NOT NULL,
  CONSTRAINT fk_authorities FOREIGN KEY(email) REFERENCES member(email)
);

CREATE UNIQUE INDEX ix_authorities ON authorities(email, authority); 

INSERT INTO member VALUES ('johndoe@gmail.org','1111','John Doe','010-1111-1111');
INSERT INTO member VALUES ('janedoe@gmail.org','1111','Jane Doe','010-1111-2222');

INSERT INTO authorities VALUES ('johndoe@gmail.org','ROLE_USER');
INSERT INTO authorities VALUES ('johndoe@gmail.org','ROLE_ADMIN');
INSERT INTO authorities VALUES ('janedoe@gmail.org','ROLE_USER');

commit;

ROLE_USER is the normal user privileges, and ROLE_ADMIN is the administrator privileges.
John Doe has both normal user and administrator privileges, and Jane Doe has only normal user privileges.

Filter Security

Create a spring configuration file for Spring Security only in the /WEB-INF folder named security.xml (no name restriction) as shown below.

security.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/security
		http://www.springframework.org/schema/security/spring-security.xsd">

	<http>
		<intercept-url pattern="/users/bye_confirm" access="permitAll" />
		<intercept-url pattern="/users/welcome" access="permitAll" />
		<intercept-url pattern="/users/signUp" access="permitAll" />
		<intercept-url pattern="/users/login" access="permitAll" />
		<intercept-url pattern="/images/**" access="permitAll" />
		<intercept-url pattern="/css/**" access="permitAll" />
		<intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')"/>
		<intercept-url pattern="/users/**" access="hasAnyRole('ROLE_ADMIN','ROLE_USER')"/>
		<intercept-url pattern="/bbs/**" access="hasAnyRole('ROLE_ADMIN','ROLE_USER')" />
		
		<form-login 
			login-page="/users/login" 
			authentication-failure-url="/users/login?error=1" 
			default-target-url="/bbs/list?boardCd=free&amp;page=1" />
		
		<logout 
			logout-success-url="/users/login" 
			invalidate-session="true"  />
		
	</http>

	<authentication-manager>
		<authentication-provider>
			<jdbc-user-service 
				data-source-ref="dataSource"
				users-by-username-query="SELECT email as username,passwd as password,1 as enabled 
					FROM member WHERE email = ?"
				authorities-by-username-query="SELECT email as username,authority 
					FROM authorities WHERE email = ?" />
		</authentication-provider>
	</authentication-manager>

</beans:beans>

Since Spring Security 4, the default value of the use-expressions attribute in the http element is 'true' and can be omitted.
The default value for login-page is '/login'.
The default value for login-processing-url is '/login' of POST method.
The default value for username-parameter is 'username'.
The default value for password-parameter is 'password'.
The default value for authentication-failure-url is '/login?error=1'.

If you want to use a non-default user login page such as '/users/ login' and go back to the login page in case of a login failure, you must specify the login-page and authentication-failure-url attributes.
In addition, <intercept-url pattern="/users/login" access="permitAll" /> is required within http element.

You can create a Spring configuration file with the specific contents of the spring-bbs-servlet.xml.
Create the applicationContext.xml in the /WEB-INF folder as shown below.

applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="configLocation" value="classpath:net/java_school/mybatis/Configuration.xml" />
	</bean>
	
	<bean id="dataSource" 
		class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
		<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
		<property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:XE"/>
		<property name="username" value="java"/>
		<property name="password" value="school"/>
		<property name="maxActive" value="100"/>
		<property name="maxWait" value="1000"/>
		<property name="poolPreparedStatements" value="true"/>
		<property name="defaultAutoCommit" value="true"/>
		<property name="validationQuery" value=" SELECT 1 FROM DUAL" />
	</bean>
    
	<bean id="multipartResolver"
		class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
		p:maxUploadSize="104857600" p:maxInMemorySize="10485760" />
	
</beans>

There is a spring configuration file for spring security only, but there is something to be set in spring-bbs-servlet.xml during Spring security configuration.
Modify the spring-bbs-servlet.xml file as follows:

spring-bbs-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:security="http://www.springframework.org/schema/security" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/security
		http://www.springframework.org/schema/security/spring-security.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/mvc
		http://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://mybatis.org/schema/mybatis-spring 
		http://mybatis.org/schema/mybatis-spring.xsd">
		
	<security:global-method-security pre-post-annotations="enabled" />
	
	<!-- omit  -->
	
</beans>	

Modify web.xml as follows.

web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
 Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->


<web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  version="3.0"
  metadata-complete="true">
    
	<display-name>Spring BBS</display-name>
	
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			/WEB-INF/applicationContext.xml
			/WEB-INF/security.xml
		</param-value>
	</context-param>

	<listener>
		<listener-class>
		org.springframework.web.context.ContextLoaderListener
		</listener-class>	
	</listener>
	
	<listener>
		<listener-class>
		org.springframework.security.web.session.HttpSessionEventPublisher
		</listener-class>
	</listener>
	
	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	
	<filter>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>
		org.springframework.web.filter.DelegatingFilterProxy
		</filter-class>
	</filter>
	
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>	
	
	<servlet>
		<servlet-name>spring-bbs</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>spring-bbs</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<error-page>
		<error-code>403</error-code>
		<location>/WEB-INF/views/noAuthority.jsp</location>
	</error-page>
	
</web-app>

Remove the login and logout methods from UsersController.java.

UsersController.java
/*
@RequestMapping(value="/login", method=RequestMethod.POST)
public String login(String email, String passwd, HttpSession session) {
	User user = userService.login(email, passwd);
	if (user != null) {
		session.setAttribute(WebContants.USER_KEY, user);
		return "redirect:/users/changePasswd";
	} else {
		return "redirect:/users/login";
	}
}

@RequestMapping(value="/logout", method=RequestMethod.GET)
public String logout(HttpSession session) {
	session.removeAttribute(WebContants.USER_KEY);
	return "redirect:/users/login";
}
*/

Modify login.jsp as follows.

/WEB-INF/views/users/login.jsp
<c:if test="${not empty param.error }">
	<h2>${SPRING_SECURITY_LAST_EXCEPTION.message }</h2>
</c:if>
<c:url var="loginUrl" value="/login" />
<form id="loginForm" action="${loginUrl }" method="post">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<table>
<tr>
	<td style="width: 200px;">Email</td>
	<td style="width: 390px"><input type="text" name="username" style="width: 99%;" /></td>
</tr>
<tr>
	<td>Password</td>
	<td><input type="password" name="password" style="width: 99%;" /></td>
</tr>
</table>

If you try to log in without <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />, you will see a blank screen.
This is because Spring Security 's CSRF prevention works.
Starting with 'Spring Security 4', CSRF prevention is enabled by default.
Therefore, you must include the CSRF token in the PATCH, POST, PUT, and DELETE requests.
If you are using Springform tags, you do not need to add the CSRF token parameter because the token parameter is added automatically.

Modify header.jsp as follows.

/WEB-INF/views/inc/header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<h1 style="float: left;width: 150px;"><a href="../"><img src="../images/ci.gif" alt="java-school logo" /></a></h1>
<div id="memberMenu" style="float: right;position: relative;top: 7px;">
<security:authorize access="hasAnyRole('ROLE_USER','ROLE_ADMIN')">
	<security:authentication property="principal.username" var="check" />
</security:authorize>
<c:choose>
	<c:when test="${empty check}">
		<input type="button" value="Login" onclick="location.href='/users/login'" />
		<input type="button" value="SignUp" onclick="location.href='/users/signUp'" />
	</c:when>
	<c:otherwise>
		<input type="button" value="Logout" id="logout" />
		<input type="button" value="Modify Account" onclick="location.href='/users/editAccount'" />
	</c:otherwise>
</c:choose>
</div>

<form id="logoutForm" action="/logout" method="post" style="display:none">
	<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form>
<script type="text/javascript" src="/resources/js/jquery-3.0.0.min.js"></script>

<script>
$(document).ready(function() {
	$('#logout').click(function() {
		$('#logoutForm').submit();
		return false;
  	});
});
</script>

Create noAuthority.jsp

Create noAuthority.jsp as shown belows.

/WEB-INF/views/noAuthority.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>403</title>
</head>
<body>
Insufficient privileges.
</body>
</html>

Modify sources

UserMapper.xml
<insert id="insertAuthority">
    INSERT INTO authorities VALUES (#{email}, #{authority})
</insert>

<delete id="deleteAuthority">
    DELETE FROM authorities WHERE email = #{email}	
</delete>
UserMapper.java
public void insertAuthority(@Param("email") String email, @Param("authority") String authority);
  
public void deleteAuthority(@Param("email") String email);
UserService.java
public void addAuthority(String email, String authority);
UserServiceImpl.java
@Override
public void addAuthority(String email, String authority) {
    userMapper.insertAuthority(email, authority);
}

@Override
public void bye(User user) {
    userMapper.deleteAuthority(user.getEmail());
    userMapper.delete(user);
}
UsersController
//omit..


import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import java.security.Principal;
import org.springframework.ui.Model;


//omit..

@RequestMapping(value="/signUp", method=RequestMethod.POST)
public String signUp(User user) {

    String authority = "ROLE_USER";

    userService.addUser(user);
    userService.addAuthority(user.getEmail(), authority);

    return "redirect:/users/welcome";
}

@RequestMapping(value="/editAccount", method=RequestMethod.GET)
public String editAccount(Principal principal, Model model) {
    User user = userService.getUser(principal.getName());
    model.addAttribute(WebContants.USER_KEY, user);

    return "users/editAccount";
}

@RequestMapping(value="/editAccount", method=RequestMethod.POST)
public String editAccount(User user, Principal principal) {
	
    user.setEmail(principal.getName());

    int check = userService.editAccount(user);
    if (check < 1) {
        throw new RuntimeException(WebContants.EDIT_ACCOUNT_FAIL);
    } 

    return "redirect:/users/changePasswd";
	
}

@RequestMapping(value="/changePasswd", method=RequestMethod.GET)
public String changePasswd(Principal principal, Model model) {
    User user = userService.getUser(principal.getName());

    model.addAttribute(WebContants.USER_KEY, user);

    return "users/changePasswd";
}

@RequestMapping(value="/changePasswd", method=RequestMethod.POST)
public String changePasswd(String currentPasswd, String newPasswd, Principal principal) {
	
    int check = userService.changePasswd(currentPasswd,newPasswd, principal.getName());

    if (check < 1) {
        throw new RuntimeException(WebContants.CHANGE_PASSWORD_FAIL);
    }	

    return "redirect:/users/changePasswd_confirm";

}

@RequestMapping(value="/bye", method=RequestMethod.POST)
public String bye(String email, String passwd, HttpServletRequest req) 
        throws ServletException {

    User user = userService.login(email, passwd);
    userService.bye(user);
    req.logout();

    return "redirect:/users/bye_confirm";
}
BbsController.java
//omit..

import java.security.Principal;

//omit..

@RequestMapping(value="/write", method=RequestMethod.POST)
public String write(MultipartHttpServletRequest mpRequest, Principal principal) 
        throws Exception {

    //omit..
    Article article = new Article();
    article.setBoardCd(boardCd);
    article.setTitle(title);
    article.setContent(content);
    article.setEmail(principal.getName());
    
    boardService.addArticle(article);	
    
    //omit..
    
    int size = fileList.size();
    for (int i = 0; i < size; i++) {
        MultipartFile mpFile = fileList.get(i);
        AttachFile attachFile = new AttachFile();
        String filename = mpFile.getOriginalFilename();
        attachFile.setFilename(filename);
        attachFile.setFiletype(mpFile.getContentType());
        attachFile.setFilesize(mpFile.getSize());
        attachFile.setArticleNo(article.getArticleNo());
        attachFile.setEmail(principal.getName());
        boardService.addAttachFile(attachFile);
    }

    //omit..
}

@RequestMapping(value="/addComment", method=RequestMethod.POST)
public String addComment(Integer articleNo, 
        String boardCd, 
        Integer page, 
        String searchWord, 
        String memo,
        Principal principal) throws Exception {
		
    Comment comment = new Comment();
    comment.setArticleNo(articleNo);
    comment.setEmail(principal.getName());
    comment.setMemo(memo);

    //omit..
}

For requests to upload files, you must add a CSRF token to the request as a query string.
(<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> does not work. this is true even if you are using Spring form tags)

Open the write.jsp and modify.jsp files.
Remove <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />.
Modify the form's action as follows.

write.jsp
<sf:form action="write?${_csrf.parameterName}=${_csrf.token}" method="post" ...
modify.jsp
<sf:form action="modify?${_csrf.parameterName}=${_csrf.token}" method="post" ...

Test

Now that the library has been added, build again.
After restart Tomcat, visit http://localhost:8080/list?boardCd=smalltalk&page=1.
You will see the login page.
Log in as username: janedoe@gmail.org, password: 1111.
If login is successful, you can see bulletin board screen.

Visit http://localhost:8080/admin.
Jane Doe is denied access because she has only user privilege, and She sees noAuthority.jsp.

References