외부 프로그램 실행시키기

 

JDK 1.4 버전 이하에서는 exec() 메소드를 이용하여 외부 프로그램을 실행 시킬 수 있고

JDK 1.5 버전 이상에서는 ProcessBuilder 객체를 이용하여 외부 프로그램을 실행 시킬 수 있다.

 

XML? JSON? 이것들은 무엇일까?

XML? JSON? 이것들은 무엇일까?
XML과 JSON은 구조화된 문서를 전송 가능하게 만든 텍스트 포멧 형식을 말한다.

이 둘은 모두 특정한 의미를 가진 데이터를 담는 포멧을 정의한다.

예를 들어 RSS피드의 경우 현재는 XML방식이 널리 사용되는데

RSS정보를 분리하여 정보를 읽을수 있도록 내용을 DB에서 불러와 XML포멧으로 변환한다.

XML 예제코드 보기

일단 XML과 같은 텍스트 방식의 전달방식은 텍스트로만 정보가 이루어 지기 때문에 기기에 구애받지 않고 어느 환경에서건

사용하 가능한  플랫폼으로 부터 자유 스러운 방식이라는 것이다.

이와 같이 기존에 SGML의 복잡성을 줄이고 HTML의 편의성을 지닌 XML은 강력한 통신수단으로 활용되어 져왔습니다.

하지만 곧 강력한 경쟁자가 등장하게 되는데 그것이 바로 JSON!!!

JSON 예제 보기

JSON도 XML과 같이 텍스트 형식의 데이터 포멧을  말하는데요 XML 단점을 보완 했다고 보는게 정확하겠죠.

이쯤에서 내맘대로 비교!

XML의 장점

작성하기가 간편하다(tag구조)
XML 사람이 읽기가 쉽다. (즉 각 장보들이 의미하는 바를 한눈에 보기가 좋다.
DTD 등 XML자체의 기능을 확장할 여지가 많이 있다.

 

XML의 단점

문서의 양이 필요이상으로 많다.(실 데이터가 아닌 tag글자)
배열형식이나 반복구조의 경우 불필요한 데이터가 계속 해서 나타난다.
결국 이로인해 파싱이 힘들어지고 속도는 느려진다.


 

JSON의 장점

내용이 함축적으로 최소한의 정보만을 가지고있다.
그렇기때문에 XML대비 용량이 획기적으로 줄어들고 속도는 그만큼 빨라지게된다.
객체구조와 {} 배열구조의 [] 적절한 만남으로 아주 효율적인 데이터 구성이 가능하다.
파싱이 매우 간편하고, 때로는 일반적인 변수처럼 사용도 가능하다.
즉, 사용하기 쉽다.



JSON의 단점

내용이 함축적이다 보니 내용의 의미파악은 힘들수 있다.
아무래도 적은규격의 데이터 전송에 적합한 방식이기떄문에 XML보다는 빠르지만
대용량급의 데이터 송수신엔 부적합 모습도 있다.

JSON이란?

– JSON은 사실 상당히 간단하고 단순한 개념인데 AJAX와 함께 쓰이면서 뭔가 잘못 이해하는 경우가 많다. 잘 정리된 사이트가 있어 스크랩함.

* 출처 : http://blog.naver.com/musecje?Redirect=Log&logNo=10126963299

=================================================================================================================

– JSON이란 무엇인가?

1. 어떻게 읽으면 되나?

제이선이라고 읽으면 되겠다.

2. 무엇을 줄인 말이냐?

JavaScript Object Notation이라는 이름에서 알 수 있듯이 자바스크립트를 위한 것이고 객체 형식으로 자료를 표현하는 것이다.

3. 이거 프로그래밍 언어냐?

사방팔방에 JSON이라는게 등장하고 각종 사용방법이 나오고 어려워 보이지만 JSON 자체는 정말 별거 아니다. JSON 그자체 는 단순히 데이터 포맷일 뿐이다. 어떠한 통신 방법도, 프로그래밍 문법도 아닌 단순히 데이터를 표시하는 표현 방법일 뿐이다.
간단한 데이터를 xml보다 좀 더 간단하게 표현하기 위해 만든 것이다. XML보다 기능이 적기 때문에 파싱도 빠르고 간단하기 때문에 클라이언트 사이드에서, 특히 모 바일에서 더욱 유용하겠다. 사실 서버 입장에서도 더 유용하기 때문에 많은 서비스들이 XML보다는 JSON을 권장한다.

다시 말하지만 이건 프로그래밍 언어가 아니다.

4. 이거 공식 사이트 없냐?

네이버에서 JSON으로 검색하면 시덥잖은 네이버 자체 컨텐츠들(블로그, 카페, 지식인)만 뜨는데 JSON 공식 사이트는 멀쩡히 있고, 한국어도 지원된다.
http://json.org/json-ko.html
여기 들어가면 좀 당황스러울 수도 있다. 뭔 놈의 공식사이트가 내용이 진짜 간단하고 별 거 없기 때문이다.
위키피디아를 보면 더 잘 설명이 되어 있다.
http://ko.wikipedia.org/wiki/JSON

5. 이거 왜 쓰냐?

단순히 데이터를 받아서 객체나 변수로 할당해서 사용하기 위한 것이다.

6. AJAX와는 다른가?

AJAX와는 별개의 개념이지만 AJAX가 없다면 JSON이라는 개념은 필요 없을 것이다. AJAX를 사용해 데이터를 주고 받을 때 그 데이터 포맷으로 JSON을 사용하는 것이다.

7. 이거 쓰려면 따로 라이브러리 같은 걸 써야 하냐?

JSON 자 체는 이미 자바스크립트 표준으로 채택되어 자바스크립트에서 기본적으로 지원하고 있기 때문에 별도의 라이브러리가 필요하거나 하지는 않 다. 그냥 eval()이라는 함수 하나로 해결되는 게 JSON이다. 하지만 JSON의 한계로 인해 라이브러리를 따로 사용하는 경우가 많다.그리고 자바스크립트 이외의 환경에서도 JSON을 간단히 사용 하기 위한 라이브러리들이 존재한다. JSON 공식 사이트 내용의 대부분이 바로 그것이다.

8. JSON 단점이나 문제점은 뭐냐?

AJAX 는 단순히 데이터만이 아니라 javascript 그 자체도 전달할 수 있다. 무슨 말이냐면 json데이터라고 해서 받았는데 그게 단 순 데이터가 아니라 자바스크립트가 될 수도 있고, 그게 실행 될 수 있다는 것이다. 데이터인 줄 알고 받았는데 악성 스크립트더 라 뭐 그럴 수 있다는 것이다.
JSON 관련 라이브러리를 따로 사용하는 이유가 이것이다. 받은 내용에서 순수하게 데이터만 추출하기 위한 라이브러리이다.

9. JSON으로 아무 데이터나 불러갈 수 있냐?

JSON의 한계는 JSON으로 가져올 수 있는 데이터는 해당 자바스크립트가 로드된 서버의 데이터에 한정된다는 것이다.
예를 들어서 http://kwz.kr/json.js에서 불러올 수 있는 데이터는 kwz.kr 서버에 존재하는 것만 가능하다는 것이다. 구글 데이터를 불러온다거나 네이버 데이터를 불러온다거나 할 수 없다.
JSON은 단순히 데이터 포맷일 뿐이고 그 데이터를 불러오기 위해선 XMLHttpRequest()라는 자바스크립트 함수를 사용해야 하는데 이 함수가 동일 서버에 대한 것만 지원하기 때문이다.

10. 어? 다른 서버의 데이터도 가져가서 보여주던데??

궁하면 통한다고 방법이야 만들면 나오는 거다. 그래서 나온 게 일종의 프락시 역할을 하는 서버쪽 스크립트 파일과 JSONP이다.

11. JSONP는 뭐냐????

JSON with Padding을 줄인 말이다. 다른 서버의 데이터를 가져오기 위한 방법이다.
이건 해당 서버에서 JSONP의 방식을 지원해 줘야지만 가능하다. 이것이 불가능하면 역시 프락시를 써야 한다.

Spring Security 3 – 로그인 페이지 커스터마이징

Spring Security 3 – 맛보기와 기본 설정의 이해 에서 스프링 시큐리티가 디폴트로 제공하는 로그인 페이지를 본 적이 있을 것이다. 이 포스트에서는 이 디폴트 로그인 페이지를 우리가 개발한 로그인 페이지로 변경하는 방법을 알아보자.

우리가 개발한 로그인 페이지를 적용해 보기 전에 먼저 Spring Security 3 – 맛보기와 기본 설정의 이해 의 접근권한 설정방식을 SpEL 표현식을 사용해서 다음과 같이 수정해 보자.

1
2
3
4
<security:http auto-config="true" use-expressions="true">
    <security:intercept-url pattern="/favicon.ico" access="permitAll" />
    <security:intercept-url pattern="/*" access="hasRole('ROLE_USER')" />
</security:http>

 

우리가 개발할 로그인 페이지를 적용하는 절차를 요약하면 다음과 같다.

1. 로그인 페이지를 개발한다.

2. 로그인 페이지를 http 엘리먼트 내부의 <form-login> 엘리먼트에 설정해 준다.

3. 로그인 페이지 자체에 대한 적절한 접근 권한을 설정해 준다.

우리가 만든 로그인 페이지가 잘 적용되는지만 확인해 보기 위해서 아무 내용도 없는 login.jsp 를 만들고 기본설정을 아래와 같이 변경한 후에 로컬 서버에 배포해 보자. login.jsp 는 http://localhost:8080/login.jsp 로 접근할 수 있다고 가정한다.

1
2
3
4
5
<security:http auto-config="true" use-expressions="true">
    <security:intercept-url pattern="/favicon.ico" access="permitAll" />
    <security:intercept-url pattern="/*" access="hasRole('ROLE_USER')" />
    <security:form-login login-page="/login.jsp"/>
</security:http>

 

브라우저에 http://localhost:8080 을 입력하면 우리가 개발한 로그인 페이지대신 다음과 같은 오류 화면이 나타날 것이다.

이와 같은 오류 화면이 보이는 이유는 방금 우리가 개발한 login.jsp 를 포함하는 모든 URL 접근시에 ROLE_USER 라는 접근권한을 필요하다고 다음과 같이 설정했기 때문이다.

<security:intercept-url pattern=”/*” access=”hasRole(‘ROLE_USER’)” />

따라서 /login.jsp URL 에 대해서는 모든 익명 사용자를 포함한 모든 사용자가 접근할 수 있도록  접근권한을 다음과 같이 설정해 줘야 한다.

1
2
3
4
5
6
<security:http auto-config="true">
    <security:intercept-url pattern="/favicon.ico" access="permitAll" />
    <security:intercept-url pattern="/login.jsp" access="permitAll" />
    <security:intercept-url pattern="/**" access="ROLE_USER" />
    <security:form-login login-page="/login.jsp"/>
</security:http>

 

다시 배포한 후, 브라우저에 http://localhost:8080 을 입력해보자. login.jsp 로 리다이렉트되어 빈 화면이 보일 것이다. 이것으로 스프링 시큐리티가 제공하는 디폴트 로그인 페이지가 우리가 개발한 login.jsp 로 잘 변경된 것을 확인할 수 있다.

이제 login.jsp 가 사용자 아이디와 비밀번호를 입력받을 수 있도록 수정해줘야 하는데 몇 가지 기억해야할 사항이 있다. 사용자 아이디와 비밀번호 폼필드명과 폼이 서브밋되는 URL 은 UsernamePasswordAuthenticationFilter 에 기본적으로 설정된 것을 사용해야 한다. 물론 각 폼필드명과 서브밋되는 URL 을 변경할 수도 있지만 이 포스트에서는 변경하는 방법을 설명하지 않을 것이기 때문이다. UsernamePasswordAuthenticationFilter 에 디폴트로 설정된 필드명은 j_username, j_password 이고 서브밋 URL 은 j_spring_security_check 이다. 또 폼 서브밋시에는 POST 방식을 사용해야 한다. 그렇지 않으면 UsernamePasswordAuthenticationFilter 가 로그인 요청을 거부할 것이다.

지금까지 설명한 내용을 바탕으로 login.jsp 를 작성하면 다음과 같다.

1
2
3
4
5
6
7
8
9
<head>
<title></title>
</head>
    <form action="j_spring_security_check" method="POST">
        ID : <input type="text" name="j_username" size="20" maxlength="50" /><br />
        Password : <input type="password" name="j_password" size="20" maxlength="50" /><br />
        <input type="submit" value="Login" />
    </form>

 

지금까지 개발한 내용을 서버에 배포한 후 브라우저에 http://localhost:8080 를 입력하면 login.jsp 로 리다이렉트할 것이고 login.jsp 가 제공하는 폼에 guest/guest 를 입력하면 정상적으로 로그인되는 것을 확인할 수 있을 것이다.

 

Spring Security 3 – 웹 보안 요청 아키텍쳐

Spring Security의 요청 처리 절차

Spring Security 는 주로 서블릿 필터와 이들로 구성된 필터체인으로의 위임모델을 사용한다. 서블릿 필터는 사용자의 요청을 가로채서 전처리 하거나 서버의 응답을 가로채서 후처리할 수 있다.

Spring Security 네임스페이스가 제공하는 http 엘리먼트의 auto-config 어트리뷰트를 사용하면, Spring Security 는 일련의 필터체인을 구성한다. 사용자 요청이 이러한 필터체인 내에서 처리되는 과정을 요약하면 다음과 같다.

자동설정으로 구성되는 필터체인에는 10개의 필터가 존재한다. 각 필터의 순서와 기능을 간단히 요약하면 다음과 같다.

1. SecurityContextPersistenceFilter

SecurityContextRepository 에서 SecurityContext 를 로딩하거나 SecurityContextRepository 로 SecurityContext 를 저장하는 역할을 한다.SecurityContext 란 사용자의 보호및 인증된 세션을 의미한다.

2. LogoutFilter

로그아웃 URL(디폴트 값 : /j_spring_security_logout) 로의 요청을 감시하여 해당 사용자를 로그아웃 시킨다.

3. UsernamePasswordAuthenticationFilter

아이디와 비밀번호를 사용하는 폼기반 인증 요청 URL(디폴트 값: /j_spring_security_check) 을 감시하여 사용자를 인증하는 역할을 한다.

4. DefaultLoginPageGeneratingFilter

폼또는 OpenID 기반 인증을 위한 로그인폼 URL(디폴트 값: /spring_security_login)을 감시하고 이와 관련된 로그인폼을 생성한다.

5. BasicAuthenticationFilter

 HTTP 기본 인증 헤더를 감시하여 처리한다.

6. RequestCacheAwareFilter

로그인 성공 후, 원래 요청 정보를 재구성하기 위해 사용된다.

7. SecurityContextHolderAwareRequestFilter

HttpServletRequestWrapper 를 상속한 SecurityContextHolderAwareRequestWapper 클래스로 HttpServletRequest 정보를 감싼다. SecurityContextHolderAwareRequestWrapper 클래스는 필터 체인상의 다음 필터들에게 부가정보를 제공한다.

8. AnonymousAuthenticationFilter

이 필터가 호출되는 시점까지 사용자 정보가 인증되지 않았다면 인증토큰에 사용자가 익명 사용자로 나타난다.

9. SessionManagementFilter

이 필터는 인증된 사용자와 관련된 모든 세션을 추적한다.

10. ExceptionTranslationFilter

이 필터는 보호된 요청을 처리하는 중에 발생할 수 있는 예외를 위임하거나 전달하는 역할을 한다.

11. FilterSecurityInterceptor

이 필터는 AccessDecisionManager 로 권한부여 처리를 위임함으로써 접근 제어 결정을 쉽게해준다.

Spring Security는 대략 25개의 필터를 제공한다. 이런 필터들은 모두 조건적으로 적용될 수 있다. 물론 javax.servlet.Filter 인터페이스를 직접 구현해서 추가할 수도 있다. 위에 나열한 필터들은 auto-config 어트리뷰트를 설정했을 때 자동으로 구성되는 필터들이라는 것을 기억하자. 위에 나열된 필터들을 명시적으로 포함시키거나 제외시킬 수도 있다.

처음부터 모든 필터체인을 하나 하나 구성할 수도 있다. 이 방식으로 구성한다는 것은 연결해야할 의존성이 많기 때문에 다소 지루할 수도 있지만 각 어플리케이션에 적합하고도 유연한 설정을 제공할 수 있다.

Spring Security 를 적용하기 위해 web.xml 에 설정하는 DelegatingFilterProxy  필터는 어떻게 Spring Security 가 설정한 필터체인과 연결되는 것일까?

DelegatingFilterProxy 를 설정하는 web.xml 을 다시 살펴보자.

1
2
3
4
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

 

위의 설정에서 filter-name 에 설정된 springSecurityFilterChain 이라는 이름은 그냥 아무렇게나 지은 것이 아니다. 사실 Spring Security 는 자기 자신을 DelegatingFilterProxy 와 연결하기 위해서 springSecurityFilterChain 라는 필터 이름을 요구한다.

auto-config 가 몰래 하는 짓

Spring Security 3 에서 auto-config 를 사용하면 다음과 같은 세 가지의 인증관련 기능을 자동적으로 제공한다.

HTTP basic Authentication

Form login authentication

Logout

auto-config 가 제공하는 기본 설정보다 좀 더 상세한 설정을 원할 경우라면 각각의 엘리먼트를 직접 선언할 수도 있다.

사용자 인증

사용자가 로그인 화면에서 제공한 비밀번호는 보안 시스템상에서 다음단계로 넘어가기 전에 비밀번호 저장소를 통해 인증되어야 한다. 비밀번호 인증과정에서 인증이라는 공통 기능을 담은 일련의 컴포넌트들이 사용된다.

인증관련 컴포넌트들을 정리하면 대략 다음과 같다.

위 다이어그램에는 3개의 주요 컴포넌트가 있다.

AbstractAuthenticationProcessingFilter

웹 기반 인증요청에서 사용되는 컴포넌트로 POST 폼 데이터를 포함하는 요청을 처리한다. 사용자 비밀번호를 다른 필터로 전달하기 위해서 Authentication 객체를 생성하고 일부 프로퍼티를 설정한다.

AuthenticationManager

사용자 비밀번호를 인증하는 역할을 담당한다. 인증에 실패하면 예외를 던지기도 하고 성공하면 Authentication 객체의 모든 프로퍼티를 완성한다. 이렇게 Authentication 객체에 채워지는 값에는 권한정보도 포함되어 있다.

AuthenticationProvider

AuthenticationManager 에게 비밀번호 인증기능을 제공하는 역할을 한다. 어떤 AuthenticationProvider 구현체들은 데이터베이스와 같은 비밀번호 저장소를 참고하여 비밀번호를 인증하기도 한다.

Authentication 이라는 인터페이스는 우리가 자주 사용하게 될 것이다. 이 인터페이스는 사용자 식별자와 비밀번호, 마지막으로 사용자에게 부여된 하나 또는 그 이상의 권한정보 등에 대한 상세정보를 가지고 있다. 개발자들은 인증된 사용자에 대한 상세 정보가 필요할 경우 보통 Authentication 오브젝트를 사용하게 될 것이다.

Authentication 인터페이스가 제공하는 메소드는 다음과 같다.

Object getPrincipal() : 사용자 아이디를 리턴한다.

Object getCredentials() : 사용자 비밀번호를 리턴한다.

List<GrantedAuthority> getAuthorities() : Authentication 저장소에 의해 인증된 사용자의 권한 목록을 리턴한다.

Object getDetails() : 인증 프로바이더에 종속적인 사용자의 상세정보를 리턴한다.

Spring Security 는 ProviderManager 라는 AuthenticationManager 인터페이스의 유일한 구현체를 제공한다. ProviderManager 는 하나 또는 여러 개의 AuthenticationProvider 구현체를 사용할 수 있다. AuthenticationProvider는 많이 사용되고 ProviderManager(AuthenticationManager 의 구현체) 와도 잘 통합되기 때문에 기본적으로 어떻게 동작하는 지 이해하는 것이 중요하다.

웹기반 사용자 아이디- 패스워드 인증 요청 처리관련 클래스들은 다음과 같이 도식화할 수 있다.

spring_security_login 으로 리다이렉트하네?

auto-config 에 의해 자동으로 Spring Security 가 적용된 페이지로 접속을 시도하면 다음 화면과 같이 spring_security_login 이라는 URL 로 리다이렉트 할 것이다.

URL의 spring_security_login 이라는 부분은 DefaultLoginPageGeneratingFilter 클래스가 정의한 디폴트 로그인 페이지 URL이다. 이 URL은 어트리뷰트 설정으로 변경가능하다.

이 폼의 HTML 소스를 살펴보자.

1
2
3
4
5
6
7
<form name='f' action='/JBCPPets/j_spring_security_check'
    method='POST'>
    User : <input type='text' name='j_username' value=''/>
    Password : <input type='password' name='j_password' />
    <input name="submit" type="submit"/>
    <input name="reset" type="reset"/>
</form>

 

우선 우리가 전혀 설정하지 않은 j_username 이나 j_password 와 같은 필드들과 j_spring_security_check 라는 폼 서브밋 URL 이 보인다. 이런 것들은 어떻게 생긴 것일까?

j_username 과 j_password 라는 폼 필드명과 j_spring_security_check 라는 폼 서브밋 URL은 UsernamePasswordAuthenticationFilter 가 디폴트로 지정한 이름들이다. UsernamePasswordAuthenticationFilter 를 명시적으로 설정해서 명칭들을 모두 변경할 수도 있다.

UsernamePasswordAuthenticationFilter 는 <form-login> 이라는 <http> 의 서브 엘리먼트로 설정할 수 있다.

비밀번호 인증

Spring Security 3 – 맛보기와 기본설정의 이해 의 authentication-manager 엘리먼트 설정 부분을 다시 살펴보자.

1
2
3
4
5
6
7
    <security:authentication-manager>
        <security:authentication-provider>
            <security:user-service>
                <security:user name="guest" password="guest" authorities="ROLE_USER" />
            </security:user-service>
        </security:authentication-provider>
    </security:authentication-manager>

 

위 설정은 메모리 비밀번호 저장소를 사용하고 있고 AuthenticationProvider 를 어떤 구현체로도 명시적으로 연결하고 있지 않다. AuthenticationManager 는 하나 또는 그 이상의 AuthenticationProvider 설정을 지원한다 것을 기억하자. 위 설정의 <authentication-provider> 엘리먼트는 디폴트로 DaoAuthenticationProvider 라는 AuthenticationProvider 인터페이스의 구현체를 사용하고 있으며 AuthenticationProvider 를 AuthenticationManager 로 자동 연결하고 있다.

DaoAuthenticationProvider 는 UserDetailsService 타입 오브젝트로 위임한다. UserDetailsService 는 UserDetails 구현체를 리턴하는 역할을 한다.

UserDetails 인터페이스는 이전에 설명한 Authentication 인터페이스와 상당히 유사하지만 서로 다른 목적을 가진 인터페이스이므로 혼돈하지 않도록 하자.

Authentication : 사용자 ID, 패스워드와 인증 요청 컨텍스트에 대한 정보를 가지고 있다. 인증 이후의 사용자 상세정보와 같은  UserDetails 타입 오브젝트를 포함할 수도 있다.

UserDetails : 이름, 이메일, 전화번호와 같은 사용자 프로파일 정보를 저장하기 위한 용도로 사용한다.

위 설정의 <user-service> 엘리먼트는 UserDetailsService 의 구현체인 InMemoryDaoImpl 를 사용하게 한다. InMemoryDaoImpl 구현체는 설정파일의 사용자 정보를 메모리 저장소에 저장한다.

DaoAuthenticationProvider 가 AuthenticationManager 에게 어떻게 인증처리를 지원하는지 지금까지 설명한 내용을 도식화해보면 다음과 같다.

인증 예외

인증과 관련된 모든 예외는 AuthenticationException 을 상속한다. AuthenticationException 은 개발자에게 상세한 디버깅 정보를 제공하기위한 두개의 멤버 필드를 가지고 있다.

authentication : 인증 요청관련 Authentication 객체를 저장하고 있다.

extraInformation : 인증 예외 관련 부가 정보를 저장한다. 예를 들어 UsernameNotFoundException 예외는 인증에 실패한 유저의 id 정보를 저장하고 있다.

가장 일반적인 예외는 다음과 같다.

BadCredentialsException : 사용자 아이디가 전달되지 않았거나 인증 저장소의 사용자 id 에 해당하는 패스워드가 일치하지 않을 경우 발생한다.

LockedException : 사용자 계정이 잠긴경우 발생한다.

UsernameNotFoundException : 인증 저장소에서 사용자 ID를 찾을 수 없거나 사용자 ID에 부여된 권한이 없을 경우 발생한다.

접근권한 부여

자동으로 설정된 Spring Security 필터 체인의 마지막 서블릿 필터는 FilterSecurityInterceptor 이다. 이 필터는 해당 요청의 수락 여부를 결정한다. FilterSecurityInterceptor 가 실행되는 시점에는 이미 사용자가 인증되어 있을 것이므로 유효한 사용자인지도 알 수 있다. Authentication 인터페이스에는 List<GrantedAuthority> getAuthorities() 라는 메소드가 있다는 것을 상기해 보자. 이 메소드는 사용자 아이디에 대한 권한 목록을 리턴한다. 권한처리시에 이 메소드가 제공하는 권한정보를 참조해서 해당 요청의 승인 여부를 결정하게 된다.

Access Decision Manager 라는 컴포넌트가 인증 확인을 처리한다. AccessDecisionManager 인터페이스는 인증 확인을 위해 두 가지 메소드를 제공한다.

supports : AccessDecisionManager 구현체는 현재 요청을 지원하는지의 여부를 판단하는 두개의 메소드를 제공한다. 하나는 java.lang.Class 타입을 파라미터로 받고 다른 하나는 ConfigAttribute 타입을 파라미터로 받는다.

decide : 이 메소드는 요청 컨텍스트와 보안 설정을 참조하여 접근 승인여부를 결정한다. 이 메소드에는 리턴값이 없지만 접근 거부를 의미하는 예외를 던져 요청이 거부되었음을 알려준다.

인증과정에서 발생할 수 있는 예상 가능한 에러를 처리하는 AuthenticationException 과 하위 클래스를 사용했던 것처럼 특정 타입의 예외 클래스들을 사용하면 권한처리를 하는 애플리케이션의 동작을 좀더 세밀하게 제어할 수 있다.

AccessDecisionManager 는 표준 스프링 빈 바인딩과 레퍼런스로 완벽히 설정할 수 있다.디폴트 AccessDecisionManager 구현체는 AccessDecisionVoter 와 Vote 취합기반 접근 승인 방식을 제공한다.

Voter 는 권한처리 과정에서 다음 중 하나 또는 전체를 평가한다.

■ 보호된 리소스에 대한 요청 컨텍스트 (URL 을 접근하는 IP 주소)

■ 사용자가 입력한 비밀번호

■ 접근하려는 리소스

■ 시스템에 설정된 파라미터와 리소스

AccessDecisionManager 는 요청된 리소스에 대한 access 어트리뷰트 설정을 보터에게 전달하는 역할도 하므로 보터는 웹 URL 관련 access 어트리뷰트 설정 정보를 가지게 된다. 기본 설정 파일의 URL 인터셉트 설정을 보면 사용자가 접근하려는 리소스에 대한 access 어트리뷰트 설정값으로 ROLE_USER 가 사용되고 있다.

<intercept-url pattern=”/*” access=”ROLE_USER” />

Voter는 사용할 수 있는 정보를 사용해서 사용자의 리소스에 대한 접근 허가 여부를 판단한다. 보터는 접근 허가 여부에 대해서 세 가지 중 한 가지로 결정하는데, 각 결정은 AccessDecisionVoter 인터페이스에 다음과 같이 상수로 정의되어 있다.

Grant(ACCESS_GRANTED) : Voter 가 리소스에 대한 접근 권한을 허가하도록 권장한다.

Deny(ACCESS_DENIED) : Voter 가 리소스에 대한 접근 권한을 거부하도록 권장한다.

Abstain(ACCESS_ABSTAIN) : Voter 는 리소스에 대한 접근권한 결정을 보류한다. 이 결정 보류는 다음과 같은 경우에 발생할 수 있다.

    – Voter 가 접근권한 판단에 필요한 결정적인 정보를 가지고 있지 않은 경우

    – Voter 가 해당 타입의 요청에 대해 결정을 내릴 수 없는 경우

접근권한 결정관련 오브젝트와 인터페이스의 설계내용을 보면 알겠지만 Spring Security 의 이런 부분들은 웹 어플리케이션 내의 인증과 접근제어에만 사용할 수 있는 것은 아니다.

지금까지의 내용을 종합해보면 “웹 요청에 대한 디폴트 인증 확인” 절차는 다음과 같이 도식화할 수 있다.

위 그림을 보면 ConfigAttribute 를 추상화함으로써 관련 클래스들이 ConfigAttribute 의 내용을 알 필요 없이 xml로 설정한 데이터(DefaultFilterInvocationSecurityMetadataSource 오브젝트에 보관된)를 ConfigAttribute 에 따라 동작하는 voter 로 전달할 수 있다는 것을 알 수 있을 것이다.  이와 같은 관심사의 분리는 동일 접근권한 결정 패턴을 사용해 새로운 유형의 보안 설정을 개발하는데 있어 견고한 기반을 제공하고 있다.

접근권한 결정 취합 설정

Spring Security 에서는 security 네임스페이스가 제공하는 엘리먼트로 AccessDecisionManager 를 설정할 수 있다.<http> 엘리먼트의 access-decision-manager-ref 어트리뷰트에 AccessDecisionManager 를 구현한 빈의 레퍼런스를 설정하면 된다. Spring Security 는 이 인터페이스를 구현한 3개의 구현체를 제공하는데 모두 org.springframework.security.access.vote 패키지에 존재한다.

AffirmativeBased : 접근을 승인하는 voter 가 하나라도 존재하면 이전의 접근 거부사실과 관계없이 바로 접근이 승인된다.

ConsensusBased : 다득표 여부가 AccessDecisionManager 의 접근 승인여부를 결정한다. 동률 또는 무효표에 처리에 대해서는 설정 가능하다.

UnanimouseBased : 모든 voter 가 접근을 승인해야 최종적인 접근이 승인된다.

UnanimouseBased 접근승인 방식 설정

지금까지 사용했던 기본 설정에 UnanimouseBased 접근승인 방식을 적용하려면 2가지를 수정해야 한다. 엘리먼트의 access-decision-manager-ref 어트리뷰트를 설정하고 관련 빈을 다음과 같이 추가하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<security:http auto-config="true" access-decision-manager-ref="unanimouseBased">
    <security:intercept-url pattern="/**" access="ROLE_USER" />
</security:http>
<bean id="unanimouseBased" class="org.springframework.security.access.vote.UnanimousBased">
    <property name="decisionVoters">
        <list>
            <ref bean="roleVoter" />
            <ref bean="authenticatedVoter" />
        </list>
    </property>
</bean>
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter" />
<bean id="authenticatedVoter" class="org.springframework.security.access.vote.AuthenticatedVoter" />

 

decisionVoters 프로퍼티는 커스텀 AccessDecisionManager 를 선언하기 전까지 자동으로 설정된다. 디폴트 AccessDecisionManager 에는 최종 인증 확인 절차에 사용할 voter 목록을 설정해줘야 한다. 위 설정에 적용된 2개의 voter 는 security 네임스페이스가 제공하는 디폴트 voter 들이다.

아쉽게도 Spring Security가 다양한 voter 를 제공하지는 않지만 AccessDecisionVoter 인터페이스를 구현해서 커스텀 voter 를 추가하는 것은 어렵지 않다.

위에 설정한 2개의 voter 가 하는 일은 다음과 같다.

RoleVoter : 리소스에 설정된 Role과 부합하는 접근권한(GrantedAuthority) Role 이 사용자에게 부여되어 있는지 확인한다. access 어트리뷰트는 콤마로 구분된 접근권한 Role 명칭들을 가질 수 있다. Role 명칭들은 디폴트로 ROLE_ 로 시작되어야 하지만 변경할 수 있다.

예) access=”ROLE_USER, ROLE_ADMIN”

AuthenticatedVoter : 와일드 카드와 같은 특수 설정을 지원한다.

● IS_AUTHENTICATED_FULLY : 새로운 사용자 아이디와 비밀번호가 입력된 경우 접근을 승인한다.

● IS_AUTHENTICATED_REMEMBERED : 사용자가 remember me 기능을 사용해 인증한 경우 접근을 승인한다.

● IS_AUTHENTICATED_ANONYMOUSLY : 사용자가 익명 사용자인 경우 접근을 승인한다.

예) access=”IS_AUTHENTICATED_ANONYMOUSLY”

스프링 표현식을 사용한 접근 설정

RoleVoter 로 구현되는 표준 Role 기반 방식 대신 SpEL(Spring Expression Language) 을 사용해서 복잡한 vote 규칙을 정의할 수도 있다. 이 방식을 사용하려면 <http> 엘리먼트에 use-expressions 어트리뷰트를 다음과 같이 설정해 주면 된다.

1
2
<security:http auto-config="true" use-expressions="true">
</textare a></p>

 

use-expressions 어트리뷰트를 추가하게 되면 intercept-url 엘리먼트의 access 어트리뷰트의 동작방식이 바뀌어 SpEL 표현식을 사용해줘야 한다. ROLE_USER 와 같은 간단한 문구대신 메소드를 호출하거나 시스템 프로퍼티를 참조하거나 값을 연산하는 등의 표현식을 사용할 수 있는 것이다.

use-expressions 어트리뷰트를 사용해 SpEL 표현식 기반의 접근 규칙을 설정할 때 주의할 점은 RoleVoter 자동설정 기능을 사용하지 말아야 한다는 것이다. 즉, 단순히 Role 정보에 의해 접근을 제어하기 원한다면 이전의 access 어트리뷰트 설정은 수정되어야 한다는 것이다. 다행히 SpEL에 포함된 hasRole 이라는 메소드가 Role 을 체크할 수 있다.

이전의 access 설정을 표현식을 사용하도록 수정하면 다음과 같다.

1
2
3
4
<security:http auto-config="true" use-expressions="true">
    <security:intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
</security:http>
</textare a></p>

 

SpEL 표현식을 사용한 접근제어는 SpEL 표현식을 어떻게 처리할지 알고 있는 WebExpressionVoter 라는 Voter 구현체에 의해 처리된다. WebExpressionVoter 는 이런 기능을 담당하기 위해 WebSecurityExpressionHandler 인터페이스의 구현체의 의존한다. WebSecurityExpressionHandler 는 표현식을 해석하고 표현식에서 사용가능한 보안관련 메소드를 제공하는 역할을 한다. 이 인터페이스의 기본 구현체는 WebSecurityExpressionRoot 클래스에 정의되어 있는 메소드를 노출시킨다.

이 클래스들 간의 흐름과 관계는 다음과 같이 도식화할 수 있다.

SpEL 접근제어 표현식에서 사용할 수 있는 메소드와 가상 프로퍼티(pseudo-property)들은 WebSecurityExpressionRoot 클래스나 이 클래스의 상위 클래스가 제공하는 public 메소드가 정의하고 있다.

가상 프로퍼티란 파라미터를 받지 않는 메소드로서 getter 를 위한 자바빈 명명 관례를 따르는 메소드를 말한다. 이러한 특징때문에 SpEL 표현식에서 사용되는 메소드는  중괄호, is 또는 get 과 같은 접두어를 생략할 수 있는 것이다.

스프링 시큐리티 3에 포함된 SpEL 메소드와 가상 프로퍼티는 다음과 같다.

hasIpAddress(ipAddress) : 특정 IP 주소 또는 넷마스크를 포함하는 IP 주소와의 일치성을 비교한다. 웹 전용이다.

사용 예) access=”hasIpAddress(‘162.79.8.30’)” 또는  access=”hasIpAddress(‘162.0.0.0/224’)”

hasRole(role) : RoleVoter 설정과 유사하며 Role 과 GrantedAuthority 와의 일치성을 비교한다. 웹 전용은 아니다.

사용 예) access=”hasRole(‘ROLE_USER’)”

hasAnyRole(role) : GrantedAuthority 와 Role 목록중 일치되는 Role 이 있는지 비교한다. 웹 전용은 아니다.

사용 예) access=”hasRole(‘ROLE_USER’, ‘ROLE_ADMIN’)”

이러한 SpEL 용 메소드 외에 SpEL 표현식에서 속성처럼 사용할 수 있는 다양한 메소드도 사용할 수 있는데 이러한 메소드들은 중괄호나 메소드 파라미터가 필요하지 않다.

permitAll : 모든 사용자의 접근을 허가한다. 웹 전용은 아니다.

사용 예) access=”permitAll”

denyAll : 모든 사용자의 접근을 거부한다. 웹 전용은 아니다.

사용 예) access=”denyAll”

anonymous : 익명사용자에게 접근을 허가한다. 웹 전용은 아니다.

사용 예) access=”anonymous”

authenticated : 인증된 사용자에게만 접근을 허가한다. 웹 전용은 아니다.

사용 예) access=”authenticated”

rememberMe : Remember Me 기능으로 인증된 사용자에게만 접근을 허가한다. 웹 전용은 아니다.

사용 예) access=”rememberMe”

fullyAuthenticated : 완전한 크리덴셜로 인증된 사용자에게만 접근을 허가한다. 웹 전용은 아니다.

사용 예) access=”fullyAuthenticated”

Voter 구현체는 요청 정보를 파악해서 접근허가, 거부, 보류 등의 결정된 정보를 리턴해야 한다는 사실을 기억하자. SpEL 기반 접근권한 설정은 Boolean 결과를 리턴하는 표현식으로만 구성되어야 한다.

또한 접근 제어 설정이 유효하지 않은 표현식을 포함하지 않는 이상 보류를 의미하는 결과를 리턴할 수도 없다. 접근제어 설정에 사용된 SpEL 표현식이 유효하지 않을 경우에만 voter 는 접근결정을 보류한다.

Spring Security 3 – 맛보기와 기본 설정의 이해

보안이 적용되지 않은 간단한 웹 어플리케이션 생성

1. New > Other > Maven Project

2. maven-archetype-webapp 선택

3. 메이븐 프로젝트 설정

4. Finish 선택

5. 이 웹 어플리케이션을 배포해서 정상적으로 동작하는 지 확인해 보자. 다음과 같이 페이지가 나타나야 한다.

웹 어플리케이션에 Spring Security 적용

이제 위에서 개발한 Hello World 웹 어플리케이션에 Spring Security 를 적용해 보자.

1. pom.xml 을 다음과 같이 수정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.happyhouse.rednics</groupId>
    <artifactId>spring-security-tutorial</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-security-tutorial Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>3.1.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>3.1.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>3.1.0.RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>spring-security-tutorial</finalName>
    </build>
</project>

 

spring-webmvc 를 넣어주지 않을 경우 ApplicationContext 초기화시에 commons.logging

2. web.xml 을 다음과 같이 수정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring-security-context.xml</param-value>
    </context-param>
    <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>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

 

3. WEB-INF 폴더에 spring-security-context.xml 파일을 다음과 같이 작성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security
                        http://www.springframework.org/schema/security/spring-security-3.1.xsd">
    <security:http auto-config="true">
        <security:intercept-url pattern="/favicon.ico" access="ROLE_ANONYMOUS" />
        <security:intercept-url pattern="/**" access="ROLE_USER" />
    </security:http>
    <security:authentication-manager>
        <security:authentication-provider>
            <security:user-service>
                <security:user name="guest" password="guest" authorities="ROLE_USER" />
            </security:user-service>
        </security:authentication-provider>
    </security:authentication-manager>
</beans>

 

4. 이제 이 프로젝트를 배포한 후 브라우저에 http://xxx:8080 과 같이 입력해보자. “Hello World” 페이지가 나오지 않고 아이디와 패스워드를 입력하라는 화면이 나온다. Hello World 페이지에 보안이 걸렸기 때문이다. 이제 guest / guest 를 입력하고 로그인 버튼을 입력해 보자.

5. 우리가 원했던 Hello World 페이지가 보일 것이다.

web.xml 과 spring-security-context.xml 설정의 이해

web.xml 설정의 이해

web.xml 설정을 다시 적어 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >
 
<web-app>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring-security-context.xml</param-value>
    </context-param>
 
    <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>
 
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

 

위 설정은 ContextLoaderListener 를 사용해서 spring-security-context.xml 파일로부터 루트 어플리케이션 컨텍스트를 생성하고 있다. 위 설정은 별도의 서블릿 컨텍스트 설정파일을 사용하지 않는다.

다음으로 모든 요청 URL(/*)  에 DelegatingFilterProxy 필터를 적용하고 있다. 사실 DelegatingFilterProxy는 보안과 직접적인 관련은 없다. DelegatingFilerProxy 필터의 이름으로 springSecurityFilterChain 을 설정해줬다. 따라서 DelegatingFilterProxy는 spring-security-context.xml 을 통해 등록된 빈 중에 이름이 springSecurityFilterChain 인 빈을 참조하게 된다. 이렇게 DelegatingFilterProxy 와 Spring Security 가 연결되게 된다.

DelegatingFilterProxy는 어플리케이션 컨텍스트에 정의된 springSecurityFilterChain 으로 필터링 작업을 위임하는 역할이 전부이다.

spring-security-context.xml 설정의 이해

spring-security-context.xml 설정을 다시 적어 보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security
                        http://www.springframework.org/schema/security/spring-security-3.1.xsd">
 
    <security:http auto-config='true'>
        <security:intercept-url pattern="/favicon.ico" access="ROLE_ANONYMOUS" />
        <security:intercept-url pattern="/**" access="ROLE_USER" />
    </security:http>
 
    <security:authentication-manager>
        <security:authentication-provider>
            <security:user-service>
                <security:user name="guest" password="guest" authorities="ROLE_USER" />
            </security:user-service>
        </security:authentication-provider>
    </security:authentication-manager>
</beans>

 

auto-config=”true” 설정에 의해 Spring Security 는 10개의 스프링 필터로 필터 체인을 구성한다. 필터의 연결 순서와 각 역할은 다음과 같다.

SecurityContextPersistenceFilter

SecurityContextRepository 에서 SecurityContext 를 로딩하거나 SecurityContextRepository 로 SecurityContext 를 저장하는 역할을 한다.SecurityContext 란 사용자의 보호및 인증된 세션을 의미한다.

LogoutFilter

로그아웃 URL(디폴트 값 : /j_spring_security_logout) 로의 요청을 감시하여 해당 사용자를 로그아웃 시킨다.

UsernamePasswordAuthenticationFilter

username 과 password 를 사용하는 폼기반 인증 요청 URL(디폴트 값: /j_spring_security_check) 을 감시하여 사용자를 인증하는 역할을 한다.

DefaultLoginPageGeneratingFilter

폼또는 OpenID 기반 인증을 위한 로그인폼 URL(디폴트 값: /spring_security_login)을 감시하여 로그인폼을 생성한다.

BasicAuthenticationFilter

 HTTP 기본 인증 헤더를 감시하여 처리한다.

RequestCacheAwareFilter

로그인 성공 후, 원래 요청 정보를 재구성하기 위해 사용됨

SecurityContextHolderAwareRequestFilter

HttpServletRequestWrapper 를 상속한 SecurityContextHolderAwareRequestWapper 클래스로 HttpServletRequest 정보를 감싼다. SecurityContextHolderAwareRequestWrapper 클래스는 필터 체인상의 다음 필터들에게 추가 정보를 제공한다.

AnonymousAuthenticationFilter

이 필터가 호출되는 시점까지 사용자 정보가 인증되지 않았다면 사용자가 익명이라는 것 나타내는 인증토큰이 요청과 관련지어 진다.

SessionManagementFilter

이 필터는 하나의 인증된 사용자와 관련된 모든 세션을 추적하고, 인증된 사용자 정보를 기반으로 세션을 추적을 처리한다.

ExceptionTranslationFilter

이 필터는 보호된 요청을 처리하는 중에 발생하는 예상된 예외를 위임하거나 전달하는 역할을 한다.

FilterSecurityInterceptor

이 필터는 AccessDecisionManager 로 인증에 대한 결정권을 위임함으로써 인증허가 및  접근제어 결정을 용이하게 한다.

Spring Security는 대략 25개의 필터를 제공한다. 이런 필터들은 모두 사용자 요청을 수정한다던지 하는 경우에 조건적으로 적용될 수 있다. 물론 javax.servlet.Filter 인터페이스를 직접 구현해서 추가할 수도 있다. 위에 나열한 필터들은 auto-config 어트리뷰트를 설정했을 때 자동으로 구성되는 필터들이라는 것을 기억하기 바란다. 위에 나열된 필터들은 명시적으로 포함시키거나 제외시킬 수 있다.

처음부터 모든 필터체인을 하나 하나 구성할 수도 있다. 이 방식으로 구성한다는 것은 연결해야할 의존성이 많기 때문에 다소 지루할 수도 있지만 각 어플리케이션에 적합하고도 유연한 설정을 제공할 수 있다.

맛보기 치고는 설명이 좀 길었던 것 같다.

부족한 부분은 Spring Security 문서를 참고하도록 하자

Spring Framework (7)

7.10. 뷰의 결정

Spring MVC에서 View는 사용자에게 결과를 랜더링하여 보여주는 Bean이다.

– InternalResourceViewResolver : 논리적인 View 이름을 사용하여 템플릿 파일 장원을 사용하여 랜더링 되는 View 객체를 결정한다.
– BeanNameViewResolver : 논리적인 View 이름을 사용해 DispatcherServlet의 어플리케이션 컨텍스트에 있는 View Bean을 결정한다.
– ResourceBundleViewResolver : 논리적인 View 이름을 사용해 ResourceBundle에 포함돼있는 View를 결정한다.
– XmlViewResolver : DispatcherServlet의 어플리케이션 컨텍스트와는 별도로, XML 파일로부터 View Bean을 결정한다.

1) 템플릿 뷰의 사용

InternalResourceViewResolver는 웹 어플리케이션 컨텍스트에 존재하는 템플릿에게 랜더링에 대한 책임을 위임하는 View 객체를 논리적으로 View 이름을 사용하여 결정한다.
이는 ModelAndView 객체로부터 리턴된 논리적인 이름을 취하고, 여기에 웹 어플리케이션 내의 템플릿의 경로에 도달하기 위한 접두어와 접미어를 붙이는 것으로 가능하다.

<bean id=”viewResolver” class=”org.springframework.web.servlet.view.InternalResourceViewResolver”>
<property name=”viewClass”>
<value>org.springframework.web.servlet.view.InternalResourceView</value>
</property>
<property name=”prefix”><value>/WEB-INF/jsp/</value></property>
<property name=”suffix”><value>.jsp</value></property>
</bean>

/WEB-INF/jsp/courseDetail.jsp
/WEB-INF/jsp/ : 접두어, courseDetail : 논리적인 뷰 이름, .jsp : 접미어

View 객체는 기본적으로 InternalResourceViewResolver인데, 이 객체는 실제 랜더링을 수행할 JSP에 단순히 요청을 전달하는 일을 한다.
만약 JSTL 태그를 사용한다면 viewClass 특성을 설정함으로써 InternalResourceView를 JstlView로 대체해야 한다. JstlView도 요청을 JSP에 전달한다.

<bean id=”viewResolver” class=”org.springframework.web.servlet.view.InternalResourceViewResolver”>
<property name=”viewClass”>
<value>org.springframework.web.servlet.view.JstlView</value>
</property>
<property name=”prefix”><value>/WEB-INF/jsp/</value></property>
<property name=”suffix”><value>.jsp</value></property>
</bean>

2) 뷰 빈 결정

JSP나 그 밖의 자원에 의해 표현되는 View가 아닐경우 InternalResourceViewResolver 대신 다른 뷰 리졸버를 선택하는 것이 좋다.
BeanNameViewResolver는 논리적인 뷰 이름을 어플리케이션 컨텍스트의 Bean 이름과 부합시키는 뷰 리졸버이다.

<bean id=”beanNameViewResolver” class=”org.springframework.web.servlet.view.BeanNameViewResolver” />

위의 설정 후 컨트롤러가 특정 논리적인 View 이름과 함께 ModelAndView를 리턴하면, BeanNameViewResolver는 논리적인 View 이름의 Bean을 찾는다.

<bean id=”courseList” class=”com.springinaction.training.mvc.CourseListPdfView” />

■ 별도의 XML 파일에서 뷰 빈 선언

View Bean을 메인 어플리케이션 컨텍스트에서 찾는 대신 별도의 XML 파일에 의뢰한다.

<bean id=”viewResolver” class=”org.springframework.web.servlet.view.XmlViewResolver”>
<property name=”location”>
<value>/WEB-INF/training-views.xml</value>
</property>
</bean>

XmlViewResolver는 기본적으로 WEB-INF/views.xml에서 View의 정의를 찾는다.
그러나 여기서는 location 특성에 /WEB-INF/training-views.xml 을 지정하여 기본 값을 재정의 했다. XmlViewResolver는 DispatcherSevlet의 컨텍스트 설정 파일에 다수의 View Bean을 선언하는 경우에 유용하다.
메인 컨텍스트 설정 파일을 깨끗하고 깔끔하게 유지하기 위해서는 View의 선언을 나머지 Bean으로부터 분리해야 할 필요가 있다.

■ 리소스 번들을 사용한 뷰 결정

프로퍼티 파일에서 View의 정의를 관리한다. 프로퍼티 파일을 사용함으로써 ResourceBundleViewResolver는 국제화와 관련한 이점을 갖는다.
다른 View 리졸버는 항상 논리적인 View 이름을 사용하여 단일한 View 구현 객체를 결정하는 반면, ResourceBundleViewResolver는 사용자의 Locale을 기초로 하여 동일한 논리적인 뷰 이름으로 서로 다른 View 구현 객체를 리턴 할 수 있다.

<bean id=”bundleViewResolver” class=”org.springframework.web.servlet.view.ResourceBundleViewResolver”>
<property name=”basename”>
<value>views</value>
</property>
</bean>

basename 특성은 View 정의를 담고 있는 프로퍼티 파일의 이름을 어떻게 구성할지를 ResourceBundleViewResolver에게 알려준다.
여기서는 views라는 값을 지정했으므로 기본적으로는 views.properties에, 미국은 views_en_US.properties, 프랑스는 views_fr_FR.properties 등으로 정의할 수 있다.
views.properties 에 JSP기반 템플릿을 설정할 경우,

courseList.class=org.springframework.web.servlet.view.JstlView
courseList.url=/WEB-INF/jsp/courseList.jspz

views_en_US.properties 에 PDF기반 템플릿을 설정할 경우,
courseList.class=com.paringinaction.training.mvc.CourseListPdfViewz

views_fr_FR.properties 에 EXCEL기반 템플릿을 설정할 경우,
courseList.class=com.paringinaction.training.mvc.CourseListExcelView

3) 뷰 리졸버 선택

일반적으로 사용하기 쉬우며 간결한 InternalResourceViewResolver 사용을 권한다.
BeanNameViewResolver와 XmlViewResolver 사이의 선택에 있어서는, DispatcherServlet의 컨텍스트 파일의 크기를 심각하게 증가시키지 않는 View Bean을 가지고 있는 경우에 는 BeanNameViewResolver를 그 반대일 경우엔 XmlViewResolver를 선택하도록 한다.
국제화에 따른 뷰 랜더링의 경우엔 ResourceBundleViewResolver.

■ 다중 리졸버 사용

다중 뷰 리졸버를 사용하기 위해서는 단순히 컨텍스트 설정 파일에서 필요한 뷰 리졸버 Bean을 선언해 준다.

7.11. Spring MVC 에러처리

Spring MVC는 Controller 내에서 발생하는 에러 처리를 지원하기 위하여 ExceptionResolver를 두고 있다.

ExceptionResolver를 이용하여 에러를 처리하고자 한다면 먼저 Bean 설정 파일에 다음과 같이 설정해줘야 한다.

<< 설정 파일 : action-servlet.xml >>

<bean id=”exceptionResolver”
class=”org.springframework.web.servlet.handler.SimpleMappingExceptionResolver”>
<property name=”exceptionMappings”>
<props>
<prop key=”com.xxx.user.ExistedUserException”>defaultErrorMessage</prop>
<prop key=”com.xxx.user.UserNotFoundException”>
defaultErrorMessage
</prop>
</props>
</property>
<property name=”exceptionAttribute” value=”sampleException” />
<property name=”defaultErrorView” value=”error” />
</bean>

– 이 종 화 (ingenuity.egloos.com) –

Spring Framework (6)

7.9. 폼 제출 처리

1) 지금까지의 Form 처리 방식

– 입력 페이지에 접근한다.
– 입력 데이터를 입력한 후 Submit
– 입력된 데이터에 대한 유효성 체크와 입력 데이터를 도메인 모델 클래스에 저장(Data Binding)
– 데이터 유효성 체크와 Data Binding이 문제없이 완료되면 사용자가 입력한 데이터를 데이터베이스에 저장하거나 수정한다.

Spring에서는 이와 같은 일련의 과정 전체를 지원하기 위하여 SimpleFormController를 지원하고 있다. SimpleFormController는 하나의 페이지에 대한 Form 처리를 진행할 경우 유용하게 사용될 수 있다.
Spring 프레임워크에서 지원하는 SimpleFormController는 GET과 POST에 따라 실행하는 작업을 달리하게 된다. GET 방식의 요청에 대해서는 입력 페이지, 수정 페이지로 이동하게 되며, POST 방식일 경우에는 입력 작업과 수정 작업을 진행하게 된다.
따라서 GET 방식일 경우에는 입력 페이지로 이동하는 과정이기 때문에 Data Binding이나 유효성 체크와 같은 작업은 실행되지 않는다. 그러나 POST 방식일 경우에는 입력 폼에서 전달된 데이터를 실질적으로 데이터베이스에 반영하는 과정이기 때문에 입력폼에서 전달된 데이터가 유효한지 체크하는 과정을 거치게 된다.
GET 방식과 POST 방식에 따라 SimpleFormController의 메써드가 호출되는 Workflow를 살펴보면 다음과 같다.

■ SimpleFormController가 Get 요청일 경우

■ SimpleFormController가 Post 요청일 경우

2) 폼 제출 처리

폼을 보여주기 위해 AbstractController 상속하고 폼을 처리하기 위해 AbstractCommandController를 상속한다면 서로 다른 컨트롤러를 관리해야 하는 단점이있다. 이를 개선한 것이 폼 컨트롤러이다.
HTTP GET 요청을 받았을 때에는 폼을 보여주고 HTTP POST 요청을 받았을 때에는 폼을 처리하는 기능이 추가된, 한 단계 더 발전된 컨트롤러이다. 게다가 폼을 처리하는 중에 어떤 에러라도 발생되면, 폼을 다시 보여줌으로써 사용자가 에러를 수정하여 다시 폼을 제출할 수 있도록 해준다.

public class RegisterStudentController extends SimpleFormController {
public RegisterStudentController() {
setCommandClass(Student.class);
}

protected void doSubmitAction(Object command) throws Exception {
Student student = (Student) command;
studentService.registerStudent(student);
}

private StudentService studentService;

public void setStudentService(StudentService studentService) {
this.studentService = studentService;
}
}

doSubmitAction() 메소드는 주입된 StudentService 참조의 enrollStudent() 메소드에 명령 객체를 전달함으로써 폼 제출(HTTP POST 요청)을 처리한다. 이 컨트롤러가 어떻게 등록 폼을 보여주는지에 대한 정의는 되어있지 않다.
SimpleFormController는 뷰와 관련된 자세한 사항을 컨트롤러의 자바 코드로부터 가급적이면 분리시키도록 설계되었다. ModelAndView 객체를 사용하는 대신 컨텍스트 설정 파일에서 컨트롤러를 설정할 수 있다.

<bean id=”registerStudentController” class=”com.springinaction.training.mvc.RegisterStudentController”>
<property name=”formView”><value>registerStudent</value></property>
<property name=”successView”><value>registerSuccess</value></property>
<property name=”studentService”><ref bean=”studentService”/></property>
</bean>

formView : 컨트롤러가 HTTP GET 요청을 받았을 때나 또는 에러 발생시 보여줘야 할 뷰의 논리적인 이름.
successView : 성공적으로 폼이 제출됐을 때 보여줘야 할 뷰의 논리적인 이름.

뷰 리졸버는 이들 값을 사용해 사용자에게 결과를 랜더링 하여 보여줄 View 객체를 검색할 것이다. ModelAndView 객체를 리턴할 필요성이 없어짐으로 인해 모델 데이터를 뷰에 전달하는것은 불가능하다. 이는 성공적으로 처리된 후에 모델 데이터를 보여줄 것인지 여부에 따라 문제가 될 수도 있고 그렇지 않을 수도 있다.
만약 데이터를 전송하여 뷰에 의해 보여지도록 해야할 경우라면 doSubmitAction() 대신 onSubmit() 메소드를 재정의해서 사용해야 한다.

■ 폼 입력값 검증

org.springframework.validation.Validator 인터페이스는 Spring MVC에서의 유효성 검증을 도모한다.

public interface Validator {
void validate(Object obj, Errors errors);
boolean supports(Class clazz);
}

supports() 메소드는 Spring이 주어진 클래스에 대해 검증기를 사용할지 여부를 결정하는데 사용된다.

<bean id=”registerStudentController” class=”com.springinaction.training.mvc.RegisterStudentController”>

<property name=”validator”>
<bean class=”com.springinaction…..” />
</property>
</bean>

3) 마법사를 통한 복잡한 폼의 처리

사용자로 하여금 다수의 페이지에 걸친 복잡한 입력 폼들을 거치도록 해야 할 경우 사용 된다. 주목할 점은 최종적으로는 하나의 폼으로서 처리된다는 것이다.
AbstractWizardFormController 클래스를 확장하여 사용되며, processFinish() 메소드를 통해 폼 입력 완료 시 폼을 종료시키기 위해 호출된다. AbstractWizardFormController가 마법사의 작업 흐름 관리와 관련된 대부분의 일을 처리하는데 서블릿명-servlet.xml 파일에 설정해야 한다.

<bean id=”feedbackController” class=”com.springinaction.training.mvc.FeedbackWizardController”>
<property name=”feedbackService”>
<ref bean=”feedbackService”>
</property>
<property name=”pages”>
<list>
<value>general</value>
<value>instructor</value>
<value>course</value>
<value>facilities</value>
</list>
</property>
</bean>

뷰의 논리적인 이름 목록을 pages 특성에 지정함으로써 어떤 페이지들이 폼을 구성하는지 마법사가 알 수 있도록 해준다.
이들 이름은 최종적으로 뷰 리졸버에 의해 View 객체에 삽입된다.

■ 폼 페이지의 단계 밟기

처음 보여질 페이지는 pages 특성에 설정된 첫 번째 값(general)이 된다.
AbstractWizardFormControlle는 어떤 페이지를 보여줘야 할지를 결정하기 위해서 자신의 getTargetPage() 메소드에 의뢰한다. 이 메소드는 int 타입의 결과를 리턴하며 pages 특성에 주어진 0부터 시작하는 페이지들의 목록에 있어서의 인덱스 값이다.
기본적으로 구현된 getTargetPage()는 ‘_target’으로 시작하고 숫자로 끝나는 이름의 요청 파라미터를 기초로 다음 페이지를 결정한다.
즉, _target0, _target1 ….

■ 마법사 완료하기

마법사의 작업이 완료되면 processFinish() 메소드가 호출되어야 하는데 이를 컨트롤러에게 알려주기 위해서는 ‘_finish’라고 하는 요청 파라미터가 사용된다.

■ 마법사 취소하기

진행중인 마법사를 취소하기 위해서는 ‘_cancel’ 파라미터를 사용한다.

AbstractWizardFormControlle가 이 파라미터를 수신하면, 제어권을 processCancel() 메소드로 전달할 것이다.
processCancel()은 기본적으로 취소 작업이 지원되지 않는다는 것을 나타내는 예외를 던진다. 따라서 이 메소드를 재정의하여 사용자가 ‘취소’ 버튼을 눌렀을 때 다른 어떤 페이지로 이동토록 해야 한다.

protected ModelAndView processCancel(HttpServletRequest request,
HttpServletResponse response, Object command, BindException
bindException) throws Exception {
return new ModelAndView(“home”);
}

만약 취소시키기 전에 정리해야 할 작업이 있다면 processCancel() 메소드 안에 ModelAndView()를 리턴하기 전에 설정해 줘야 한다.

■ 마법사 폼 검증하기

마법사 컨트롤러는 Validator 객체를 사용하여 명령 객체의 검증을 한번에 실시하는 대신 한번에 한 페이지만큼을 검증한다. 이는 페이지가 전환될 때마다 validatePage() 메소드를 호출함으로써 수행된다.

protected void validatePage(Object command, Errors errors, int page) {
FeedbackSurvey feedback = (FeedbackSurvey) command;
FeedbackValidator = (FeedbackValidator) getValidator();

if(page == 0) {
validator.validateEmail(feedback.getEmail(), errors);
}
}

이 경우, 검증 대상이 증가함에 따라 validatePage() 메소드는 코드량이 증가하는 문제가 발생된다. 따라서 여기서 FeedbackValidator의 validateEmail()메소드를 호출 했듯이, 컨트롤러 검증기 객체에 세밀한 수준의 검증 메소드에 검증에 대한 책임을 위임하기를 권장한다. 이 모든 것은 컨트롤러를 설정할 때 validator 특성을 설정해야 함을 전제로 한다.

<bean id=”feedbackController” class=”com.springinaction.training.mvc.FeedbackWizardController”>
<property name=”feedbackService”>
<ref bean=”feedbackService”>
</property>
<property name=”pages”>
<list>
<value>general</value>
<value>instructor</value>
<value>course</value>
<value>facilities</value>
</list>
</property>
<property name=”validator”>
<bean class=”com.springinaction.training.mvc.FeedbackValidator” />
</property>
</bean>

여기서 유의해야 할 중요한 사항은, 다른 명령 컨트롤러들과는 달리 마법사 컨트롤러는 결코 Validator 객체의 표준 validate() 메소드를 호출하지 않는다는 사실이다.
이는 validate() 메소드가 명령 객체 전체를 검증하는 반면, 마법사 컨트롤러는 마법사의 명령 캨게가 한번에 한 페이지만큼 검증돼야 한다는 것을 알기 때문이다.
만약 어떤 이유에서든 processFinish()를 호출하기 이전에 명령 객체 전체를 검증해야할 필요가 있다면 다음과 같이 구현한다.

protected void validatePage(Object command, Errors errors, int page, boolean isFinish) {
FeedbackSurvey feedback = (FeedbackSurvey) command;
FeedbackValidator = (FeedbackValidator) getValidator();

if(page == 0) {
validator.validateEmail(feedback.getEmail(), errors);
}

if(isFinish) {
validator.validate(command, errors);
}
}

4) 단일 컨트롤러에서 다중 액션 처리하기

MultiActionController는 다중 액션을 수행할 수 있는 특별한 종류의 컨트롤러로서, 각 액션은 다양한 메소드와 붙을 수 있다. 예를 들어 강의 목록을 보여주는 courseUnsorted(), coursesSortedByDate(), coursesSortedByName() 이렇게 하나의 컨트롤러에 세 개의 메소드가 있을 경우 원하는 메소드를 통해 수행되려면 URL에서 파일명에 나타내는 부분에 의해서 선택된다.
즉, http://localhost/courseUnsorted.dohttp://localhost/coursesSortedByDate.do,
http://localhost/coursesSortedByName.do
이는 매우 직관적이긴 하지만, 메소드명이 노출되므로 좋은 방법이 아닐 수 있다.

■ 메소드 이름 결정

MultiActionController는 메소드 이름 리졸버에 기초해 메소드 이름을 결정할 수 있다.

– ParameterMethodNameResolver : 요청 파라미터를 기초로 하여 실행 메소드 이름을 결정한다.
– PropertiesMethodNameResolver : 키-값 쌍의 목록을 기초로하여 실행 메소드 이름을 결정한다.

<bean id=”multiactionController” class=”com.springinaction.training.mvc.ListCoursesController”>
<property name=”methodNameResolver”>
<ref bean=”methodNameResolver” />
</property>
</bean>

■ 리졸버 선택

Struts의 DispatchAction과 유사한 것이 ParameterMethodNameResolver 이다.
즉 요청을 통해 전달된 파라미터에 기호하여 메소드를 선택할 수 있게 해준다.

<bean id=”methodNameResolver” class=”com.springinaction.web.servlet.mvc.multiaction.ParameterMethodNameResolver”>
<property name=”paramName”>
<value>action</value>
</property>
</bean>

paramName 특성은 실행 메소드의 이름을 담고 있는 요청 파라미터의 이름을 나타내며, 여기서는 action으로 설정돼있다.
즉, …?action=coursesUnsorted 처럼 해준다. 이는 POST 방식으로도 가능하다.
메소드 이름을 결정하는 또 다른 방법은 URL 패턴을 메소드 이름과 매핑하는 것이다.

<bean id=”methodNameResolver” class=”com.springinaction.web.servlet.mvc.multiaction.PropertiesMethodNameResolver”>
<property name=”mappings”>
<props>
<prop key=”/courseList.do”>coursesUnsorted</prop>
<prop key=”/coursesByDate.do”>coursesSortedByDate</prop>
<prop key=”/coursesByName.do”>coursesSortedByName</prop>
</props>
</property>
</bean>

5) 일회성 컨트롤러

public interface ThrowawayController {
ModelAndView execute() throws Exception;
}

일회성 컨트롤러를 만들기 위해서 해야 할 일은 오직 이 인터페이스를 구현하고 execute() 메소드 안에 프로그램 로직을 구현하는 것이다.
HttpServletRequest나 명령 객체를 통해 파라미터를 얻는 대신, 일회성 컨트롤러는 스스로가 명령 객체로서 행동한다.

public class DisplayCourseController implements ThrowawayController {
private Integer id;

public void setId(Integer id) {
this.id = id;
}

public ModelAndView excute() throws Exception {
Course course = courseService.getCourse(id);
return new ModelAndView(“courseDetail”, “course”, course);
}

// INJECTED
private CourseService courseService;
public void setCourseService(CourseService courseService) {
this.courseService = courseService;
}
}

또한 일회성 컨트롤러를 DispatcherServlet의 설정 파일에 선언해야 한다.
<bean id=”displayCourseController” class=”com.springinaction.training.mvc.DisplayCourseController” singleton=”false”>
<property name=”courseService”><ref bean=”courseService”/></property>
</bean>

singleton=”false” 부분에 주목해야 한다. 기본적으로 모든 Bean은 singleton이기 때문에 singleton을 false로 지정하지 않는다면 DisplayCourseController는 요청들 사이에서 재사용될 것이다. 즉, 사용이 끝난 컨트롤러는 폐기할 것이며 각 요청에 대한 새로운 인스턴스를 생성시킬 것을 스프링에게 지시하는 것이다.
일회성 컨트롤러를 사용하기 위해 해야 할 일이 또 하나 있다. 핸들러 어댑터를 사용해 어떻게 요청을 컨트롤러에 전달할지를 DispatcherServlet이 알게 해줘야 하는 것이다.
ThrowawayController는 Controller와 동일한 계층도에 속하지 않기 때문에, DispatcherServlet에게 다른 핸들러 어댑터를 사용할 것을 반드시 알려줘야 한다.

<bean id=”throwawayHandler” class=”org.springframework.web.servlet.mvc.throwaway.ThrowawayControllerHandlerAdapter” />

– 이 종 화 (ingenuity.egloos.com) –

Spring Framework (5)

7. Spring MVC

7.1. 모델 1 개발 방식

1) 장점

개발 속도가 빠르며 개발자의 스킬이 낮아도 배우기 쉬워 빠르게 적용할 수 있다.

2) 단점

JSP 페이지에서 프리젠테이션 로직과 비즈니스 로직을 모두 포함하기 때문에 JSP 페이
지가 너무 복잡해 진다. 또한 개발자와 디자이너의 분리된 작업이 어려워진다.
가장 큰 문제는 JSP 페이지의 코드가 복작해 짐으로 인해 유지보수 하기 어려워진다.
이는 정교한 Presentation 레이어를 구현하기 힘들다.(유효성 체크, 에러 처리 등)

7.2. 모델 2 개발 방식

1) 장점 (Spring MVC를 기준으로)
– Presenation에서 명확한 역할 분담이 된다.
– UI 레이어를 단순화 시킴으로써 디자이너도 작업하는 것이 가능하게 된다. 단지 Display용으로만 사용된다.
– Presentation 레이어의 정교한 개발이 가능하다. 유효성 체크, 에러 처리와 같은 기능들은 Spring 프레임워크에서 제공한다.
– Dependency Pull 없이 Dependency Injection만을 이용해서 애플리케이션을 개발하는 것이 가능하다.
– UI 레이어가 단순해 짐으로서 유지보수가 쉽다.

2) 단점
– 새로운 기술을 익혀야 하는 부담감이 있다.
– 프로젝트 초반에 개발 속도의 저하를 가져올 수 있다.

7.3. Spring MVC에서 하나의 요청에 대한 Life Cycle

Spring MVC내에서 처리되는 하나의 요청에 대한 Life Cycle과정은 위 그림과 같다.

① 클라이언트의 요청에 대한 최초 진입 지점은 DispatcherServlet이 담당하게 된다.
대부분의 MVC를 지원하는 프레임워크가 메인 Servlet을 가지는 것처럼 Spring MVC
또한 메인 Servlet이 최초 진입지점으로 다음의 작업을 처리하게 된다.

② DispatcherServlet은 Spring Bean Definition에 설정되어 있는 Handler Mapping 정보를 참조하여 해당 요청을 처리하기 위한 Controller를 찾는다.

③ DispatcherServlet은 선택된 Controller를 호출하여 클라이언트가 요청한 작업을 처리.

④ Controller는 Business Layer와의 통신을 통하여 원하는 작업을 처리한 다음 요청에 대한 성공유무에 따라 ModelAndView 인스턴스를 반환한다. ModelAndView 클래스에는 UI Layer에서 사용할 Model데이터와 UI Layer로 사용할 View에 대한 정보가 포함되어있다.

⑤ DispatcherServlet은 ModelAndView의 View의 이름이 논리적인 View 정보이면 ViewResolver를 참조하여 이 논리적인 View 정보를 실질적으로 처리해야할 View를 생성하게 된다.

⑥ DispatcherServlet은 ViewResolver를 통하여 전달된 View에게 ModelAndView를 전달하여 마지막으로 클라이언트에게 원하는 UI를 제공할 수 있도록 한다. 마지막으로 클라이언트에게 UI를 제공할 책임은 View 클래스가 담당하게 된다.

7.4. Spring 프레임워크에서 지원하는 기능들

Spring 프레임워크에서는 위 그림에서 보여지는 각 항목들에 대하여 여러가지 형태의 API들을 지원하고 있다. HandlerMapping도 한가지 방식으로 Mapping을 지원하는 것이 아니라 요청 URI를 바탕으로 지원하는 방식, Bean 이름을 기준으로 지원하는 방식등 여러가지 형태로 사용 가능하도록 지원하고 있다.

Controller, ViewResolver, View 또한 너무도 많은 종류를 지원하고 있어서 이 중에 어느것을 사용해야 하는지를 판단하는 것조차 힘든 것이 사실이다. 그러나 기본적으로 위 그림과 같은 방식으로 처리된다는 것을 이해하고 있다면 웹 애플리케이션을 개발할 때 필요로하는 API들을 사용하면 된다. 또한 Spring 프레임워크에서 지원하지 못하는 기능들이 있다면 기존의 API들을 확장해서 우리들만의 기능들을 추가하는 것이 가능하다.
Spring 프레임워크는 다양한 방식으로 유연성을 제공하고 있다. 이와 같은 유연성은 Spring 프레임워크에서 제공하는 API들을 활용함으로써 얻게 될 수도 있으며, Spring 프레임워크에서 제공하는 API를 확장해서 우리들만의 기능을 추가함으로써 확장이 가능한 것이다.

7.5. DispatchserServlet 설정

DispatchserServlet은 Spring MVC에서 프론트 컨트롤러의 역항을 하는 서블릿으로서, Spring MVC의 심장이라고 할 수 있다. DispatchserServlet을 web.xml에 정의해야 한다.
DispatchserServlet은 어플리케이션이 시작할 때 DispatchserServlet의 init() 메소드가 호출되면서 Bean 설정 파일의 초기화가 진행된다.

<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatchserServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-name>은 매우 중요하다. DispatchserServlet은 서블릿의 이름에 따른 XML 파일로부터 Spring 어플리케이션 컨텍스트를 로드한다. DispatchserServlet이 초기화될 때 디폴트로 사용하는 Bean 설정 파일의 이름은 “서블릿명-servlet.xml” 된다.
즉, action-servlet.xml이라는 파일로부터 어플리케이션 컨텍스트를 로드하려고 시도할 것이다.
그 다음엔 DispatchserServlet에 의해 다뤄질 URL을 지정해야 한다. web.xml에 다음과 같이 설정한다.

<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

설정 파일이 모두 로드 되도록 하기 위해서는 web.xml에 컨텍스트 로더를 설정해야 한다.
컨텍스트 로더는 DispatchserServlet이 로드하는 것 이외의 컨텍스트 설정 파일들을 로드할 수 있다.

<listener>
<listener-class>web.context.ContextLoaderListener</listener-class>
</listener>

어떤 컨텍스트 로더를 사용하든 간에, 로드해야 할 Spring 설정 파일의 위치를 지정해야 한다. 만약 지정하지 않는다면 컨텍스트 로더는 기본적으로 /WEB-INF/applicationContext.xml 이라는 Spring 설정 파일을 찾는다.
그러나 이 파일을 그 자체로 어플리케이션 계층에 따른 어플리케이션 컨텍스트의 분리를 지원하지 않기 때문에, 이 기본값을 그대로 사용하지 않는 것이 좋다.
개선책으로 서블릿 컨텍스트에 contextConfigLocation 이라는 파라미터를 설정함으로써, 컨텍스트 로더가 로드할 수 있는 하나 이상의 Spring 설정 파일을 지정한다.

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>

7.6. Spring 프레임워크에서 지원하는 Controller

 

7.7. 요청과 컨트롤러와의 매핑

■ BeanNameUrlHandlerMapping
컨트롤러의 Bean 이름에 기초해 컨트롤러를 URL과 매핑.

■ SimpleUrlHandlerMapping
컨텍스트 설정 파일에 정의돼있는 일련의 특성들을 사용해 컨트롤러를 URL과 매핑.

■ CommonsPathMapHandlerMapping
컨트롤러의 코드에 있는 소스 수준의 메타데이터를 사용하여 컨트롤러를 URL과 매핑.

1) URL과 Bean이름과의 매핑
예를 들어, ListCoursesControll Bean이 http://localhost/listCourses.do 라면, 컨텍스트 설정 파일에  BeanNameUrlHandlerMapping Bean을 선언해야 한다.

<bean id=”beanNameUrlMapping”
class=”org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping” />

그 다음엔 처리돼야 할 URL 패턴으로 컨트롤러 Bean의 이름을 지정해야 한다.
ListCoursesController를 위한 URL 패턴은 ‘listCourses.do’ 이므로, 다음과 같이 컨텍스트 설정 파일에 컨트롤러를 선언한다.

<bean name=”/listCourses.do” class=”com.springaction.test.mvc.ListCoursesController”>
<property name=”courseService”>
<ref bean=”courseService”>
</property>
</bean>

2) SimpleUrlHandlerMapping

대부분의 경우에 사용되는 핸들러 매핑이다. 특별한 방법으로 Bean의 이름을 지정할 필요 없이, URL 패턴을 직접적으로 컨트롤러에 매필할 수 있도록 해준다.

<bean id=”urlMapping” class=”org.springframework.web.servlet.handler.SimpleUrlHandlerMapping”>
<property name=”mappings”>
<props>
<prop key=”/register.htm”>registerStudentController</prop>
<prop key=”/student/listCourses.htm”>listCourseController</prop>
<prop key=”/student/displayCourse.htm”>displayCourseController</prop>
<prop key=”/student/enroll.htm”>enrollController</prop>
</props>
</property>
</bean>

3) 컨트롤러 매핑을 위한 메타데이터 사용 (CommonsPathMapHandlerMapping)

컨트롤러의 소스 코드 내에서 URL 매핑을 결정하기 위한 소스 수준의 메타데이터를 사용. 컨스턴트 설정 파일에서 이 핸들러 매핑을 <bean>으로 선언해 준다.

<bean id=”urlMapping” class=”org.springframework.web.servlet.handler.metadata.CommonsPathMapHandlerMapping” />

그 다음에 URL 패턴을 선언하는 PathMap 속성을 사용하여 각 컨드롤러에 캐그를 달면된다.

7.8. 컨트롤러를 사용한 요청 처리

컨트롤러는 Spring MVC의 두뇌 역할을 한다. Spring MVC 어플리케이션의 작동을 구현할때에는, Spring의 컨트롤러 클래스 중 하나를 확장하여 사용하게 될 것이다. 컨트롤러는 DispatchserServlet으로부터 요청을 받아들여, 사용자를 대신하여 어떤 비즈니스 기능을 수행하게 된다.

1) 단순한 컨트롤러 작성

컨트롤러의 요구사항이 직관적이고 단순한 경우라면 AbstractController의 서브 클래스로 구현할 수 있다.

public class ListCourseController extends AbstractController {
public ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
Set allCourses = courseService.getAllCourses();
return new ModelAndView(“courseList”, “courses”, allCourses);
}

// INJECTED
private CourseService courseService;

public void setCourseService(CourseService courseService) {
this.courseService = courseService;
}

}

handleRequestInternal() 메소드는 AbstractController 실행에 있어서의 주된 메소드이다.

■ ModelAndView 개요

모든 컨트롤러의 실행 메소드는 반드시 ModelAndView를 리턴해야 한다.
return new ModelAndView(“courseList”, “courses”, allCourses);
courseList : 출력을 보여주는데 사용될 뷰 컴포넌트의 논리적인 이름.
courses : 파라메타 명
allCourses : 파라메타 값

■ 컨트롤러 묶기

ListCoursesController 작성 후 이를 컨텍스트 설정 파일에서 설정한다.
이 컨트롤러는 Spring MVC 컴포넌트 설정인 서블릿명-servlet.xml 파일에 설정.

<bean id=”listCourseController” class=”com.springinaction.training.mvc.ListCourseController”>
<property name=”courseService”><ref bean=”courseService”/></property>
</bean>

2) 명령 처리

AbstractController에 기반한 컨트롤러에서도 HttpServletRequest로 부터 필요한 파라미터를 얻을 수 있다.
그러나 바인딩 로직과 유효성 검증 로직은 진정한 컨트롤러의 역할이 아니다.
파라미터에 기반하여 작업을 수행하는 상황이라면 AbstractCommandController와 같은 명령 컨트롤러를 확장해야 한다. 이 컨트롤러는 파라미터를 명령 객체에 자동으로 바인딩시켜 줄 것이며, 파라미터의 유효성 확인을 위한 검증기를 플러그인 할 수 있는 hook을 제공한다.

public class DisplayCourseController extends AbstractCommandController {
public DisplayCourseController() {
setCommandClass(DisplayCourseCommand.class);
}

protected ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object command,
BindException errors) throws Exception {

DisplayCourseCommand displayCommand = (DisplayCourseCommand) command;
Course course = courseService.getCourse(displayCommand.getId());
return new ModelAndView(“courseDetail”, “course”, course);
}

// INJECTED
private CourseService courseService;

public void setCourseService(CourseService courseService) {
this.courseService = courseService;
}
}

DisplayCourseController 작성 후 이를 컨텍스트 설정 파일에서 설정한다.
이 컨트롤러는 Spring MVC 컴포넌트 설정인 서블릿명-servlet.xml 파일에 설정한다.

<bean id=”displayCourseController” class=”com.springinaction.training.mvc.DisplayCourseController”>
<property name=”courseService”><ref bean=”courseService”/></property>
</bean>

handle() 메소드는 AbstractCommandController를 위한 주된 실행 메소드이다.
HttpServletRequest와 HttpServletResponse 이외에도 컨트롤러의 명령에 해당하는 Object를 파라미터로 취한다.

– 이 종 화 (ingenuity.egloos.com) –

Spring Framework (4)

5. Bean 설정 파일 관리

Spring 프레임워크의 Bean 설정 파일은 작은 어플리케이션의 경우 단 하나만으로 모든 Bean을 관리할 수 있지만 중/대규모의 어플리케이션일 경우 하나의 Bean 설정 파일로 모든 Bean을 관리한다는 것은 좋지 않다. 이때 Bean 설정 파일을 일정한 원칙하에 분리하는 것이 중요하다.

5.1. 수평적 분리 방법

각 컴포넌트마다 하나씩의 Bean 설정 파일을 가지는 것이다. 이 설정 파일에 해당 컴포넌트와 관련된 모든 정보를 가지는 구조.

1) User 컴포넌트
applicationContext-user.xml (UI계층, 비즈니스 계층, 퍼시스턴스 계층)

2) Board 컴포넌트
applicationContext-board.xml (UI계층, 비즈니스 계층, 퍼시스턴스 계층)
수평적 분리 방법을 사용할 경우 각 컴포넌트마다 설정 파일을 관리할 수 있으므로 명확한 분리가 되는 이정이 있지만 각 계층에서 공통적으로 사용하는 정보의 중복이 발생되는 단점이 있다.

5.2. 수직적 분리 방법

각 계층마다 하나씩의 Bean 설정 파일을 가지는 방법.

UI 계층 – applicationContext-ui.xml
비즈니스 계층 – applicationContext-biz.xml
퍼시스턴스 계층 – applicationContext-dao.xml

계층마다 중복되는 내용을 방지할 수 있는 장점이 있지만 각각의 컴포넌트를 분리하는 것이 명확하지 않을 수 있으며, 컴포넌트가 많아질 경우 설정 파일이 비대해져서 개발 용이성 및 유지보수성을 저하시킬 수 있다.

5.3. 프로젝트 규모에 따른 Bean 설정 파일 관리 전략

프로젝트 초반에 Bean 설정 파일을 어떻게 관리할 것인지에 대한 전략을 수립하는 것이 중요하다.

1) applicationContext.xml
어플리케이션에서 공통적으로 사용되는 Aspect, Mail, MessageSource에 대한 Bean을 정의.

2) applicationContext-jdbc.xml
퍼시스턴스 계층에서 공통적으로 사용되는 Bean을 정의.

3) applicationContext-user.xml
사용자 관리 구현을 위한 Bean을 정의.

4) applicationContext-board.xml
게시판 구현을 위한 Bean을 정의.

6. 트랜잭션

하나의 유즈 케이스에 해당하는 업무를 처리하는 단위는 비즈니스 계층에서 담당하고 있다.
각 유즈 케이스에 해당하는 업무들은 비즈니스 계층의 각 메소드 내에서 하나의 업무 단위로 구현하게 된다.
그러므로 트랜잭션 처리는 비즈니스 계층의 메소드 단위 별로 처리하는 것이 좀 더 바람직하다. 이는 퍼시스턴스 계층에서 트랜잭션을 처리할 때보다 각 계층간의 독립성을 유지할 뿐만 아니라 재사용 성을 증가시킨다는 것을 알 수 있다.

6.1. 트랜잭션의 특성

트랜잭션은 ACID로 표현할 수 있다.

1) 원자성(Automiccity)
하나 이상의 행위들이 단일한 작업단위로 함께 묶여 구성된다. 이는 성공, 실패 시 모두 함께 반영된다는 것을 의미한다.

2) 일관성(Consistency)
트랜잭션이 종료되면 시스템은 모델링한 비즈니스 상태와의 일관성을 유지해야 한다.

3) 격리성(Isolation)
사용자들의 작업이 서로 엉키지 않고 다수가 동일한 데이터에 대해 작업하는 것을 허용해야 한다. 따라서 각 트랜잭션은 독립적이어야 하며, 동일한 데이터에 대해 동시에 읽기와 쓰기가 발생하는 일을 막아야 한다.

4) 지속성(Durability)
트랜잭션이 완료되면 트랜잭션의 결과는 지속됨으로써 어떤 시스템 문제가 일어나도 살아남을 수 있어야 한다.

6.2. Spring 트랜잭션 기본
Spring 프레임워크는 트랜잭션을 지원하기 위해 PlatformTransactionManager, TransactionDefinition, TransactionStatus 세 개의 인터페이스를 핵심 API로 사용하고 있다.

1) TransactionDefinition
개발자들이 제어 가능한(트랜잭션 전달, timeout, read-only상태, 격리성) 부분을 추상화 하고 있다.

2) TransactionStatus
트랜잭션의 상태를 관리하는 역할을 담당하는 인터페이스.
PlatformTransactionManager에서 트랜잭션을 commit할지 rollback할지를 결정하기 위하여 사용된다.

3) PlatformTransactionManager
실질적은 트랜잭션을 실행하는 역할.

– 이 종 화 (ingenuity.egloos.com) –