java-school logo
Last Modified 2017.7.4

스프링 시큐리티 4 적용

pom.xml에서 스프링 시큐리티 버전 변경

pom.xml
<properties>
	<spring.version>4.3.9.RELEASE</spring.version>
	<spring.security.version>4.0.2.RELEASE</spring.security.version>
	<jdk.version>1.7</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>

src/main/webapp/WEB-INF/lib에 있는 라이브러리를 모두 지운다.
pom.xml 파일이 있는 루트 디렉터리로 이동해 다음을 수행한다.

mvn clean compile war:inplace

톰캣을 재실행한다.
로그인 화면으로 이동하여 로그인을 시도하면 빈 화면을 만나게 된다.
에러 메시지도 로그에 없다.
원인은 스프링 시큐리티 4의 CSRF 방지 기능이 작동하고 있기 때문이다.
스프링 시큐리티 4부터는 이 기능이 디폴트로 작동한다.
따라서 PATCH, POST, PUT, DELETE 요청에 CSRF 토큰을 포함해야 한다.
/users/login.jsp 파일을 열고 폼에 다음을 추가한다.

<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

스프링 폼 태그를 사용하는 폼(일반적으로 <form:form>, 우리의 프로젝트에서는 <sf:form>의 모양이다) 은 자동으로 토큰 파라미터가 추가되므로 위 코드가 필요 없다.

다시 로그인을 시도한다.
이번에는 /j_spring_security_check를 찾을 수 없다는 404 에러를 만나게 된다.

스프링 시큐리티 4에서는 http 자식요소 form-login의 속성 중 기본값이 변경된 속성

<form-login>에서 login-processing-url 속성의 기본값은 /j_spring_security_check에서 POST /login으로,
username-parameter 속성의 기본값은 j_username에서 username으로,
password-parameter 속성의 기본값은 J_password에서 password로,
authentication-failure-url 속성의 기본값은 /login?error=1으로 변경되었다.

스프링 시큐리티 설정 파일을 열고 강조된 부분을 참고하여 수정한다.

security.xml
<form-login 
	login-page="/users/login"
	authentication-failure-url="/users/login?error=1"  
	default-target-url="/bbs/list?boardCd=free&page=1" />

login-page 속성의 기본값은 /login, authentication-failure-url 속성의 기본값은 /login?error=1이다.
설정 파일에서 속성을 생략한다면 이 값이 적용된다.
사용자 로그인 페이지(/users/login)를 사용하고 로그인 실패시 다시 로그인 페이지로 이동하게 하려면, login-page 속성 뿐만 아니라 authentication-failure-url 속성을 명시해야 하며 아래와 같은 추가적인 설정이 필요하다.(이미 되어 있다.)

<http use-expressions="true">
    <intercept-url pattern="/users/login" access="permitAll" />

http의 use-expressions의 속성 기본값이 false에서 true로 변경되었으므로 생략할 수 있다.

<http>
    <intercept-url pattern="/users/login" access="permitAll" />

로그인 페이지 수정

명시하지 않은 설정은 디폴트 값이 적용됨을 고려하면서 /users/login.jsp를 수정한다.

/users/login.jsp
<c:if test="${param.error != null }">
        <h2>Username/Password not corrrect</h2>
</c:if>
<c:url var="loginUrl" value="/login" />
<form action="${loginUrl }" method="post">
<p style="margin:0; padding: 0;">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</p>
<table>
<tr>
    <td style="width: 200px;"><spring:message code="user.email" /></td>
    <td style="width: 390px"><input type="text" name="username" style="width: 99%;" /></td>
</tr>
<tr>
    <td><spring:message code="user.password" /></td>
    <td><input type="password" name="password" style="width: 99%;" /></td>
</tr>
</table>

설정 파일일 변경되었으니 톰캣을 재실행하고 로그인을 테스트한다.
로그인이 성공했다면 로그아웃을 실행한다.
다시 빈 화면을 만나게 될 것이다.
이는 logout의 logout-url 속성의 기본값이 /j_spring_security_logout에서 POST /logout으로 변경되었기 때문이다.
POST /logout 요청을 해야하기에 모든 화면이 포함하는 header.jsp를 수정하여 로그아웃을 수정해 보자.
/inc/header.jsp파일을 열고 <head>와 </head>사이에 다음을 추가한다.

<input type="button" value="<spring:message code="user.logout" />" id="logout" />

header.jsp 가장 아래에 다음을 추가한다.

<form id="logoutForm" action="${pageContext.request.contextPath}/logout" method="post" style="display:none">
	<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form>

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

다시 로그아웃을 테스트한다.
header.jsp에 jQuery를 사용하고 있으므로 화면을 보여주는 페이지는 모두 jQuery를 임포트하도록 다음 코드를 추가한다.

<script type="text/javascript" 
    src="${pageContext.request.contextPath}/js/jquery-3.2.1.min.js"></script>

로그아웃까지 테스트했다면 다시 로그인하고 새글 쓰기를 시도한다.
새글 쓰기 처리에서 다시 빈 화면을 만나게 된다.
첨부 파일의 경우 <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />이 아닌 쿼리 스프링으로 CSRF 토큰을 전달해야 한다.
이는 스프링 폼 태그를 사용하고 있다 하더라도 마찬가지다.
write.jsp와 modify.jsp 파일을 열고 <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />이 있다면 지우고, 아래와 같이 폼의 action 속성을 수정한다.

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

하지만 이 방법은 쿼리 파라미터가 노출될 수 있다.
민감한 데이터를 노출되지 않도록 바디나 헤더에 두는 것이 좀 더 낫다.
이에 관한 정보는 아래 참고에 링크해 둔다.

참고 관련 글