Spring 4 Security example
Spring 4에서 Security 활용 로그인 인증 예
Spring Security가 제공하는 기능은 대략 다음과 같은 용어로 정리할 수 있다
- 인증 (Authentication) : 회원인지 확인
- 인가 (Authorization) : 특정 페이지에 접속하는 것을 제한
- UI (로그인 폼 등) : 로그인 폼 출력
Spring Security 를 이용하면 개발자가 로그인, 로그아웃 기능을 작성하지 않아도 스프링에서 지원하는 폼과 기능을 이용할 수 있다
스프링 보안기능을 설정하는 것만으로도 특정 URL으로 접속하는 이용자의 인증을 확인하여 접근을 제한하거나 로그인 폼을 보여준다
아래의 내용은 Spring Security의 개념을 파악하는데 도움이 될 것이며 데이터베이스를 이용한 이용자의 인증방법을 이해하고 사용하려면 이 부분을 이해하고 있어야 할 것이다.
Maven 라이브러리 설정(3개)
spring-security-web, spring-security-config, spring-security-taglibs
스프링 시큐리티는 다음과 같은 버전에서 작동이 되는 것을 확인할 수 있었다
Spring 라이브러리 버전 4.1.9, Security 버전 4.1.0
Spring 라이브러리 버전 4.3.2, Security 버전 4.1.1
pom.xml 파일에 다음과 같이 라이브러리를 추가한다
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.1.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.1.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>4.1.0.RELEASE</version> </dependency>
루트 컨텍스트 설정파일에 스프링보안 설정을 추가한다
참고: 루트 컨텍스트 설정파일은 web.xml 에서 contextConfigLocation 이라는 이름으로 등록된 설정파일을 의미한다(아래 참조)
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
Spring 4에서는 CSRF ( Cross Site Request Forgery ) 방지 기능이 디폴트로 작동하는데, CSRF기능이 작동하지 않는 상태에서는 <logout /> 태그만으로 로그아웃기능이 작동하지만 CSRF 기능이 작동할 때는 <logout /> 태그외에 아래의 설정처럼 <csrf disabled="true"/> 를 추가로 사용해야 디폴트 로그아웃 기능이 작동하게 된다.
/WEB-INF/spring/root-context.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" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.1.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <http auto-config='true' use-expressions="true"> <intercept-url pattern="/admin/**" access="hasAuthority('ROLE_ADMIN')" /> <intercept-url pattern="/manager/**" access="hasRole('ROLE_MANAGER')" /> <intercept-url pattern="/member/**" access="isAuthenticated()" /> <intercept-url pattern="/**" access="permitAll" /> <form-login /> <csrf disabled="true"/> <logout/> </http> <authentication-manager> <authentication-provider> <user-service> <user name="guest" password="1111" authorities="ROLE_USER"/> <user name="manager" password="2222" authorities="ROLE_MANAGER"/> <user name="admin" password="3333" authorities="ROLE_ADMIN, ROLE_USER"/> </user-service> </authentication-provider> </authentication-manager> </beans:beans>
위의 설정에서 hasAuthority('ROLE_ADMIN') 과 hasRole('ROLE_MANAGER') 의 차이점은 예를 들어, hasRole()을 사용하여 'ROLE_MANAGER' 권한을 검사하는 경우에는 hasRole('ROLE_MANAGER') 이나 hasRole('MANAGER')를 사용해도 문제가 없지만, hasAuthority()를 사용하여 권한을 설정하는 경우에는 'ROLE' 부분을 생략할 수 없고 반드시 hasAuthority('ROLE_ADMIN') 과 같이 사용해야 한다
또, 위의 설정 중에서 <logout /> 태그만 사용하여 디폴트 로그아웃 기능이 작동하기를 바란다면, 디폴트로 CSRF 방지기능이 작동하고 있는 상태이므로 다음과 같이 POST 방식으로 /logout 요청을 전달해야 한다
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>로그아웃</title> </head> <body> <c:url var="logoutUrl" value="/logout"/> <form action="${logoutUrl}" method="post"> <input type="submit" value="Log out" /> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> </form> </body> </html>
Spring Security의 디폴트 로그인 폼 URL
http://ip:port/context/login
시큐리티 설정파일에 intercept-url 을 설정하고 설정된 URL으로 이용자가 접속하면 스프링 시큐리티는 로그인 검사를 하여 로그인 폼을 띄운다. 로그인은 거쳤지만 권한이 다른 곳으로 접속하려는 경우에는 스프링 시큐리티에서 403 Access Denided 오류를 출력한다
Spring Security의 디폴트 Logout 요청 URL
http://ip:port/context/logout
위의 URL을 요청하면 로그아웃 기능이 실행되고 화면에 로그인 폼을 보여준다
로그아웃을 위한 요청 URL을 생성하는 좋은 방법은 웹페이지 어디서나 다음과 같은 JSTL URL 태그를 사용하는 것이다
<c:url var="logoutUrl" value="/logout"/>
스프링 보안기능 작동방식만 간단히 테스트하려면 콘트롤러 클래스를 경우하지 않고 이용자의 요청을 바로 뷰에 연결하기 위해 서블릿 설정파일에 다음과 같이 설정한다.
콘트롤러 클래스를 작성하고 그 안에서 요청을 처리하고 뷰의 경로를 리턴하는 것이 일반적이므로 아래와 같은 방식을 반드시 사용할 필요는 없다
...... ........... <view-controller path="/index" view-name="security/index" /> <view-controller path="/home/main" view-name="security/homeMain" /> <view-controller path="/manager/main" view-name="security/managerMain" /> <view-controller path="/admin/main" view-name="security/adminMain" /> <view-controller path="/member/main" view-name="security/memberMain" /> <view-controller path="/logout" view-name="security/logout" /> ..... ........
web.xml 파일에는 루트 컨텍스트 설정파일과 springSecurityFilterChain 를 등록해야 한다
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" 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_2_5.xsd"> <!-- The definition of the Root Spring Container shared by all Servlets and Filters --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/root-context.xml</param-value> </context-param> <!-- Creates the Spring Container shared by all Servlets and Filters --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Processes application requests --> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/servlet-context.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <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> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- spring security --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
뷰로 사용할 jsp 파일을 WEB-INF/views/security/(임의의 위치) 안에 작성한다
index.jsp (Spring Security 관련 태그가 사용됨)
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>INDEX</title> </head> <body> <h3>INDEX !</h3> <%--로그인을 거친 이용자일 경우에는 이용자의 로그인 ID를 보여준다 --%> <sec:authorize access="isAuthenticated()"> <sec:authentication property="name"/>님 환영합니다 </sec:authorize> <ul> <li><a href="<c:url value='/home/main' />">/home/main</a></li> <li><a href="<c:url value='/member/main' />">/member/main</a></li> <li><a href="<c:url value='/manager/main' />">/manager/main</a></li> <li><a href="<c:url value='/admin/main' />">/admin/main</a></li> <%--로그인을 거친 이용자일 경우에는 로그아웃 링크를 보여준다 --%> <sec:authorize access="isAuthenticated()"> <li><a href="<c:url value='/logout' />"> 로그아웃</a></li> </sec:authorize> </ul> </body> </html>
memberMain.jsp, adminMain.jsp, homeMain.jsp, managerMain.jsp 등의 파일은 다음과 같이 중요 내용이 없이 거의 동일하게 작성한다
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>memberMain</title> </head> <body> <h3>memberMain !</h3> memberMain: <sec:authentication property="name"/>멤버 화면 <a href="<c:url value='/index' />"> [index바로가기]</a> </body> </html>
설정한대로 접근제어가 제대로 되고 있는지 확인한다
웹브라우저 주소창에 다음과 같이 입력하고 엔터를 친다
http://IP주소:포트번호/컨텍스트명/index
예) http://192.168.8.32:8088/SpringWeb/index
스프링 보안설정의 내용에 따라 index로의 접근은 'permitAll' 으로 설정되어 있으므로 누구나 요청할 수 있다
그리고 home/main 요청도 접속제한을 설정하지 않은 상태이므로 누구나 접근이 가능한 상태이다
그 외의 요청(member, manager, admin)은 보안설정이 되어 있으므로 인가된 이용자인지 확인하기 위해 스프링은 로그인 폼을 보여주고 이용자의 인증을 수행한다
스프링이 보여주는 로그인 폼을 통해 로그인을 거치면 해당 이용자는 인가된 URL에만 접속이 가능하고 인가되지 않은 URL에 접속하면 403 에러가 출력된다