[펌] 2장 Intecept와 Granted Authority

ITWeb/개발일반 2012. 3. 5. 17:38
원본글 :  http://springmvc.egloos.com/506465 


리소스의 권한(Intercept) - 전장에 이어 계속

전장에 이어서 리소스의 권한설정을 계속 하도록 하겠습니다. 먼저 우리는 DelegatingFilterChain 클래스를 통해 스프링 시큐리티가 모든 URL요청을 가로챌 수 있도록 설정하는 법을 배웠습니다. 이제 스프링 컨텍스트에서 DelegatingFilterChain이 가로챈 요청을 세분화하는 방법을 알아보죠.

먼저 미리 작성한 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-3.1.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">

이 네임스페이스는 security를 기본 xmlns로 선택하고 있는 컨텍스트 네임스페이스입니다. 요소명이 <beans:beans>로 된 점을 주의하시고 왜 이렇게 설정되있는지 확실히 이해하도록 노력하세요. 종종 이런 컨텍스트 파일 설정에 익숙하지 않으셔서 오류를 겪으시는 분들이 꽤 있습니다.

네임스페이스를 설정했다면 이제 본격적으로 시큐리티 설정을 해봅시다. 기본적인 테스트를 위해 다음과 같은 소스를 security-context.xml에 삽입합니다.

<http auto-config="true">
<intercept-url pattern="/-" access="ROLE_USER" />
</http>

<authentication-manager>
<authentication-provider>
<user-service>
<user name="guest" password="guest" authorities="ROLE_USER"/>
</user-service>
</authentication-provider>
</authentication-manager>

그 다음 테스트를 위해 서버를 기동해보세요. 성공했다면 다음과 같은 로그인 창이 뜨게 될 것입니다.


일단 이 로그인 창이 무엇을 위한 로그인 창인지 이해가 안되 조금 어안이 벙벙하실 수도 있습니다. 저같은 경우는 이 창을 보면서 "스프링 시큐리티를 사용하려면 온라인 인증을 받아야 하나?" 라는 생각을 먼저 했었습니다. 그도 그럴 것이 정말 한게 아무것도 없고 몇가지 XML설정만 해준 것이 끝이었거든요. 근데… 한참을 보고 나서야 알게 된 사실이지만 정말 놀랍게도 이것은 스프링 시큐리티가 모든 설정을 기본값으로 설정해준 덕에 우리가 얻게된 로그인 창이었습니다.

이번엔 아이디와 비밀번호에 각각 guest를 입력하고 Login 버튼을 클릭해봅시다. 아마 당신은 애초에 얻고자 했던 URL 리소스에 접근할 수 있을 것입니다. 스프링 시큐리티가 재미난 마술을 부린 것 같지 않습니까?

<http auto-config="true">

먼저 어떤 원리에서 Login 창이 뜨게 된건지부터 알아봅시다. 이 모든 마법은 바로 auto-config="true"에서 발생한 트릭이었습니다. 스프링 시큐리티는 기본적으로 Ahthorization(권한 부여)에 관한 대부분의 설정이 <http> 요소에 위치해 있으며 설정 가능한 모든 요소에 디폴트 값이 존재합니다. 그러므로 <http>요소의 auto-config 속성을 true로 잡아줌으로써 우리는 모든 디폴트 속성값으로 서버를 설정했던 셈이죠.

<intercept-url pattern="/-" access="ROLE_USER" />

그 다음 이 부분… 어디서 많이 본 단어가 나오지 않습니까? 제가 일전부터 리소스의 권한이라고 목청껏 떠들어댔던 intercept가 나오고 있습니다. <intercept-url>은 DelegatingFilterProxy에서 가로챈 요청을 좀 더 세부적으로 나눠주며(pattern) 접근할 수 있는 권한을 설정(access)합니다.


부여된 권한(Granted Authority)

그렇다면 대충 리소스의 권한은 이런 식으로 설정할 수 있다는 것을 알게 됬지만 문제는 ROLE_USER와 같이 부여된 권한(Granted Authority) 설정은 어디서 하게 되는 걸까요? 보호하고 싶은 리소스에 보호막을 씌웠지만 누군가 접근할 수 있도록 권한도 부여해야 하지 않습니까! 그리고 ROLE_USER라는 단어는 어디서 나오게 된 거고 꼭 이렇게 써야만 하나요?

곧바로 이런 문제의 해답을 얻을 순 없겠지만 원리에 근접하고, 유치하다 싶은 고민들은 보안의 원리를 깨우치는데 매우 중요한 역할을 담당합니다. 왜냐하면 이런 문제의 근본적인 해답은 바로 Authentication(인증), Authorization(권한부여)에 대한 깊은 이해에서 나오게 되기 때문이죠.

먼저의 모든 단어의 뜻은 무시하고 보안이란 맥락에서만 보았을 때 위에서 소개한 리소스의 권한(Intercept)은 Authentication(인증)의 영역에 포함됩니다. 우리가 무언가의 인증을 받은 후에 리소스에 접근할 권한을 얻게 되므로 리소스의 권한은 인증작업에 일부가 되는 셈이죠. 그렇다면 부여된 권한(Granted Authority)는 어디에 속할까요? 바로 Authorization(권한부여)에 속하게 됩니다. 권한을 부여하려면 먼저 권한부터 설정되있어야 하며 궁극적으로 설정된 권한을 유저에게 부여해줘야 하기 때문입니다.

그러므로 <http>요소는 Authentication(인증)의 범주에 속해있으며 스프링 시큐리티는 Authorization(권한부여)의 영역을 분할하기 위해 <authorization-manager>란 요소를 따로 사용하고 있습니다. 아래의 소스는 <authorization-manager>의 가장 하위 요소인 <user>인데요. 이 요소를 통해 우리는 작게나마 권한부여에서 수행할 역할에 대해 가늠할 수 있게 됩니다.

<user name="guest" password="guest" authorities="ROLE_USER"/>

먼저 인증받을 사용자의 아이디와 비밀번호를 입력한 뒤에 해당 사용자에게 권한(ROLE_USER)를 부여합니다. 부여할 권한이 꼭 ROLE_USER와 같을 필요는 없지만 별도의 튜닝이 없다면 가급적 'ROLE_' 이란 문자열로 시작해야 합니다. 왜냐하면 스프링 시큐리티는 RoleVoter라고 부여된 권한(Granted Authority)을 검사하는 클래스를 가지고 있는데 이 검사자가 문자열이 ROLE_이란 접두어로 시작하는 지를 검사하기 때문입니다. 만약 ROLE_이란 접두어로 시작하지 않는다면 시큐리티는 접근 보류(ACCESS_ABSTAIN)라는 결론을 짓게 됩니다.


org.springframework.security.access.AccessDecisionVoter의 int 상수
ACCESS_GRANTED(접근 승인, 값=1) : RoleVoter가 접근 결정자에게 접근을 승인할 것을 요청.
ACCESS_ABSTAIN(접근 보류, 값=0 : RoleVoter가 접근 결정자에게 접근을 보류할 것을 요청.
ACCESS_DENIED(접근 거부, 값=-1) : RoleVoter가 접근 결정자에게 접근을 거부할 것을 요청.

그러므로 이런 사실을 종합해본다면 부여할 권한의 이름 설정이 중요한 역할을 담당하며 또 <intercept-url>에서 선별한 자원에 사용자가 접근할 수 있게 하려면 <intercept-url access>의 값과 <user authorities>의 값이 서로 일치하게 만들어야 한다는 것입니다.

그리고 또 하나 기억해야 할 것은 지금은 우리가 <user-service>를 이용해 직접 사용자를 작성하고 있지만 향후 서비스에서 모든 사용자를 직접 매핑할 수는 없는 노릇이므로 앞으로 <jdbc-user-service>를 이용해 Authorization(권한 부여)의 대부분을 DB로 이전시켜야 한다는 것입니다. 그러므로 지금부터라도 데이터베이스에 단순히 사용자의 정보만 기록할 것이 아니라 권한, 그룹(퍼미션) 등 다양한 역할을 데이터베이스가 수행해야 한다는 사실을 기억해야 합니다.


스프링 시큐리티의 표현식 언어

스프링 표현식 언어를 사용하면 RoleVoter에서 수행하지 않는 보안설정을 표현식을 통해 추가할 수 있습니다.

<http auto-config="true" use-expressions="true">
<intercept-url pattern="/-" access="hasRole('ROLE_USER')"/>
</http>

위처럼 표현식 사용을 설정하면 아래의 표현식들의 사용이 가능해집니다. 표현식은 어디까지나 RoleVoter가 영향력을 가지는 <intercept access>에서만 설정할 수 있습니다.

hasIpAddress(ip) : 접근자의 IP주소가 매칭하는지 확인합니다.
hasRole(role) : 역할이 부여된 권한(Granted Authority)와 일치하는지 확인합니다.
hasAnyRole(role) : 부여된 역할 중 일치하는 항목이 있는지 확인합니다.
 예 - access = "hasAnyRole('ROLE_USER','ROLE_ADMIN')"

위의 표현식 외에도 다음과 같은 조건들을 access에서 사용할 수 있습니다.

permitAll : 모든 접근자를 항상 승인합니다.
denyAll : 모든 사용자의 접근을 거부합니다.
anonymous : 사용자가 익명 사용자인지 확인합니다.
authenticated : 인증된 사용자인지 확인합니다.
rememberMe : 사용자가 remember me를 사용해 인증했는지 확인합니다.
fullyAuthenticated : 사용자가 모든 크리덴셜을 갖춘 상태에서 인증했는지 확인합니다.

원한다면 표현식 사이에 AND, OR연산도 가능합니다.
access = "hasAnyRole('ROLE_USER','ROLE_ADMIN') or authenticated"



로그인 페이지 커스터마이징

<intercept-url pattern="/login" access="permitAll" />
<intercept-url pattern="/-" access="hasRole('ROLE_USER')"/>
<form-login login-page="/login" username-parameter="username" password-parameter="password" login-processing-url="/authentication" />

아마 스프링 시큐리티에서 제공하는 기본 로그인 창을 계속 이용하고 싶으신 분은 아무도 없으실 겁니다. <form-login> 요소를 이용하면 손쉽게 로그인 페이지를 커스터마이징하실 수 있습니다.

login-page : 로그인이 요청될 시에 이동할 URL을 설정합니다.
username-parameter : 로그인 아이디의 파라미터명 즉 name필드값을 설정합니다.
passoword-parameter : 비밀번호의 파라미터 명을 설정합니다.
login-processing-url : 폼에서 전송할 URL 값을 설정합니다. (action=login-processing-url)

여기서 주의할 점은 로그인 URL 인터셉터를 모든 리소스를 차단하는 인터셉터의 위쪽으로 배치시켜야 한다는 것입니다. 만약 그렇지 않다면 리디렉션 순환 오류로 정상적인 로그인 창이 뜨지 않으실 겁니다.

글이 길어지는 관계로 2장은 여기까지 마치고 3장에서 미처 다 하지 못한 <http>관련 어트리뷰트 설정법에 대해 자세히 다루도록 하겠습니다.
: