본문 바로가기

학원/복기

[Spring] Spring Security

HomeController

@Controller
public class HomeController {
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home() {
		return "home";
	}
	
	@RequestMapping(value = "/user/page", method = RequestMethod.GET)
	public String userPage() {
		return "user_page";
	}
	
	@RequestMapping(value = "/manager/page", method = RequestMethod.GET)
	public String managerPage() {
		return "manager_page";
	}
	
	@RequestMapping(value = "/admin/page", method = RequestMethod.GET)
	public String adminPage() {
		return "admin_page";
	}
}

 

JSP

 

 

 

home.jsp

<body>
	<h1>메인페이지</h1>
	<hr>
	<h3><a href="<c:url value="/user/page"/>">USER</a></h3>
	<h3><a href="<c:url value="/manager/page"/>">Manager</a></h3>
	<h3><a href="<c:url value="/admin/page"/>">Administrator</a></h3>
</body>

 

user_page.jsp

<body>
	<h1>User Page</h1>
	<hr>
	<h3><a href="<c:url value="/"/>">메인으로</a></h3>
</body>

 

admin_page.jsp

<body>
	<h1>Administrator Page</h1>
	<hr>
	<h3><a href="<c:url value="/"/>">메인으로</a></h3>
</body>

 

manager_page.jsp

<body>
	<h1>Manager Page</h1>
	<hr>
	<h3><a href="<c:url value="/"/>">메인으로</a></h3>
</body>

 


 

권한을 가진 사용자만 페이지에 접근할 수 있도록 해보자 

 

 

 

Spring Security 

 

Spring Security는 SpringMVC 프로그램의 인증과 인가 기능을 지원하는 보안 프레임워크이다.

 

Spring Security는 인증과 인가를 위해 Principal 객체를 아이디로 Credential 객체를 비밀번호로 사용하는 Credential 기반의 인증 방식을 사용한다.

  • 인증(Authenticateion) : 로그인 사용자가 맞는지 확인하는 절차
    • 인증을 정상적으로 수행하기 위해 사용자를 식별할 수 있는 정보가 필요함 - Credential 
  • 인가(Authorization - 권한) : 인증된 사용자가 요청된 자원에 접근 가능한가를 결정하는 절차
    • 인증 처리 후 실행됨. - 권한은 일반적으로 역할(Role) 형태로 부여된다. 

 

 

Spring Security를 SpringMVC 프로그램에 적용하여 제공받을 수 있는 기능

  • 다양한 형태(폼로그인 인증, 토큰 기반 인증, OAuth2 기반 인증, LDAP 인증)의 사용자 인증 기능 적용 
  • 프로그램 사용자의 역화(Role)에 따른 권한 레벨 적용
  • 프로그램에서 제공하는 자원에 대한 접근 제어
  • 데이타 암호화
  • SSL 적용
  • 일반적으로 알려진 웹보안 공격 차단

 

Spring Security를 SrpingMVC 프로그램에 적용하는 방법

 

1. srping-security-web, spring-security-core, spring-security-config, spring-security-tablibs 라이브러리를 프로젝트에 빌드 처리 해준다 (메이븐을 이용 : pom.xml)

 

단, 모든 라이브러리 버전을 동일하게 설정하면 Spring 프레임워크 관련 라이브러리의 버전을 고려하여 빌드 처리해주어야 한다. 

 

pom.xml

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-web</artifactId>
	<version>${spring.security-version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-core</artifactId>
	<version>${spring.security-version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-config</artifactId>
	<version>${spring.security-version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-taglibs -->
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-taglibs</artifactId>
	<version>${spring.security-version}</version>
</dependency>

 

 

2. [web.xml] 파일에 Spring Security 기능을 제공하는 Filter 클래스를 Filter로 등록하고 Filter가 실행되기 위한  URL 주소를 매핑 처리해준다.

 

web.xml

...
<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>
...

 

DelegatingFilterProxy 클래스를 필터로 등록되도록 설정한다.

이때 ,  필터의 이름은 [springSecurityFilterChain]으로 등록되도록 작성해야 한다.

 

DelegatingFilterProxy : 메인 Filter Chain에 위치되도록 설정하는 클래스

  • Spring Security 필터를 사용하는 시작점으로 설정되며 서블릿 컨테이너 영역(WAS)의 필터와  ApplicationContext(Spring Container)에서 Spring Bean으로 등록된 필터를 연결하는 다리 역할을 수행한다.
  • 사용자가 프로그램을 요청하면 DelegatingFilterProxy가 요청을 받아 FilterChainProxy 객체에게 요청을 위임하여 필터들이 순서대로 실행되도록 사용 한다.

 

 

3.[web.xml] 파일에 Spring Security 기능의 필터가 사용하기 위한 정보를 제공하는 Spring Bean Configuration File 설정 - ContextLoaderListener 클래스가 읽을 수 있도록 파일 경로를 지정해준다. 

 

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">

	<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<!-- Spring Security 기능을 제공하기 위한 Spring Bean Configuration File을 생성하여 등록 -->
		<param-value>
			/WEB-INF/spring/root-context.xml
			/WEB-INF/spring/security-context.xml
		</param-value>
	</context-param>
	
    ...
	...
    
</web-app>

 

 

4.Spring Security 관련 Spring Bean Configuration File을 작성한다.

  • Spring Security 관련 필터가 동작되기 위한 정보를 Security 네임스페이스 추가하여 spring-security.xsd 파일의 엘리먼트를 사용하여 제공한다. 

 

security-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	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></http>
	<authentication-manager></authentication-manager>
</beans:beans>

 

 

 

 

스코드를 보면 

<input name="_csrf" type="hidden" value="d164573c-5700-4330-ac2d-1717e5d01f22" />

을 반드시 써주어야 한다는 것을 확인할 수 있다


 

Spring Security Filter의 종류

 

1.SecurityContextPersistenceFilter : SecurityContextRepository에서 SecurityContext를 가져오거나 생성하는 필터

2.LogoutFilter : 로그아웃 요청을 처리하는 필터

3.UsernamePasswordAuthenticationFilter : 아이디와 비밀번호를 사용하는 Form 기반 유저 인증을 처리하는 필터

=> Authentication 객체를 만들고 AuthenticationManager에게 인증 처리 위임

=> AuthenticationManager는 실질적인 인증에 대한 검증 단계를 총괄하는 AuthenticationProvider에게

인증 처리를 위임 - UserDetailService와 같은 서비스를 사용해서 인증 가능

4.ConcurrentSessionFilter : 동시 세션과 관련된 필터 - 이중 로그인 방지

5.RememberMeAuthenticationFilter : 세션이 사라지거나 만료 되더라도 쿠키 또는 DB를 사용하여

저장된 토큰을 기반으로 인증 처리하는 필터

6.AnonymousAuthenticationFilter : 사용자 정보가 인증되지 않았다면 익명 사용자 토큰을 반환하는 필터

7.SessionManagementFilter : 로그인 후 Session과 관련된 작업을 처리하는 필터

8.ExceptionTranslationFilter : 필터 체인 내에서 발생되는 인증 및 인가 관련 예외를 처리하는 필터

9.FilterSecurityInterceptor : 권한 부여와 관련한 결정을 AccessDecisionManager에게 위임해 권한

부여 결정 및 접근 제어를 처리하는 필터

10.HeaderWriterFilter: Request의 HTTP 헤더를 검사해 Header를 추가하거나 빼주는 필터

11.CorsFilter : 허가된 사이트나 클라이언트의 요청인지 검사하는 필터

12.CsrfFilter : CSRF Tocken을 사용하여 CSRF 공격을 막아주는 기능을 제공하는 필터

 

 


 

 

authentication-manager : 인증 관리자를 등록하기 위한 엘리먼트

=> 다양한 형태의 인증 방식을 제공할 수 있는 기능을 구현한다.

 

authentication-provider : 인증 제공자를 등록하기 위한 엘리먼트 

=> 실질적인 인증 작업을 진행하는 기능을 구현한다.

 

user-service : 인증 정보를 등록하기 위한 엘리먼트

=> 인증 처리를 이용하여 사용자의 권한 관련 정보를 반환하는 기능 

 

  • user : 인증을 위한 정보를 설정하는 엘리먼트 
  • name 속성 : 사용자를 구분하기 위한 식별자(아이디)를 속성값으로 설정
  • password 속성 : 사용자의 비밀번호를 속성값으로 설정
    • Spring Security 5.0 이상에서는 비밀번호를 반드시 암호화 처리하여 비교되도록 구현
    • password 속성값으로 설정된 비밀번호 앞부분에 {noop}를 붙여 사용하면 암호화 처리하지 않아도 비교 처리할 수 있다.
  • authorities 속성 : 권한(Role)를 속성값으로 설정 
    •  권한은 ROLE 기반으로 설정 - 속성값은 ROLE_XXX 형식으로 설정 
    •  , 기호를 사용하여 권한을 구분하여 여러개의 권한 설정 가능

 

securiy-context.xml

	<authentication-manager>
		<authentication-provider>
			<user-service>
				<user name="abc123" password="{noop}123456" authorities="ROLE_USER"/>
				<user name="xyz789" password="{noop}123456" authorities="ROLE_MANAGER"/>
				<user name="opq456" password="{noop}123456" authorities="ROLE_ADMIN"/>
			</user-service>
		</authentication-provider>
	</authentication-manager>

 

 

<http>는 Spring Security 관련 환경 설정을 제공한다.

 

use-expressions 속성에는 false 또는 true(기본값) 중 하나를 속성값으로 설정한다.

use-expressions 속성값을 [true]로 설정하면 SqEL 표현식을 사용하여 페이지 접근 여부 설정이 가능하다. 

 

 

SpEL 표현식

  • hasRole('role') : 권한을 가지고 있는 경우를 표현 
  • hasAnyRole('role1', 'role2', ...) : 나열된 권한들 중 하나를 가지고 있는 경우를 표현
  • permitAll : 모든 사용자의 접근 가능을 표현 
  • denyAll : 모든 사용자의 접근 불가능을 표현
  • isAnonymous() : Anonymous 사용자(인증 받지 않은 사용자)인 경우에만 접근 가능
  • isRememberMe() : Remember-me 기능으로 로그인한 사용자인 경우에만 접근 가능
  • isAuthenticated() : 인증 처리된 사용자(Remember-me 기능의 사용자 포함)인 경우에만 접근 가능 
  • isFullyAuthenticated() : 인증 처리된 사용자(Remember-me 기능의 사용자 제외)인 경우에만 접근 가능 

 

 

securiy-context.xml

<http auto-config="true" use-expressions="true">
	<intercept-url pattern="/user/**" access="hasAnyRole('ROLE_USER', 'ROLE_MANAGER', 'ROLE_ADMIN')"/>
	<intercept-url pattern="/manager/**" access="hasAnyRole('ROLE_MANAGER', 'ROLE_ADMIN')"/>
	<intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')"/>
	<intercept-url pattern="/**" access="permitAll"/>
</http>

 

  • intercept-url :  요청 페이지에 접근 가능한 권한을 설정하는 엘리먼트
    • 특정 페이지에 대한 권한 설정을 먼저하고 나머지는 마지막에 설정하는 것을 권장한다.
  • pattern 속성 : 요청 페이지의 URL 주소를 속성값으로 설정
    •  * (현재 폴더의 모든 자원들) 또는 **(하위 폴더에 있는 모든 자원들) 등의 패턴문자를 사용하여 속성값 설정이 가능하다. 
  • access 속성 : 페이지에 접근 가능한 권한(Role)을 속성값으로 설정할 수 있다. 
    •  use-expressions 속성을 [true]인 경우 SqEL을 사용하여 권한 설정이 가능하다. 
    •  권한이 없는 사용자가 페이지를 요청할 경우 403 에러 코드가 발생한다.

 

 


 

 

use-expressions [false]로 설정하면 SpEL를 사용하지 않는다는 뜻이다.

intercept-url 엘리먼트를 사용하여 페이지에 접근 가능한 권한을 하나만 설정할 수 있다.

 

security-context.xml

<http auto-config="true" use-expressions="false">
		<intercept-url pattern="/user/**" access="ROLE_USER"/>
		<intercept-url pattern="/user/**" access="ROLE_MANAGER"/>
		<intercept-url pattern="/manager/**" access="ROLE_MANAGER"/>
		<intercept-url pattern="/admin/**" access="ROLE_ADMIN"/>
</http>

...

<authentication-manager>
	<authentication-provider>
		<user-service>
			<user name="abc123" password="{noop}123456" authorities="ROLE_USER"/>
			<user name="xyz789" password="{noop}123456" authorities="ROLE_MANAGER"/>
			<user name="opq456" password="{noop}123456" authorities="ROLE_ADMIN"/>
		</user-service>
	</authentication-provider>
</authentication-manager>

MANAGER는 user폴더, manager 폴더 아래의 페이지에 접근 가능하다.

 

 

아래의 방법도 가능하다.

<http auto-config="true" use-expressions="false">
		<intercept-url pattern="/user/**" access="ROLE_USER"/>
		<intercept-url pattern="/manager/**" access="ROLE_MANAGER"/>
		<intercept-url pattern="/admin/**" access="ROLE_ADMIN"/>
</http>

<authentication-manager>
	<authentication-provider>
		<user-service>
			<user name="abc123" password="{noop}123456" authorities="ROLE_USER"/>
			<user name="xyz789" password="{noop}123456" authorities="ROLE_USER,ROLE_MANAGER"/>
			<user name="opq456" password="{noop}123456" authorities="ROLE_ADMIN"/>
		</user-service>
	</authentication-provider>
</authentication-manager>

 

 

일반적으로 아래의 예시처럼  use-expressions 을 [true]로 설정하는 방법을 많이 사용한다.

(권한이 없는 사용자가 페이지를 요청할 경우 AccessDeniendException 예외 발생가 발생한다.)

<http auto-config="true" use-expressions="true">
	<!--<intercept-url pattern="/user/**" access="ROLE_USER"/>
	<intercept-url pattern="/manager/**" access="ROLE_MANAGER"/>
	<intercept-url pattern="/admin/**" access="ROLE_ADMIN"/>  -->
		

	<intercept-url pattern="/guest/**" access="hasAnyRole('ROLE_USER', 'ROLE_MANAGER', 'ROLE_ADMIN')"/>
	<intercept-url pattern="/user/**" access="hasRole('ROLE_USER')"/>
	<intercept-url pattern="/manager/**" access="hasAnyRole('ROLE_MANAGER')"/>
	<intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')"/>
	<intercept-url pattern="/**" access="permitAll"/>
</http>

 

 

home.jsp

<h1>메인페이지</h1>
<hr>
<h3><a href="<c:url value="/guest/page"/>">Guest</a></h3>
<h3><a href="<c:url value="/user/page"/>">User</a></h3>
<h3><a href="<c:url value="/manager/page"/>">Manager</a></h3>
<h3><a href="<c:url value="/admin/page"/>">Administrator</a></h3>