▸Spring Security

스프링 Security_로그인_기본 컨텍스트 설정 [1/9]

코데방 2020. 3. 27.
728x90
- Develop OS : Windows10 Ent, 64bit
- WEB/WAS Server : Tomcat v9.0
- DBMS : MySQL 5.7.29 for Linux (Docker)
- Language : JAVA 1.8 (JDK 1.8)
- Framwork : Spring 3.1.1 Release
- Build Tool : Maven 3.6.3
- ORM : Mybatis 3.2.8

 

커스터마이징 순서대로 총 9개의 포스팅으로 나눠져 있습니다. 순서대로 보면 쉽게 적용할 수 있습니다.

 

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_기본 컨텍스트 설정 [1/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_DB 연동 로직 작성 [2/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_로그인 실패 대응 로직 [3/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_로그인 성공 대응 로직 [4/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_DB 패스워드 암호화 [5/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_암호화된 DB 패스워드로 인증 [6/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_Principal 객체 [7/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_자동 로그인(Remember-me) [8/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_security 태그 라이브러리 [9/9]

 

붉은색 네모가 이번 포스팅 내용

 

 

로그인 및 권한 인증 로직 자체는 매우 간단하게 짤 수 있지만 보안적인 요소까지 고려하면 작성하기가 매우 어렵습니다. 스프링의 Security는 보안 전용 프레임워크로, 몇 가지 설정과 커스터마이징을 거치면 높은 수준의 보안성을 가진 로직을 구현할 수 있습니다.

 

구조 자체는 복잡하지만 우리가 실제로 손을 대야할 부분은 많지 않기 때문에 큰그림만 이해하면 쉽게 구성할 수 있습니다.

 

 


 

 

1. Spring Sercurity 의존 설정 (pom.xml)

 

기본 내장된 라이브러리가 아니므로 아래 세 가지 라이브러리를 의존설정 해줍니다. 사용하고 있는 스프링 버전에 맞춰야하기 때문에 버전은 아래와 같이 기입해줍니다. pom.xml 가장 윗쪽에 보면 해당 프로퍼티에 버전이 명시돼 있는 부분이 있습니다. 태그라이브러리는 선택사항이지만 아주 편리하므로 같이 사용해주는 것이 좋습니다.

<!-- Spring security -->
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-web</artifactId>
	<version>${org.springframework-version}</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-config</artifactId>
	<version>${org.springframework-version}</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-taglibs</artifactId>
	<version>${org.springframework-version}</version>
</dependency>

 

 


 

 

 

2. 필터 설정 (web.xml)

 

로그인과 권한 인증은 사용자가 서비스를 요청할 때마다 이루어져야 합니다. 따라서 인코딩 필터와 마찬가지로 필터 기능을 사용해 서블릿에 도달하기 전 요청을 가로채서 작업할 수 있도록 해줍니다. 모든 URL(/*) 요청을 해당 필터에서 먼저 가로채겠다는 의미입니다. 

 

필터 클래스로 등록된 'DelegatingFilterProxy' 객체는 사용자 요청을 가로채서 개발자가 설정 파일에 등록해둔 여러 요소들을 가지고 스프링의 보안 로직을 적용하는 가장 시작점입니다. 내부 로직을 보면 정말 많은 필터 체인과 처리 클래스들이 연동되어 있습니다.

<!-- 스프링 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>

 

 


 

 

3. 루트 컨텍스트 설정 (root-context 또는 새로 만든 설정 파일)

 

언제나 그렇듯이 스프링에서 제공해주는 기능들을 이용할 때는 컨테이너(컨텍스트)에 등록해주면 됩니다. 컨테이너가 생성되면서 그 안의 설정을 읽어 적절한 Bean 객체를 생성해주고 사용자가 필요할 때 제공해주기 때문이죠.

 

먼저 전체 설정 코드입니다. 하나씩 이어서 알아보겠습니다. 1~8번글을 지나면서 커스터마이징에 따라 설정 부분도 계속 바뀌게 되는데 일단 가장 기본 설정입니다. 

<?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:s="http://www.springframework.org/schema/security"
	xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<s:http auto-config="true" use-expressions="true">

		<!-- 정적 리소스는 모두 접근 허용 -->
		<s:intercept-url pattern="/resources/**" access="permitAll" />

		<!-- 로그인된 상태에서는 로그인이나 회원가입 화면에 접근 못하도록 함 -->
		<s:intercept-url pattern="/loginView" access="isAnonymous()" />
		<s:intercept-url pattern="/registerUserView" access="isAnonymous()" />

		<!-- 관리자페이지는 관리자만 접근 허용 -->
		<s:intercept-url pattern="/admin/**" access="hasRole('admin')" />

		<!-- 로그인 설정 -->
		<s:form-login	
			username-parameter="userId" 
			password-parameter="userPw"
			login-processing-url="/loginAsk" 
			login-page="/loginView" default-target-url="/"
			authentication-failure-url="/loginView" />

		<!-- 로그아웃 설정 -->
		<s:logout 
			logout-url="/logoutAsk"
			logout-success-url="/"
			invalidate-session="true"
			delete-cookies="true" />
	</s:http>

	<!-- 권한이 없어서 금지된 URI 접속할 때 보여줄 페이지(403 에러 페이지 대체) -->
	<s:access-denied-handler error-page="/" />

	<!-- DB 연동 설정 -->
	<s:authentication-manager>
		<s:authentication-provider user-service-ref="userLoginService">
		</s:authentication-provider>
	</s:authentication-manager>

</beans>

 

 

 

3-1. security 네임 스페이스 추가 및 이름 지정

 

Spring security 설정에 필요한 네임 스페이스입니다. 체크하면 security에 대한 네임스페이스가 생기는데, 그대로 security로 써도 되고 위의 코드와 같이 간단한 문자('s' 등)로 변경해서 사용해도 됩니다. 

 

'security'를 's'로 변경 

 

 

 

3-2. 로그인 설정 (intercept-url)

 

	<s:http  auto-config="true" use-expressions="true">

		<!-- 정적 리소스는 모두 접근 허용 -->
		<s:intercept-url pattern="/resources/**" access="permitAll" />

		<!-- 로그인된 상태에서는 로그인이나 회원가입 화면에 접근 못하도록 함 -->
		<s:intercept-url pattern="/loginView" access="isAnonymous()" />
		<s:intercept-url pattern="/registerUserView" access="isAnonymous()" />

		<!-- 관리자페이지는 관리자만 접근 허용 -->
		<s:intercept-url pattern="/admin/**" access="hasRole('admin')" />

 

로그인과 로그아웃 설정 부분이 가장 핵심입니다. 스프링이 디폴트로 가지고 있는 'ROLE_USER' 등의 권한 이름이 있고, 표현식을 쓰지 않을 수도 있지만 그렇게 사용할 일은 거의 없을 것 같으므로 생략하겠습니다. 

 

security 필터는 사용자가 요청한 URL을 서블릿에게 주지 않고 먼저 가로챕니다. 인터셉터 설정(intercept-url)은 가로챈 URL을 어떻게 처리할지 결정해주는 부분입니다. "pattern=/url"을 요청한 사용자가 "access=권한"에 맞으면(true) 서블릿으로 보내주고, 아닐 경우 접근을 금지시키거나 로그인 페이지로 이동시키겠다라는 의미입니다. 권한 표현식(access="표현식")에 따라 조정할 수 있습니다.

 

그리고 위에서부터 순서대로 적용되니, '허용할 범위 → 금지할 범위' 순으로 작성해야 합니다. 같은 범위를 금지부터 하고 허용하면 먼저 순서인 금지만 적용됩니다.

 

표현식 설명
hasRole('role') 해당 권한이 있으면 요청한 페이지를, 없으면 로그인 페이지로 이동
hasAnyRole('role1,'role2') 포함된 권한 중 하나라도 있으면 요청한 페이지를, 없으면 로그인 페이지로 이동
isAuthenticated() 로그인 인증을 받은 사용자는 권한에 관계 없이 허용, 익명 사용자는 로그인 페이지로 이동
isFullyAuthenticated() 자동 로그인하지 않고 로그인 인증을 한 사용자는 권한에 관계 없이 허용
isAnonymous() 권한이 없는 익명의 사용자만 접근을 허용함 (로그인되어 권한이 있으면 접근 불가)
isRememberMe() 자동 로그인 대상 사용자의 경우 접근을 허용
permitAll 모두 접근 허용
denyAll 모두 접근 불가

 

 

 

* 정적 리소스 경로(/resources/**)는 권한에 관계없이 서블릿으로 보내주겠다.

<!-- 정적 리소스는 모두 접근 허용 -->
<s:intercept-url pattern="/resources/**" access="permitAll" />

 

제가 적용한 위 예시 코드에서는 굳이 불필요한 설정이지만 설명을 위해 추가해뒀습니다.

 

만약 '/*' 또는 '/**' 표현식으 루트(/) 하위 URL을 모두 가로챈다면 정적 리소스에 대한 요청까지 가로채기 때문에 로그인 화면에 대한 디자인이 깨지게 됩니다. 저처럼 루트 하위 URL 범위를 필터링하지 않고 사용하는 경우는 상관없지만 루트 하위 URL 전체를 필터링하는 경우라면 그 위(먼저 순서)에 필수적으로 지정해줘야하는 설정입니다.

 

서블릿 컨텍스트에서 정적 리소스를 컨트롤러로 보내지 않고 따로 설정해주는 것과 같은 의미입니다.

 

 

 

* 로그인 화면(/loginView)과 회원가입 화면(/registerUserView)은 권한을 가지지 않은(로그인을 하지 않은) 요청자만 서블릿으로 보내주고, 권한을 가진 사람은 접근을 금지하겠다.

<!-- 로그인된 상태에서는 로그인이나 회원가입 화면에 접근 못하도록 함 -->
<s:intercept-url pattern="/loginView" access="isAnonymous()" />
<s:intercept-url pattern="/registerUserView" access="isAnonymous()" />

 

저는 관리자 페이지를 제외하고는 로그인을 안하더라도 다른 페이지에 접근할 수 있도록 설정했습니다. 그래서 로그인한 사용자가 로그인이나 회원가입에 대한 URL을 요청하더라도 접근할 수 없도록 했습니다. 로그인하면 버튼은 보이지 않겠지만 즐겨찾기를 해두거나 URI를 직접 입력해 접속할 수도 있으니까요.

 

그리고 만약 위의 정적 리소스에서 설명한 것과 같이 루트 하위(/*, /**) 범위를 모두 인터셉터 하는 경우 로그인 화면에 대한 접근 허용도 추가로 해줘야 합니다. 모든 범위에는 로그인 화면도 포함되기 때문에 무한 리다이렉트가 발생하다 에러가 납니다.

 

 

 

* /admin 하위 URL(admin/**)을 요청하는 사용자가 'admin' 권한을 가지고 있을 경우에는 서블릿으로 보내서 정상적으로 처리하고, 아닐 경우에는 로그인 페이지로 강제 이동시키겠다.

<!-- 관리자페이지는 관리자만 접근 허용 -->
<s:intercept-url pattern="/admin/**" access="hasRole('admin')" />

 

 

 

 

3-3. 로그인 화면 설정

 

	<!-- 로그인 설정 -->
	<s:form-login	
		username-parameter="userId" 
		password-parameter="userPw"
		login-processing-url="/loginAsk" 
		login-page="/loginView" default-target-url="/"
		authentication-failure-url="/loginView" />

 

hasRole()이나 hasAnyRole() 등으로 권한을 지정한 경우, 권한이 없는 사용자의 요청은 무시되고 스프링 security가 설정된 로그인 페이지로 요청 URL을 바꿔서 서블릿에 넘겨줍니다. 로그인 페이지는 우리가 직접 만들겠지만, 데이터를 서버로 다시 보내 주면(submit) 이 데이터를 서블릿과 컨트롤러에 넘겨서 처리하지 않고 스프링 security의 내부 로직으로 처리합니다. 그 처리에 대한 설정입니다.

 

* username-parameter : 입력한 ID에 대한 파라미터 네임

* password-parameter : 입력한 PW에 대한 파라미터 네임

* login-processing-url : View 페이지의 <form action="/url"> 에서 지정한 URL

* login-page : 서블릿에 넘겨줄 로그인 페이지 URL

* default-target-url : 로그인에 성공하면 보내줄 페이지

* authentication-failure-url : 로그인에 실패하면 보내줄 페이지

 

View의 <form action="/URL">의 URL은 서블릿에 도달하지 않기 때문에 컨트롤러에서 별도로 작성할 필요가 없습니다. 컨트롤러에서는 로그인 화면을 분배하는 메소드 하나만 작성하면 되고 나머지 로직은 불필요합니다.

 

 

 

3-4. 로그아웃 로직 설정

 

	<!-- 로그아웃 설정 -->
	<s:logout 
		logout-url="/logoutAsk"
		logout-success-url="/"
		invalidate-session="true"
		delete-cookies="true" />
</s:http>

 

로그인 설정과 같이, 로그아웃에 대한 처리를 설정합니다. 열심히 쿠키를 지우고 세션을 지우고 하던 작업을 설정만으로 알아서 해결해줍니다. 컨트롤러에 도달하지 않고 처리되기 때문에 해당 요청 URL에 대한 로직을 컨트롤러에 포함하지 않아도 됩니다. 

 

* logout-url : 로그아웃 요청에 대한 URL (버튼 눌렀을 때 발생하는 요청 URL)

* logout logout-success-url : 로그아웃 완료되면 보내줄 URL

* invalidate-session : 세션 삭제 여부

* delete-cookies : 쿠키 삭제 여부

 

 

 

3-5. 권한이 없어 접근 금지될 경우 보여줄 페이지 설정

	<!-- 권한이 없어서 금지된 URI 접속할 때 보여줄 페이지(403 에러 페이지 대체) -->
	<s:access-denied-handler error-page="/" />

 

기본적으로 403 에러 페이지를 출력해줍니다. 하지만 보기가 좀 그러니 접근 급지에 대한 예쁜 페이지를 하나 만들어서 보여주고 싶다면 설정해주는 부분입니다. 일단 저는 페이지 만들기가 귀찮아서 메인 화면으로 리다이렉트 시켰습니다.

 

 

 

 

3-6. DB 연동 설정

 

<!-- DB 연동 설정 -->
<s:authentication-manager>
	<s:authentication-provider user-service-ref="userLoginService">
	</s:authentication-provider>
</s:authentication-manager>

 

DB의 데이터(ID, PW, 권한)을 스프링에 전달해주는 클래스의 Bean 객체 이름을 아래와 같이 래퍼런스로 제공해주면 나머지는 스프링 security가 알아서 인증 로직을 수행합니다. 이 클래스를 작성하는 방법은 다음글을 참조하시면 됩니다.

 

* user-service-ref="DB인증을 처리할 Bean 객체 이름"

 

 


 

 

이것으로 기본적인 인증 로직을 위한 컨텍스트 설정 작업이 끝났습니다. 이제 DB에서 사용자 정보를 가져오는 클래스만 작성해주면 됩니다. 다음글에서 이어가도록 하겠습니다. 

 

컨텍스트 설정은 기본 제공되는 "root-context.xml" 파일에 해도 되고 따로 만들어줘도 됩니다. 따로 만들 경우 "web.xml" 파일의 루트 컨텍스트 설정에 등록해주는 것을 잊으면 안됩니다.

 

<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>
		/WEB-INF/spring/root-context.xml
		/WEB-INF/spring/root-dbConnection.xml
		/WEB-INF/spring/root-security.xml
	</param-value>
</context-param>
728x90

댓글

💲 추천 글