2012/02/25 09:09
1장 스프링 시큐리티란?
드디어 대망의 스프링 시큐리티입니다! 이것이 정녕 막판 보스는 아니지만 현재까지의 개발환경에서 스프링 시큐리티만 어느 정도 가닥이 잡힌다면 웹서비스 제작에 필요한 대부분의 요소는 자리를 잡게 되는 셈입니다. 게다가 스프링 시큐리티는 매우 지능적이며 대부분 간단한 선언만으로 작동하므로 대량의 코드를 손쉽게 절약해줍니다.
그러므로 스프링 시큐리티를 이용하는 것만으로도 고철자물쇠에서 최첨단 10 중 보안장치를 설치하는 것과 동일한 효과를 얻을 수 있겠다 말하겠습니다.
스프링 시큐리티는 강력하면서도 쉽습니다. 게다가 단 몇십줄의 코드만으로도 대형 웹서비스사와 비슷한 수준의 보안을 유지할 수 있다는 장점이 있습니다. 물론 진짜로 비슷한 수준을 유지하려면 적정 수준의 튜닝이 필요하겠지만 그 튜닝의 기반으로 삼기에 스프링 시큐리티는 정말 최상 중 최상의 선택입니다.
한가지 단점은 아직 한국에서 스프링 시큐리티에 대한 활용이 미비한 상태인데다 제대로된 포럼글이나 최신버전에 맞는 설명이 많지 않다는 것입니다. (덕분에 저도 문서를 작성하기 위해 필요한 정보를 찾느라 정말 애먹었습니다. 영어 모르는게 정말 서럽더군요.) 스프링 시큐리티가 ACEGI란 이름으로 시작해, 세상에 나온지 벌써 10년 가까이 됬음에도 아직까지 큰 관심이 없다는 것은 한국이 아직도 웹서비스의 발전이 미미하거나 보안에 대해 크게 간과하면서 많은 관심이 두고있지 않다는 사실일지도 모릅니다.
앞으로 이 포스트는 최소 3부 이상의 기나긴 마라톤 포스트가 될 예정이며 단순히 스프링 시큐리티를 활용하는데 그치지 않고 보안의 기본상식이나 개념에 대해 알아가는 시간이 되도록 할 예정입니다.
그리고 이건 개인적인 이야기지만 시중에 나온 스프링 시큐리티3란 서적이 있긴 있는데 한글을 읽으면서도 외계어 읽는 듯한 기분을 느끼길 좋아하신다면 구매하셔도 상관은 없습니다… 그런 이유가 아니시라면 개인적으로 구매는 권장하지 않습니다. (현재까지 프로그래밍 서적에서 비추하는 책은 iBATIS 인 액션과 더불어 바로 이 책입니다. iBATIS 인 액션은 개발진이 직접 쓴 책이지만 솔직히 책이 필요없을 정도로 라이브러리가 너무 쉽습니다...;;)
보안이란?
위의 두 단어는 스프링 시큐리티 뿐만이 아니라 일반 보안에서도 핵심 축입니다. 개인적으로 너무나 중요하다 생각되어 하단 한글명에 욕심을 부려보았습니다. 게다가 많은 사람들이 Authentication과 Authorization의 이름이 비슷해 "명사형, 동사형 정도의 차이겠지"라며 착각하실 때가 종종 있는데 두 단어의 뜻은 전혀 다릅니다. 그러므로 글을 읽으시면서 비슷한 단어니까 비슷한 뜻이겠지… 라고 넘어가서는 절대 안됩니다. 한글 발음으로는 아우덴티케이션, 아우토리제이션입니다.
먼저 인증의 종류부터 알아봅시다. 인증에는 여러가지 종류가 있지만 보통 3가지로 분류하곤 합니다.
크리덴셜(Credential:자격) 기반 인증 : 우리가 웹에서 사용하는 대부분의인증 방식은 크리덴션 기반의 인증 방식입니다. 즉 권한을 부여받는데 1차례의 인증과정이 필요하며 대개 사용자명과 비밀번호를 입력받아 입력한 비밀번호가 저장된 비밀번호와 일치하는지 확인합니다. 일반적으로 스프링 시큐리티에서는 아이디를 프린시플(principle), 비밀번호를 크리덴셜(credential)이라고 부르기도 합니다.
이중 인증(Two-factor authentication) : 한번에 2가지 방식으로 인증을 받는 것을 말합니다. 예를 들어 금융, 은행 웹어플리케이션을 이용해 온라인 거래를 하실 때에는 로그인과 보안 인증서, 2가지 방법으로 인증을 받곤 합니다. 별 것 아닌 것 같지만 Authentication이 하나 추가됨으로서 프로그래밍 적으로 변화해야 할 부분은 상당히 광범위해집니다.
물리적인 인증 : 이 부분은 웹의 영역을 벗어난 것이지만 가장 효과적인 보안 수단 중에 하나입니다. 예를 들어 컴퓨터를 킬 때 지문을 인식받는다거나 키를 삽입해야 하는 것들 말입니다.
앞으로 우리가 스프링 시큐리티를 이용해 구현해나갈 인증(Authentication)은 눈치채셨겠지만 바로 크리덴셜(Credential) 인증입니다. 그리고 한가지 말씀드리고 싶은 것은 보안용어에 대해 잘 모르신다면 여기서 나오는 영어 단어들을 유심히 살펴보고 한번씩 써보는 것이 좋습니다.
왜냐하면 이런 용어들이 앞으로 스프링 시큐리티의 클래스, 또는 메서드 명으로 나오게 되며 보안용어에 익숙치 않은 독자는 앞으로 문서를 읽어 나가면서 굉장히 혼란스러울 수도 있기 때문입니다. 그러므로 이런 문제를 미리 방지하기 위해서라도 사전적 의미만으로 표현할 수 없는 보안용어의 참 의미를 미리 숙지할 수 있도록 주의깊게 읽으셔야 합니다.
이제 인증(Authentication)의 종류에 대해 어느 정도 이해가 되셨면 다음은 권한부여(Authorization) 차례입니다. 권한부여에는 크게 2가지로 나뉠 수 있습니다.
부여된 권한(Granted Autority) : 적절한 절차로 사용자가 인증되었다면 권한을 부여(Granted Authority)해야 할 것입니다. 회원가입 등을 통해 반영구적인 권한이 부여됬다면 우리는 이 회원에게 부여된 권한을 어딘가에 저장해야 하구요. 만약 해당 사용자가 로그인을 했는데 메인 페이지로 넘어갈 수 없다면 권한부여에 문제가 있다는 것이겠죠.
리소스의 권한(Intercept) : 사용자의 권한만 있다고 보안이 제대로 동작할리는 없습니다. 보안이란 본래 권한이 없는 자들이 원천적으로 리소스에 접근할 수 없도록 막아내는 것이기 때문입니다. 그런 의미에서 적절한 권한을 가진자만 해당 자원에 접근할 수 있도록 자원의 외부요청을 원천적으로 가로채는 것(Intercept)이 웹보안, 그 중 권한부여(Authorization)의 핵심 원칙이라 할 수 있겠습니다.
위의 보안 용어들은 중요한 개념이라고도 할 수 있지만 한편으로는 골치아픈 해결과제라고도 할 수 있습니다. 즉 우리가 위와 같은 주요 개념들에 입각해 구현해야 할 보안을 생각해본다면 일단 우선적으로 "어떤 방식으로 권한을 부여할까?", "해당 리소스에 어떻게 권한수준을 부여하지?", "인증받은 사람은 어떻게 인증받았다는 정보를 지속적으로 유지할 수 있을까?"와 같은 골치아픈 과제들이 산더미같이 존재할 수 있을 것입니다. 게다가 문제들을 해결하는 것도 힘든 일이지만 일을 더욱 더욱 어렵게 만드는 것은 보안이 아무리 잘해도 티 안나고 한번만 실수해도 독박은 다 뒤집어쓰는 전업주부 방식의 프로그래밍 분야이라는 것입니다.
그러므로 위와 같은 문제들의 올바른 해결을 위해서라도 스프링 시큐리티의 손을 한번 더 들어주고 싶어집니다. 스프링 시큐리티는쉽고 편리하다는 장점도 있지만 이런 보안 개념이 없는 자가 보안이 필요한 쇼핑몰이나 주요 웹사이트를 설계했다고 가정했을 때 발생할 막대한 피해들을 방어할 수 있는 최선의 선택입니다. 스프링 시큐리티는 이런 과제들을 거의 10년 가까이 연구하며 발전해온 뛰어난 보안 프레임워크라는 점을 볼 때 우리에게 스프링 시큐리티는 선택이 아니라 필수라고도 할 수 있습니다.
이제 보안의 핵심용어와 스프링 시큐리티의 중요성에 대해 어느 정도 설명했으니 스프링 시큐리티에 대해 좀 더 깊이 들어가 발생하는 문제에 어떤 해결방식을 선택했는지 차근차근 집어나가보려고 합니다.
리소스의 권한(Intercept)
리소스의 권한이 영어로 Intercept가 아니라는 것쯤은 저도 스타크래프트를 조금 해봤으므로 알고 있습니다. 그럼에도 Intercept라는 말을 리소스 권한 옆에 당당히 붙여놓는 것은 보안에서 리소스에 접근권한을 설정하는 것이 바로 Intercept 방식으로 작동하고 있기 때문입니다. 아무리 서버 성능이 좋고 직원이 많더라도 우리가 가지고 있는 모든 리소스에 일일이 권한을 설정할 수는 없는 노릇입니다.대신에 우리가 @MVC에서 보았듯 DispatcherServlet처럼 멋지게 클라이언트의 요청을 가로챌 수만 있다면 간단히 문제를 해결할 수 있을 것입니다.
개인적으로 @MVC의 개발에 간접적으로나마 스프링 시큐리티가 많은 공헌을 하지 않았을까 생각하는데 그 이유는 스프링 시큐리티가 이미 ACEGI 때부터 Filter를 이용해 클라이언트의 요청을 가로채는 하는 방식을 굉장히 오래 전부터 개발해놨었기 때문입니다. (Spring Security는 스프링 커뮤니티에 합류하기 전까지 Acegi라는 이름의 프레임워크였습니다. 이름이 Acegi인 이유는 13579순서대로 알파벳을 정렬했다고 하는군요.) 그렇기 때문에 스프링 시큐리티는 @MVC의 DispatcherServlet이나 AOP를 이용해 프록시를 생성하지 않고 아주 오래 전부터 사용해온 고유의 DelegatingFilterProxy 클래스를 사용합니다. 물론 DelegatingFilterProxy를 이용하면서도 AOP 포인트컷의 활용 또한 가능합니다.
먼저 기존의 제작된 프로젝트에 다음과 같이 web.xml에 DelegatingFilterProxy를 등록해 봅시다.
<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>
여기서 주의할 점은 <filter-name>의 값이 반드시 springSecurityFilterChain이어야 한다는 점입니다. 왜 꼭 이름을 springSecurityFilterChain으로 지어야 하냐면 DelegatingFilterProxy 클래스는 setTargetBeanName(String)이라는 메서드를 갖고 있는데 이 메서드는 실제 요청을 처리할 필터를 주입받습니다. 만약 이 메서드를 통해 구현할 필터빈을 주입받지 못한다면 DelegatingFilterProxy 클래스는 기본값으로 <filter-name>의 값과 동일한 빈이 스프링 컨텍스트에 존재하는지를 검색하게 됩니다.
그러나 곧 알게 되겠지만 DelegatingFilterProxy 클래스가 springSecurityFilterChain이란 빈이 필요하다고 직접 빈을 만들어줄 필요는 없습니다. 왜냐하면 springSecurityFilterChain은 스프링 시큐리티의 inner bean이기 때문에 자동으로 생성되기 때문이죠.
이런 관점에서 본다면 DelegatingFilterProxy는 굳이 스프링 시큐리티 에서만 사용할 게 아니라 다른 목적의 필터 체인으로도 충분히 사용될 수 있는 확장성이 존재합니다. 아마 스프링 제작진들도 이 클래스가 스프링 시큐리티에서 탄생했지만 역할의 중요성을 인식해 시큐리티 내부 패키지에 위치시키지 않고 org.springframework.web.filter에 등록시킨 것 같습니다.
아마 이쯤되면 어떤 예리한 독자 분이 "DispatcherServlet이 이미 모든 요청을 가로채는데 또 DelegatingFilterChain이 가로채면 우선순위는 어떻게 결정되나요?" 라는 질문을 던지실 수도 있겠군요. 정답만 이야기하자면 필터가 우선순위가 됩니다. web.xml을 보면 알다시피 DispatcherServlet은 서블릿으로 등록되있고 DelegatingFilterChain은 필터로 등록되어 있습니다. 만약 이런 상황에 서로 겹치는 URL이 요청으로 들어온다면 필터는 프록시 패턴처럼 서블릿을 샌드위치 해버립니다. 이런 점에서 본다면 우리는 왜 DelegatingFilterChain이 필터로 만들어졌으며 DispatcherServlet이 왜 서블릿으로 만들어졌는지 이해할 수도 있겠죠.
이제 필터를 이용해 모든 URL이 DelegatingFilterProxy를 통과하도록 설정했다면 컨텍스트를 설정할 차례입니다. 보안에 대해서 따로 관리해줄 컨텍스트 파일을 만드는 것이 좋으므로 security-context.xml이란 파일을 새로 만들도록 합시다. 그리고 완성된 빈은 web.xml에 등록하는 것도 잊지 말구요. 여기까지 완료하시면 스프링 시큐리티를 사용하기 위한 기본 세팅은 마무리 됩니다.
어떠셨나요? 글을 읽으시면서 어느 정도 보안에 대해 감이 오시나요? 만약 아무리 읽어도 감이 오지 않는다면 스스로 의문을 만들고 그 답을 찾아보도록 노력해보세요. 개인적으로 스프링 시큐리티 API문서를 찾아보시길 권장합니다 ^^; 그럼 글이 길어지는 관계로 이번 장에선 보안의 기본원리와 스프링 시큐리티의 기본 세팅까지 다루고 다음 장부터 본격적으로 security-context.xml을 이용한 보안설정을 다루도록 하겠습니다. 여기까지 읽어주셔서 감사하구요. 좋은 하루 되세요 :D
출처 - http://springmvc.egloos.com/504862
2012/02/26 00:44
2장 Intecept와 Granted Authority
리소스의 권한(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>관련 어트리뷰트 설정법에 대해 자세히 다루도록 하겠습니다.
출처 - http://springmvc.egloos.com/506465
2012/03/01 08:30
3장 데이터베이스로 이동하는 Authentication 정보
로그아웃 커스터마이징
이제 로그아웃 페이지를 커스터마이징 해볼까 합니다. 만약 로그아웃 주소를 커스터마이징 하지 않는다면 기본적으로 "/j_spring_security_logout"이란 URL을 통해 사용자의 권한을 해제하는 작업을 시작할 수 있습니다. 그러나 이런 눈에 뻔히 보이는 URL을 이용하면 악의적인 공격자가 해당 어플리케이션의 보안이 스프링 시큐리티로 이루어진 것을 알아채고 쉽게 보안취약점을 찾아낼 지도 모릅니다.
<http>에 다음과 같은 요소를 추가함으로서 손쉽게 로그아웃 경로를 커스터마이징 할 수 있습니다.
<logout invalidate-session="true" logout-url="/unAuthentication" logout-success-url="/" />
invaldate-session : 세션을 모두 무효로 할 것인지를 사용자에게 묻습니다.
logout-url : 로그아웃 경로를 설정합니다.
logout-seccess-url : 로그아웃이 성공한 뒤에 이동한 경로를 설정합니다.
MySQL로 Authentication 구현하기
2장에서 말했다시피 서비스를 상용화 하기 위해선 Authentication(인증) 부분을 DB로 이전해야 합니다. 이게 무슨 말이냐면 해당 사용자의 권한(Authority)과, 세부정보(UserDetails)들을 모두 DB에 기록하고 스프링 시큐리티는 그 정보들을 쿼리를 이용해 가져오는 방식으로 바뀐다는 뜻입니다. 당연한 듯 해 보일 수도 있겠지만 이게 기본적인 인증과 권한부여에 대한 개념이 잡혀있지 않으면은 금새 삼천포로 빠지기 쉽상입니다.
더욱이 상용 서비스로 오픈하기 위해서는 인증 자료의 DB이전은 필수 중 하나이며 Authentication을 위한 정보들을 하나도 남기지 않고 말끔히 DB로 옮기기 때문에 이런 과정들이 조금 복잡하고 이해가 가지 않을 수도 있습니다. 일단 쿼리문에 익숙하지 않은 사용자들은 이 부분에서 많이 답답하실 수도 있으므로 MySQL과 쿼리문에 대해서는 따로 사이드 패널을 만들어 정보를 올리도록 하겠습니다.
CREATE TABLE users (
principle int auto_increment primary key,
id varchar(50) not null,
crc_id int not null,
password varchar(50) not null,
enabled boolean not null,
INDEX(crc_id)
);
먼저 위와 같이 사용자의 정보를 담고 있는 테이블을 만들어 봅시다. 더 필요한 정보가 있으시다면 ALTER를 이용해 추가하셔도 상관없지만 지금은 우선적으로 로그인과 권한의 DB이전을 목적으로 하므로 필요한 최소한의 정보만 기입하도록 합시다. 위와 같은 인덱스 구조는 필자가 주로 사용하는 방식이므로 마음에 안드신다면 문서를 모두 읽으신 뒤에 인덱스 설정을 바꾸셔도 상관 없습니다.
'principle' 은 사용자의 유일값을 담는 컬럼입니다. 1장에서 필자가 아이디를 principle이라고 부르기도 한다 했지만 정확히 말하자면 어떠한 값과도 일치하지 않는 사용자의 고유값을 나타내므로 위처럼 id와 분리하여 사용해도 괜찮습니다. principle은 동일한 컬럼 내에서 어떠한 값과도 일치해서는 안되므로 해당 컬럼에 primary key 속성을 부여하였고 별도의 유일값 인증 로직을 구현하지 않기 위해 auto_increment로 자동증가값을 삽입하였습니다. (만약 principle이 공개키가 아닌 보안키라면은 auto_increment로 구현해서는 안됩니다.)
crc_id는 id 컬럼을 crc32로 암호화한 데이터입니다. 왜 해당 컬럼을 crc32로 암호화하여 따로 저장하느냐면 바로 속도 때문인데요. 'id'같이 스트링 값이 들어가는 컬럼을 primary key나 index로 설정한다면 인덱싱 속도가 엄청나게 느려지게 됩니다. 왜 지대한 영향을 끼치게 되는지는 추후에 다른 문서를 통해 설명하도록 하고 여러분은 일단 varchar과 char를 인덱스로 설정하는 것은 가급적 피해야될 사항이라는 것만 기억하시면 됩니다.
Crc32.java
CRC32는 MD5나 SHA1보다 가벼운 데이터 암호화를 위해 만들어진 기법입니다. CRC32의 암호화 값은 모두 int 범위 안에 속하므로 주로 데이터 인덱싱을 위해 자주 사용되곤 합니다. 다만 주의할 것은 이 값을 유일값으로 설정하면은 안되는데, 그 이유는 CRC32는 확률적으로 동일한 암호화 값이 나올 가능성이 다른 암호화 기법에 비해 매우 높기 때문입니다.
enabled는 해당 유저를 활성화할 것인지 비활성화 할 것인지를 묻는 컬럼입니다. 이 컬럼은 필수컬럼이며 향후 해당 사용자의 기록을 지우지 않고서도 비활성화 할 수 있는 기능을 제공할 것입니다.
스프링의 계층별, 기능별 패키지 구성하기
필자는 스프링 시큐리티를 공부하면서 바로 이 테이블을 작성하는 과정에서 너무 많은 의문이 들어 쉽사리 다음 과정으로 이동할 수가 없었습니다. 거의 몇일을 여기에 목매며 왜 꼭 이래야만 하냐는… 고민을 수없이 되뇌였습니다. 이유는 알고 싶었는데 어떻게 알아가야 하는지 방법은 도통 알 수가 없었죠. 그러다가 문득 든 생각이 스프링 시큐리티의 커맨드 오브젝트(테이블과 매핑되는 자바빈 클래스가)를 역추적하면 이유를 알 수 있겠다는 것이었습니다.
이와 관련해서 잘 이해가 가지 않으신다면 위의 링크된 문서를 읽어보시는 것도 좋습니다. 일단은 여기서 대략적으로 설명드리자면 자바 프로그래밍에서는 자바빈 클래스는 대개 테이블과 밀접한 관계를 맺곤 합니다. 그렇다면… 만약 스프링 시큐리티에서도 테이블의 정보를 담는 빈이 있고 우리가 이 빈을 참조할 수 있다면 왜 이런 테이블 구조를 구성해야 하는지 알 수 있지 않을까요?
이 생각은 그대로 통했습니다. 스프링 시큐리티에서는 테이블에 매핑되는 정보를 User란 자바빈 클래스를 가지고 있었고 테이블과 관계된 내용만 요약해서 보자면 아래의 코드를 통해 이해할 수 있었습니다.
public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
this(username, password, true, true, true, true, authorities);
}
public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
if (((username == null) || "".equals(username)) || (password == null)) {
throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
}
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
}
User 클래스는 UserDetails란 인터페이스를 상속한 클래스이며 위와 같은 생성자를 갖고 있습니다. User 클래스는 오로지 생성자만을 통해 프로퍼티의 설정이 가능하므로 굳이 클래스 전체를 보지 않더라도 위의 생성자만으로 이해가 가능합니다. 여기서 enabled를 제외한 나머지 불리언 값들은 계정의 만료와 락 설정이며 기본적으로 true로 설정되고 있으므로 여기에서는 설명을 생략합니다.
차근차근 프로퍼티를 하나씩 살펴보자면 먼저 username은 테이블의 principle과 일치합니다. 이름이 달라 헷갈릴 수도 있지만 다른 값과 구별되는 유일한 값이라 생각하면 이해하기 쉬우실 겁니다. 물론 principle 컬럼 보다는 crc_id가 유일값으로 적당하지 않겠냐고 생각할 수도 있지만 crc32로 만든 암호화 코드는 데이터가 1만개 이상일 때 약 1%의 확률로 서로 일치하는 값이 생기므로 유일값으로 적당하지 않습니다. (그렇기 때문에 unique index가 아닌 일반 index로 설정해둔 것입니다.)
password는 username의 principle을 확인하는 크리덴셜이며 테이블의 password와 동일합니다. 그 다음 Collection<? extends GrantedAuthority> authorities는 이 유저의 권한을 뜻하는데 좀 이상하게 authority(권한)의 복수형인 authorities(권한들)로 되어 있는데다 Collection 인터페이스 형태로 되어 있습니다. 눈치 채셨겠지만 그 이유는 한 사용자가 하나 이상의 권한을 가질 수 있게 설정할 수 있게 하기 위해서입니다.
근데 authorities를 보면서 조금 싸한 느낌과 함께 이상한 의문이 번뜩 미간을 스칩니다. 왜냐하면 우리가 설정한 테이블에서 authorities는 없었거든요! 스프링 시큐리티에서 users 테이블에 authorities를 함께 설정하지 없는 이유는 하나 이상의 권한을 설정하기 위해서이기도 하지만 또 하나의 이유는 확장성 때문입니다. 지금은 자세히 설명할 수 없지만 스프링 시큐리티에서는 사용자에게 직접 권한을 부여하는 방식과 그룹으로 묶어 권한을 부여하는 방식, 2가지를 제공합니다.
그리고 우리가 지금부터 할 것은 사용자에게 직접 권한을 부여하는 방식입니다. 다음과 같은 권한 테이블을 하나 더 만들어 보도록 합시다.
create table authorities (
principle int not null,
authority varchar(50) not null
unique index ix_auth_principle (principle, authority)
);
이 테이블은 users 테이블과 밀접한 관계를 맺고 있으며 authorities.principle과 users.principle은 서로 일치하므로 레퍼런스 키로 두 키를 묶어줄 수도 있지만 원할한 속도와 MySQL 기능에 너무 의존하지 않기 위해 이런 밀접한 관계는 트랜잭션으로 해결하는 것이 좋습니다.
이제 어느 정도 감을 잡으셨으리라 생각합니다. 글이 길어지는데다 제 지식이 바닥나가는 관계로 오늘은 여기까지 하고 다음 장에서 스프링 시큐리티의 JdbcDaoImpl을 MyBatisDaoImpl로 대체해보고 스프링 시큐리티의 확장 가능성에 대해 이야기 해보도록 하겠습니다.
출처 - http://springmvc.egloos.com/516241
2012/03/02 16:54
4장 JdbcDaoImpl의 커스터마이징.
JdbcDaoImpl을 이용한 토큰 생성
이제 본격적으로 DB를 이용한 로그인 서비스를 완성 해보도록 하겠습니다. 설명드리기 이전에 제가 저번 장에서 JdbcDaoImpl을 MyBatisDaoImpl로 대체하겠다고 말씀드렸었는데 이게 직접 시도해본 결과 몇가지 문제점이 있다는 것을 알게 되었습니다. 정확히 말하자면 완전히 불가능한 사항은 아니지만 User 클래스(UserDetails 인터페이스를 구현한…)가 내부 프로퍼티들의 get,set 메서드를 가지고 있지 않고 오로지 생성자만을 통해 해당 클래스 생성이 가능한데다가, 생성자만을 통해 User 클래스를 만들 수 있게 한 이유가 보안 때문인지 아니면 단순히 설계 때문인지 명확하지 않기 때문입니다.
그러므로 이 문제가 확실해지기 전까지 이번 장에 JdbcDaoImpl을 MyBatisDaoImpl로 확장하지 않고 JdbcDaoImpl의 기능을 조금 수정한 CrcJdbcDaoImpl 클래스를 만들어 문제를 해결해 보도록 하겠습니다.
근데 곰곰히 생각해보니 제가 JdbcDaoImpl이 무엇인지도 제대로 설명하지 않고 무작정 횡설수설한 것 같네요. 우선 JdbcDaoImpl을 확장하기 전에 이 클래스가 무슨 역할을 하는지부터 설명드리도록 하겠습니다.
JdbcDaoImpl의 위치를 그림으로 설명해보자면 위의 이미지와 같습니다. JdbcDaoImpl은UserDetailsService 인터페이스를 구현한 클래스이며 데이터베이스에서 정보를 가져와 스프링 시큐리티에서 사용할 수 있게끔 UserDetails 형태로 가공해 줍니다. 그리고 스프링 시큐리티에서 제공하는 또 하나의 UserDetailsService, JdbcUserDatailsManager는 JdbcDaoImpl을 확장한 클래스이며 기본적인 CRUD(Create · Read · Update · Delete) 작업과 그룹설정을 추가한 클래스입니다. 그러나 JdbcUserDetailsManager는 쿼리문을 클래스 내부에 삽입해둔데다 쿼리문을 컨텍스트 파일에 직접 설정해줘야 하는 불편함이 있습니다.
단순히 스프링 시큐리티에서 제공하는 기본 기능에 만족하는 사용자라면 JdbcUserDatailsManager를 이용해도 상관없지만 궁극적으로 스프링 시큐리티를 자신의 서비스에 최적화 되게끔 튜닝해서 사용하고 싶다면 이 클래스를 사용하는 대신 직접 JdbcDaoImpl을 확장해 사용하시는 것이 좋습니다. 더욱이 우리가 MyBatis를 통해서 쿼리문과 서비스의 완전한 분리시키길 원했던 것처럼 완전히 독립적인 코드의 사용을 위해서라도 JdbcUserDatailsManager 클래스를 이용하는 것은 자제합시다.
이제 본격적으로 JdbcDaoImpl을 튜닝할 차례입니다. 필자는 이 클래스를 MyBatis로 변환하려고 한차례 클래스를 완전히 뜯어 고쳤었는데 스프링 시큐리티에서 JdbcTemplate를 사용하는 것이 영 마음에 들지 않았었거든요. 근데 뜯어고치는 와중에 위에도 말했다시피 MyBatis로 ORM을 교체하려면 User 클래스의 생성자 주입을 get, set 방식으로 새로 구성해야 한다는 사실을 깨닫고 User 클래스까지 새로 구현해볼 작정이었습니다.
근데 User 클래스를 자세히 들여다보니 이 클래스에는 set 메서드가 아예 존재하지 않았던 겁니다. 하나만 없으면 모르겠는데 사용자의 크리덴션을 생성하는 클래스가 프로퍼티의 set 메서드를 전부 없앴므로 이건 뭔가 이유가 있다는 거겠죠. 그래서 그동안의 작업을 전부 지우고 간단하게 Crc32를 이용해 아이디를 검색하는 CrcJdbcDaoImpl 클래스로 바뀌게 되었습니다.
public class CrcJdbcDaoImpl extends JdbcDaoImpl {
@Override
protected List<UserDetails> loadUsersByUsername(String username) {
String crcCode = String.valueOf(new Crc32().getCode(username));
return getJdbcTemplate().query(getUsersByUsernameQuery(), new String[] {crcCode, username}, new RowMapper<UserDetails>() {
public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException {
String username = rs.getString(1);
String password = rs.getString(2);
boolean enabled = rs.getBoolean(3);
return new User(username, password, enabled, true, true, true, AuthorityUtils.NO_AUTHORITIES); }
});
}
}
새로 만든 클래스는 JdbcDaoImpl을 상속하였으며 위의 메서드 하나만 오버라이딩 하면 됩니다. 간단히 위의 클래스를 설명하자면 원래 JdbcDaoImpl를 이용하면 varchar값을 인덱스로 설정할 수 밖에 없는 탓에 사용자가 늘어날수록 로그인 속도가 줄어드는 것을 줄이기 위해 Crc32로 암호화한 int값을 해시코드(crc_id)로 설정했었죠. 이 CrcJdbcDaoImpl 클래스는 앞으로 스프링 시큐리티가 로그인을 시도하는 이용자의 아이디를 검색할 때 아이디와 더불어 int값의 암호화 인덱스를 타게 해줍니다. 현재로써는 속도가 개선됬는지 잘 못느끼시겠지만 사용자가 늘어날수록 인트값 인덱스와 varchar값 인덱스의 속도차는 어마어마해집니다.
이제 JdbcDaoImpl을 작성하였다면 이걸 컨텍스트에 등록할 차례입니다.
<authentication-manager>
<authentication-provider user-service-ref="securityService" />
</authentication-manager>
<beans:bean id="securityService" class="com.billions.market.security.CrcJdbcDaoImpl">
<beans:property name="dataSource" ref="dataSource" />
<beans:property name="usersByUsernameQuery">
<beans:value>
SELECT principle, password, enabled
FROM users
WHERE crc_id = ? AND id = ?
</beans:value>
</beans:property>
<beans:property name="authoritiesByUsernameQuery">
<beans:value>
SELECT principle, authority
FROM authorities
WHERE principle = ?
</beans:value>
</beans:property>
</beans:bean>
위의 컨텍스트 예제를 여러분의 설정에 맞춰서 바꾸시면 됩니다. 이 설정을 보시면 쿼리문이 직접 컨텍스트에 등록되있는 것을 알 수 있는데 사실 제가 이런 것을 너무 싫어해서 MyBatis로 바꾸려다 보안으로 인해 딱 여기까지만 한수접은 부분입니다. 보안을 위해서였다 생각하시고 만약에 여기에 쿼리문을 등록하시기 싫으시다면 CrcJdbcDaoImpl 클래스를 더욱 확장해서 아예 쿼리문을 CrcJdbcDaoImpl 안에 박어 넣는 방법도 있습니다. 그러나 저는 혹시나 추후에 쿼리문을 수정할 일이 생길지도 모를 것 같아 이렇게 컨텍스트에서 직접 작성하는 방식을 택하였습니다.
컨텍스트에 등록한 프로퍼티를 하나씩 설명하자면 usersByUsernameQuery는 Username 즉, 로그인시 아이디 값을 통해 User 객체를 생성하는 쿼리문을 말합니다. 위에 제가 만든 클래스가 바로 이 쿼리문을 이용하게 될거라면 이해하시기 편할겁니다. 원래는 검색조건이 하나 밖에 안됬었는데 살짝 수정해서 2개가 들어갈 수 있도록 바꾼 것 뿐입니다 ^^;
그리고 authoritiesByUsernameQuery는 사용자의 권한을 가져오는 쿼리를 일컫습니다. 위의 쿼리문을 자세히 관찰하신다면 얻을 수 있는 결론이지만 우리가 이런 권한을 가져올 때 username 대신에 principle을 조건문으로 사용할 수 있는 까닭은 usersByUsernameQuery에서 쿼리문을 통해 principle 값을 가져왔기 때문입니다.
SELECT principle, password, enabled FROM users WHERE crc_id = ? AND id = ?
return new User(username(principle이 들어감), password, enabled, true, true, true, AuthorityUtils.NO_AUTHORITIES);
여기서 위의 쿼리문의 순서는 매우 중요합니다. 왜냐하면 바로 아래 User 객체를 만드는 생성자에 쿼리문에서 불러들인 순서대로 값이 입력되기 때문이죠. 만약 쿼리문의 순서가 뒤죽박죽이 된다면 값이 제대로 안들어간다거나 오류가 발생할 수 있습니다.
이제 스프링 시큐리티의 설정이 모두 끝났으므로 Authorities 테이블과 매핑되는 Authority 자바빈 객체를 만들고 컨트롤 할 수 있도록 MyBatis를 활용해 서비스 계층과 DAO 계층 클래스을 만듭니다. 그리고 DB에 users테이블과 authorities 테이블에 각각 로그인 정보와 권한 설정을 해준 뒤 테스트를 해보시면 잘 작동할 것입니다.
지금 스프링 시큐리티에 대해 작성했던 글들을 다시 한번 훑어보고 있는데 부족한 점이 너무 많네요. 나름 스프링 시큐리티에 대해 자세히 설명해드리고자 시작했던 문서였지만 워낙 스프링 시큐리티가 확장이 필수적인 프레임워크인데다 새로 확장해야할 부분을 자세하게 설명하자면 연관있는 모든 클래스를 하나하나 나열해야 하는 탓에 생략된 부분이 너무 많습니다.
아직 많이 살펴보진 못했지만 지금까지의 결과로만 말씀드리자면 스프링 시큐리티는 굉장히 좋은 보안 프레임워크지만 제대로 이용하기 위해선 절대로 기본 세팅만으로 사용할 수는 없는 프레임워크인 듯 합니다. 스프링 시큐리티를 제대로 이용하고 싶으시다면 어떻게든 어거지로 짜맞춰 기본 기능만으로 이용하려하지 마시고 최대한 확장해서 자신의 기능에 맞춰서 사용할 수 있도록 클래스들의 원본을 찬찬히 뜯어보세요.
출처 - http://springmvc.egloos.com/518902
2012/03/03 08:29
5장 스프링 시큐리티에서의 비밀번호 암호화
만약 당신이 만들고 있는 웹사이트가 영리를 목적으로 하고 있다면 반드시 비밀번호를 암호화해야 한다. 비단 비밀번호에만 해당하는 것은 아니지만 고객의 중요정보를 암호화하지 않는 기업은 사용자의 정보를 크래커들에게 무단으로 제공하려는 기업이나 다름이 없다. 현대의 발달된 IT기술에는 이런 악의적인 공격자들을 방어하는 많은 암호화 기술들이 있으며 스프링 시큐리티에도 대략 4가지 정도의 기본 암호화 클래스를 제공하고 있다.
암호화에 있어서 개발자들이 지켜야할 규칙들이 몇가지 있다.
1. 복호화(디코딩)가 불가능한 단방향 암호코드로 만들어져야 한다.
2. 암호는 개발자나 관리자도 알 수 없어야 하며 이용자 이외에는 누구도 접근할 수 없는 형태여야 한다.
3. 공격자가 예측할 수 없도록 솔트 처리를 해주어야 한다.
이러한 규칙은 개발자들이 반드시 준수해야 할 사항이며 비영리의 목적이라 하더라도 사용자의 개인정보는 영리 웹사이트와 똑같은 가치가 있으므로 암호화를 게을리 해선 안된다.
데이터베이스에서 제공하는 암호화
많은 개발자들이 데이터베이스에서 제공하는 간단한 암호화 기술을 이용하고 있으며 필자가 사용하는 MySql에서는 CRC32(), MD4~5(), SHA(), password()와 같은 암호화 함수들이 제공되고 있다. 근데 곰곰히 생각해보면 데이터베이스에서 제공하는 암호화 함수로 사용자의 정보를 암호화 해도 되는 것인지는 조금 생각해볼 여지가 있다.
select sha('비밀번호');
당신 회사의 수석 프로그래머가 보다 빠른 개발을 위해 따로 암호화 단계를 자바에서 생략하고 위와 쿼리문을 통해 MySQL에서 제공하는 암호화 함수를 이용한다고 생각해보자. 이런 방식이라면 암호화가 매우 간편하고 사용이 용이해진다는 장점을 얻을 수 있지만 극명하게 드러나는 단점은 당신이 스프링을 이용함으로써 얻는 개방 폐쇄 원칙(Open-Close Principle)을 철저히 파괴한다는 점이다.
만약 이런 방식으로 비밀번호를 암호화하여 서비스 개발을 완료한 뒤 당신의 서비스가 매우 성공적인 성과를 올렸다 생각해보자. 자사는 서비스의 확장을 꾀하고 있으며 곧 비싸고 기술지원 서비스까지 해주는 유료 소프트웨어들을 도입하고자 한다. 그 중에는 10g과 같은 오라클 데이터베이스도 포함되어 있다.
만약 이러한 상황이 닥친다면 당신이 사전에 구현해 놓은 서비스는 매우 치명적인 단점을 지니게 된다. 왜냐하면 데이터 이전 중에 가장 중요한 비밀번호를 MySQL에서 암호화하였으므로 오라클에서는 이 암호화된 비밀번호를 풀 수 없게 되기 때문이다. 더욱이 서로 다른 형태의 데이터베이스를 이용하고 있다면 당신은 매우 복잡한 로직으로 서로 다른 데이터베이스에서 다른 암호화 방식을 사용하게끔 설계해야만 할 것이다.
그러므로 당신이 OCP의 원칙에 따라 설계하고자 한다면 데이터베이스의 암호화 기능에 크게 의존하는 방식을 택해서는 절대로 안될 것이다. 데이터베이스의 기능을 의존하더라도 모든 데이터베이스에서 지원하는 공통요소를 이용해야지 특정 데이터베이스에서 제공하는 서비스를 이용했다간 본의 아니게 서비스 확장에 큰 장애를 만들게 될지도 모른다.
물론 많은 중소 웹사이트들이 위와 같이 데이터베이스 단계에서 암호화를 많이 활용하고 있다. 그 이유는 우리가 일전에 트랜잭션을 설명하면서 했던 것과 동일하게 코드 상의 복잡함을 줄이기 위한 불가피한 선택이었을 것이다. 허나 스프링 시큐리티를 이용한다면 이런 복잡한 암호화 단계를 개발영역으로 끌어들임과 동시에 코드 상의 복잡함도 대부분 축소할 수 있게 된다. 더욱이 스프링 시큐리티는 암호화 클래스를 빈으로 설정한 뒤 따로 주입받아 사용하기 때문에 기능의 확장에 있어서도 매우 자유롭다.
<authentication-manager>
<authentication-provider user-service-ref="securityService">
<password-encoder ref="passwordEncoder" />
</authentication-provider>
</authentication-manager>
<beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" />
securityService는 우리가 전장에서 만들었던 CrcJdbcDaoImpl 클래스를 주입한 것이며 스프링 시큐리티에서 기본으로 제공하는 Sha 인코더로 텍스트를 인코딩해보자. 먼저 패스워드를 인코딩하는 빈을 설정하고 위와 같이 <password-encoder> 요소에 주입시키면 앞으로 로그인 시에 시큐리티 필터가 자동으로 해당 패스워드를 암호화하여 서버로 전송시킬 것이다.
물론 여기서 끝이 아니다. 지금과 같은 상황이라면 저장 시에는 일반 텍스트로 저장되고 로그인 시에는 인코딩한 패스워드가 비교되므로 기능이 정상적으로 작동하지 않는다. 제대로 기능을 동작시키려면 우리가 암호화한 코드를 전송시킨 것과 똑같이 DB에 입력할 비밀번호도 같은 빈으로 암호화시켜 저장시켜야만 한다.
여기서 비밀번호를 저장시키는 클래스는 사용자가 직접 작성한 코드 일수도 있고 시큐리티에서 제공하는 JdbcUserDetailsManager 클래스일 수도 있다. 여하튼 방식은 동일하게 위의 passwordEncoder 빈을 주입받아 다음과 같은 메서드로 인코딩한 값을 저장시켜주면 된다.
@Autowired PasswordEncoder passwordEncoder;
passwordEncoder.encodePassword("패스워드", null);
출처 - http://springmvc.egloos.com/520257