비밀스럽게, 안전하게
콜치아픈 녀석들은 어디에도 있는법…
웹 애플리케이션 개발자라면 웹 사이트를 보호할 의무가 있습니다. 다음 세가지 유형의 해커들을 경계해야 합니다.
임퍼스네이터 : 마치 자신을 다른사람인양 속이는 녀석
업그레이더 : 자신의 등급을 속이는 녀석
이즈드롭퍼 : 중요한 정보를 몰래 훔쳐보는 녀석
서블릿 보안의 4요소
임퍼스네이터, 업그레이더, 이즈드롭퍼 녀석들 때문에 곤경에 처한 웹 사이트를 구할 방법은 서블릿 보안 밖엔 없습니다. 서블릿 스펙에서는 서블릿 보안을 다음의 4가지 개념으로 압축하여 다릅니다. : 인증, 인가, 비밃보장, 데이터 무결성
1. 인증 : 임퍼스네이터 계획을 차단하기 위하여
2. 인가 : 업그렝더 계획을 차단하기 위하여
3.비밀보장, 데이터 무결성 : 이즈드롭퍼 계획을 차단하기 위하여
HTTP환경에서 어떻게 인증하는가: 드랜잭션 보안 기초과정
HTTP관점에서 보면..
1. 브라우저가 제일 먼저 update.jsp를 요청합니다.
2. update.jsp는 제약조건이 걸린 자원임을 알아차린 컨테이너는
3. HTTP 401(Unauthorized)응답을 보냅니다. 여기에는 www-인증헤더와 보안영역 정보가 들어있습니다.
4. 401 코드를 받아본 브라우저, 보안영역 정보에 기초하여 사용자 이름과 패스워드가 들어있지요.
5. 다시한번 update.jsp를 요청합니다. 하지만 이번 요청엔 보안 관련 HTTP 헤더에 사용자 이름과 패스워드가 들어있지요
6. 사용자 이름과 패스워드가 일치하는지 체크한다음, 맞다면 사용할수 있는 권한을 인가합니다.
7. 보안관련 체크를 모두 통과했다면 HTML 페이지를 넘겨줍니다. 아니라면 다시한번 HTTP 401 오류를 보내겠죠
컨테이너 관점에서 ..
1. 요청을 접수한다음, 컨테이너는 이 요청이 보안 테이블에 있는 URL인지 체크합니다.
2. 보안 테이블에 있는 URL이라면, 여기에 무선 보안 제약이 걸려 있는지 체크하고 만약 그렇다면 401코드를 리턴합니다.
1. 사용자 이름과 패스워드가 들어있는 요청을 접수한 컨테이너는 이 요청이 보안테이블에 있는 URL인지 체크합니다.
2. 보안테이블에 있는 URL이라면 사용자 이름과 패스워드가 일치하는지 체크합니다.
3. 사용자 이름과 패스워드가 올바르다면, 사용자가 이자원을 접근할수 있는 권한인지 확인합니다. 만약 그렇다면 클라이언트로 해당 자원을 넘겨줍니다.
컨테이너가 무슨 일을 한걸까요?
컨테이너가 한 일들 :
1. 요청한 자원에 대한 검색(lookup) 작업을 합니다.
2. 인증작업을 합니다.
3. 인가작업을 합니다.
인증안에 인가 내용까지 들어있죠
서블릿 스펙에는 컨테이너가 패스워드와 사용자 이름을 포함해서 인증데이터를 어떻게 고나리하는가에 대해선 나와 있지 않습니다. 일반적을 사용자 이름과 패스워드 역할을 관리하는 테이블이 따로 있어 여기서 지원하기 위한 방법도 따로 제공합니다. 보통 이 데이터들은 관계형 데이터베이스 또는 LDAP 시스템에 저장되어 있지요 일반적으로 이 데이터는 관리자가 책임지고 관리하지요
보안 영역
tomcat-users.xml 파일
<tomcat-users>
<role rolename=”guest”/>
<role rolename=”Member”/>
</user name=”Bill” password=”coder” roles=”Member,Guest”/>
…
</tomcat-users>
인즐 활성화 하기
인증이 작동하려면 DD 상에 간단히 다음과 같이 설정해야 합니다.
<login-config>
<auth-mothod>BASIC</auth-method>
</login-config>
인가 단계 1: 역할(롤)정의하기
서블릿 환경에서 가장 일반적인 인가 방식은 특정 보안 역할을 가진 사용자만 특정 서블릿에 HTTP 요청을 날릴수 있도록 설정하는 것이죠.
컨테이너 제조사마다 다른 파일:
tomcat-users.xml의 <role>항목
<tomcat-users>
<role rolename=”Admin”/>
<role rolename=”Member”/>
<role rolename=”Guest”/>
<user username=”Annie” password=”admin” roles=”Admin, Member, Guest”/>
<user username=”Member” password=”coder” roles=”Member, Guest”/>
<user username=”Guest” password=”newbie” roles=”Guest”/>
</tomcat-users>
서블릿 스팩:
web.xml의 <sercurity-role> 항목
<security-role>
<role-name>Admin</role-name>
<role-name>Member</role-name>
<role-name>Guest</role-name>
</security-role>
<login-config>
<auth-method> BASIC </auth-method>
</login-config>
인증 단계2 : 자원 / 메소드 제약 정의하기
DD의 <security-constraint>항목
<security-constraint>
<web-resource-collection>
<web-rescurce-name>UpdateRecipes</web-resource-name>
<url-pattern>/Beer/AddRecipe/*</url-pattern>
<url-pattern>/Beer/ReviewRecipe/*</url-pattern>
<http-method>GET</http-method>
<web-resource-collection>
<auth-constraint>
<role-name>Admin</role-name>
<role-name>Member</role-name>
</auth-constraint>
</security-constraint>
</web-app>
—————————————————핵심정리 <web-resource-collection>
ㅇ <web-resourece-collection>에는 두개의 주요 하위 항목이 있습니다. : <url-pattern>(1..*),<http-method>(옵션항목,0..*)
ㅇ 자원에 대한 요청에 제약을 걸기 위해서는 URL패턴과 HTTP 메소드가 같이 사용되어야 합니다.
ㅇ <web-resource-name>은 반드시 있어야 하는 항목입니다.
ㅇ <description>은 옵션항목입니다.
ㅇ <url-pattern>에 들어가는 값은 서블릿 표준 명명 규칙 및 매핑 규칙을 따라야 합니다.
ㅇ <url-pattern> 항목은 적어도 하나는 있어야 합니다. 하나 이상이어도 문제가 안되죠.
ㅇ <http-method>에 들어갈수 있는 메소드는 GET,POST, PUT, TRACE, DELETE, HEAD 그리고 OPTIONS 입니다.
ㅇ HTTP 메소드를 등록하지 않으면 모든 메소드에 다 제약을 건다는 의미입니다.
ㅇ <http-method>로 메소드를 등록한다는 의미는 해당 메소드에 제약을 건다는 의미이기도 하지만, 반대로 이를 제외한 나머지 HTTP 메소드는 모두 자동적으로 허용한다는 걸 의미합니다.
ㅇ <security-constraint>안에 하나이상의<web-resource-collection>을 포함할수 있습니다.
ㅇ <auth-constraint>는 ㅡsecurity-constraint>안에 있는 모든 <web-resource-collection>항목에 적용됩니다.
역할에 따라 보안을 달리 코딩하기
HttpServletRequest에는 프로그램 적인 보안과 관련된 3개의 메소드가 있으니
getUserPrincipal(): 이건 EJB에 주로 사용하는 녀석이죠.
getRemoteUser(): 이건 인증이 되었는지 안 되었는지 체크하는 메소드 입니다.
isUserInRole : HTTP메소드 레벨에서가 아니라 메소드 내에서 인가 관리를 하다는 것이 다르죠. 이 메소드를 사용하면 사용자 역할에 따라 서비스 메소드를 다르게 코딩할수 있습니다. DD의 선언적인 인가 관리를 통과한 사용자에 대해, 서비스 메소드 코드안에서 사용자가 어떤 역할이냐에 따라 조건적으로 무엇을 하도록 코딩 할수 있다는 의미 입니다.
어떻게 동작하는가?
isUserInRole()을 호출하기 전, 사용자는 반드시 인증을 거쳐야 합니다. 인증되지 않은 사용자가 이메소드를 호출하면 당연히 거짓(false)을 리턴하겠죠
2. 컨테이너는 isUserInRole()인자로 넘겨온 값, 예를 들어 Manager와 정의된 사용자 역할을 서로 비교합니다.
3. 사용자가 해당 역할이라고 한다면 참(true)을 리턴합니다.
서블릿 코드
if(request.isUserInRole(“Manager”)){
//UpdateRecipe 페이지를 호출하니다.
}
배포서술자
<web-app..>
<servlet>
<security-role-ref>
<role-name>Manager</role-name>
</role-link>Admin</role-link>
</security-role-ref>
..
</servlet>
</web-app>
<web-app>
</security-role>
<role-name>Admin</role-name>
<role-name>Member</role-name>
<role-name>Guest</role-name>
</security-role>
</web-app>
다시 살펴본 인증
4가지 인증방식
BASIC 인증은 로그인 정보를 인코딩한 형태로 전송합니다. 제법 안전한 것처럼 보이나 인코딩 방식인 base64는 아주 잘 알려진 방식이기에 생각만큼 안전하지 않스빈다.
DIGEST 인증은 로그인 정보를 보다 안전한 형태로 전송하니다. 하지만 이 암호화 매커니즘은 그리 널리 사용되는 것이 아니기에 J2EE 컨테이너가 반드시 지원해야 하는 인증방식은 아닙니다.
CLIENT-CERT 인증은 로그인 정보를 공인키 인증을 사용하여 매우 안전한 형태로 전송합니다. 이 매커니즘의 단점은 클라이언트가 시스템에 로긍니 하기 전에 인증서를 가지고 있어야 한다는 것이죠 보통 호나경에서 클라언트가 인증서를 가지고 있는 경우가 드물기에 CLIENT-CERT 인증방식은 비즈니스 환경에서 주로 사용하니다.
FORM인증에선 자신만의 로그인 폼을 제공할수 있습니다. 일반 HTML 이면 된다느 말이죠 폼에 기반하여 전송하기에 4가지 보안방식중 가장 약한 방식으로 정보를 전송합니다.
Request 데이터 보호
DD의 <security-constraint>항목은 요청이 들어온다음 일어날 일에 대한 것이죠 즉 클라이언트가 요청을 보내고 난 다음, 컨테이너가 <security-constraint>를 보고 참고하여 이 요청에 대해 어떻게 응답할지 결정하죠 이미 요청 데이터는 네트워크를 타고 전송되고 난 다음 말입니다.
비인증 클라이언트가 제약이 걸려 있지만 전송 보장 방식이 설정되어 있지 않은 자원에 요청을 보내는 경우
비인증 클라이언트가 제약이 걸려 있지만 전송 보장 방식이 설정되어 있지 않는 자원에 요청을 보내는 경우
1. 클라이언트가 DD에 <security-constraint>가 설정된 /BuyStuff.jsp 요청
컨테이너가<security-constraint>를 체크한뒤 /BuyStuff.jsp가 제약이 걸린 자원이라는 것을 인식함. 즉 클라이언트가 인증되어야 한다는 말이지 그다음 컨테이너는 이 요청이 <transport-guarantee>가 NONE라는것을 간파하고는…
2. 컨테이너는 401 응답을 클라이언트로 보냅니다. 브라우저에게 사용자에게 로그인 정보를 물어보세요 라고 요청하라는 의미죠
3. 브라우저가 동일한 요청을 한번더 날리지만, 이번에는 헤더에 사용자 로그인 정보가 들어있죠
4. 컨테이너가 클라이언트를 인증합니다. 그 다음 사용자 역할을 체크하여 해당자원에 접근할수 있는지 인가 여부를 결정합니다. 모든 체크가 끝났으면, 응답을 보냅니다.
비인증 클라이언트가 제약에 걸려 있으며, 전송 보장방식이 CONFIDENTIALITY인 자원에 요청을 보내는 경우
1. 클라이언트가 DD에 <security-constraint>가 설정되어 있으며, 전송 보장 방식이 설정도니 /BuyStuff.jsp 요청
컨테이너가 제약이 걸린 자원의 전송을 보장해서 보내야 한다는 사실을 미리 알고 있고, 그 다음 들어온 요청을 체크해보니 전혀 보안이 되지 않은 상태로 날아 왔다는 것을 알고는
2. 컨테이너는 301 응답을 클라이언트로 보냅니다. 즉 안전한 전송방식으로 요청을 다시 보낼것을 브라우저에게 요청하는거죠
3. 브라우저가 동이랗나 요청을 한번 더 리지만, 이번에는 전송방식을 바꿔 보안 연결을 해서 날리죠 즉 요청하는자원은 똑같지만 프로토콜이 HTTPS로 바뀌지요.
4. 이제 컨테이너는 이 자원에 제약이 걸려 있다는 걸 알고는 사용자 인증을 요청합니다. 즉 브라우저에게 401 응답을 보내 인증 절차를 밟을걸 요구합니다.
5. 브라우저가 동일한 요청을 한번 더 날리지만 이번에는 헤더에 사용자 로그인 정보가 들어있습니다. 물론 이 데이터는 보안 연결을 통해서 날아오겠죠 앞 페이지와는 달리 사용자 로그인 정보는 안전하게 전송됩니다.
정리: 요청을 접수하면, 첫번째로 컨테이너는 <transport-guarantee> 항목을 체크합니다. 뭔가 설정되어 있다면, 다음과 같이 질문을 던지죠 “이 요청이 보안 연결을 통해 들어온 것인가요?” 라고 말이죠 만약 보안 연결이 아니라면, 컨테이너는 인증/인가 관련 정보는 거들떠 보지 않고선 다음과 같이 퉁명스럽게 애기하죠 이봐 자네가 안전하다는걸 보장할수 있을때 다시 말을 걸라구