728x90

점점 더 많은 패스워드 보안 애플리케이션을 조직의 컴퓨터 환경에 추가한다면 이는 인증의 복잡함도 함께 가중시키는 것이고 결국 개발자와 사용자에게 부담을 주게 된다. 대부분의 엔터프라이즈 애플리케이션 통합 프로젝트들은 싱글사인온(SSO) 기능을 포함하고 있다. 이는 사용자가 다양한 애플리케이션을 사용하기 위해 단 한번만 로그인 하도록 하는 기능이다. 이 글에서 자바 플랫폼에 SSO를 구현하는 방법을 설명한다.

 

당신의 기업은 많은 자바 애플리케이션들을 구동하고 있으며 그 엔터프라이즈 리소스에 접근하기 위해서는 인증이 필요합니까? 그렇다면 싱글사인온(SSP) 보안 기능을 구현하여 사용자 침입을 최소화 할 필요가 있다. 이 글에서 Kerberos와 Java Generic Security Services API (GSS-API)를 사용하여 SSO를 구현하는 방법을 배우게 될 것이다. 우선 SSO의 정의를 설명하고 강력한 애플리케이션을 설명할 것이다. 그런 다음 Kerberos 기반의 SSO를 구현할 때 발생하는 메시지 교환 시퀀스도 연구한다. 자바 GSS-API와 GSS를 사용하여 SSO를 달성하는 전형적인 자바 애플리케이션 아키텍쳐를 간단히 소개한다. 마지막으로 이 모두를 종합하여 코드 예제를 선보인다. 자바 개발자가 GSS Kerberos 티켓으로 SSO를 어떻게 구현하는지 보게 될 것이다.

 

코드

j-gss-sso.zip (다운로드) 파일에는 이 글에 사용된 모든 코드가 포함되어 있다. 이 글의 각 리스팅은 파일 이름이 포함되어 있는 주석으로 시작한다. 이 이름들을 사용하여 각 리스팅을 j-gss-sso.zip 아카이브에 있는 상응하는 파일에 매치한다. 이 아카이브에는 Setup.txt 파일도 있다. 코드 실행 전에 이 파일을 읽기 바란다.

 

싱글사인온'이란 무엇인가?

근본적으로 싱글사인온 인증이 의미하는 것은 인증 데이터의 공유이다. 예를 들어 웨어하우징 회사의 많은 사원들은 엔터프라이즈 리소스(데이터베이스 테이블 등)에 접근하여 그들의 업무에 필요한 것을 수행해야 한다. 동시에 다른 사원들도 작업 내용에 따라 다양한 리소스가 필요하다. 회계 담당자는 회계 관련 데이터베이스 테이블에 접근해야 하는 반면 판매 담당자는 판매 관련 데이터베이스 테이블에 접근해야 한다. CEO라면 기업의 데이터베이스 어디에나 접근이 필요할 것이다.


분명한 것은, 이 기업은 무엇보다도 제대로 된 인증 메커니즘이 필요하다. 어떤 사원이 특정 리소스에 접근을 시도했는지 결정할 수 있도록 말이다. 일단 기업 인증 모듈이 사원의 존재를 알면 엔터프라이즈 구현 안의 인증 모듈은 인증이 있는 사용자가 적절한 권한을 갖고 리소스에 접근했는지의 여부를 검사할 수 있다. 사원들이 인증용 유저네임과 패스워드를 사용한다고 가정해보자. 이 기업의 인증 모듈은 당연히 유저네임과 패스워드의 데이터베이스를 갖고 있을 것이다. 들어오는 인증 요청은 유저네임-패스워드 쌍으로 동반될 것이다. 이 인증 모듈은 내부 데이터베이스의 쌍과 비교를 하게 된다.

이제 우리의 웨어하우징 회사는 이 범위안에서 실행되는 여러 애플리케이션들을 갖고있다. 다양한 애플리케이션들이 같은 엔터프라이즈의 다른 모듈을 형성한다. 각각의 애플리케이션은 그 자체로 완벽하다. 이것 나름대로 사용자 기반의 여러 다른 티어(백엔드 데이터베이스, 비지니스 로직, 사용자용 GUI)를 갖고있다는 것을 의미한다. 엔터프라이즈 애플리케이션 통합(EAI)는 이와 같은 독립된 애플리케이션들을 하나의 엔터프라이즈에 통합하는 프로젝트를 일반적으로 가르킨다.

EAI 프로젝트에서 인증 프로세스에 두드러지는 하나의 일반적인 요소가 있다. 특정 애플리케이션의 사용자는 그 엔터프라이즈 내의 또 다른 애플리케이션에 액세스 해야한다. 예를 들어 판매 데이터베이스를 사용하고 있는 판매 담당자가 특정 부품을 검색하기 위해 재고 데이터베이스에 액세스 해야 할 경우도 있다. 이러한 유형의 크로스-애플리케이션 인증을 실현시킬 수 있을까? 두 가지 선택이 있다

  • 두 애플리케이션에서 유저네임과 패스워드 데이터베이스를 중복시킬 수 있다. 따라서 이러한 두 개의 애플리케이션들이 웨어하우징 회사의 모든 사원들을 위해 인증 요청을 효율적으로 처리할 수 있도록 하는 것이다. 사용자는 두 개의 애플리케이션에서 개별적으로 인증을 처리하게 된다. 다시 말해서 자신의 유저네임과 패스워드로 들어가면서 두 애플리케이션 중 하나에 액세스하고 그 애플리케이션은 모든 인증 단계를 수행하게 된다. 유저네임과 패스워드 데이터베이스를 중복할 뿐만 아니라 인증 프로세스 오버헤드까지 중복하는 것이다. 이 솔루션에서 중복의 양은 명확해진다.

  • 두 번째 선택은 판매 애플리케이션과 재고 애플리케이션 간 싱글사인온을 통한 인증 데이터 공유를 가능하게 하는 것이다. 만일 사용자가 한 애플리케이션 인증을 갖고 있다면 그의 인증 정보는 다른 것에 전달 될 수 있다. 이 두 번째 애플리케이션은 그 모든 인증 단계를 거치지 않고도 인증 정보를 수락한다. 이 솔루션에는 중복이 없다. 두 개의 애플리케이션이 서로 신뢰하여 각 애플리케이션이 서로의 인증 데이터를 수락할 수 있도록 하는 것이 유일한 필요조건이다.

    일반적으로 SSO는 개별 인증 모듈로서 구현된다. 사용자 인증이 필요한 모든 애플리케이션들은 SSO 기반의 인증 모듈에 의지하여 사용자를 확인한다. 이 인증 정보에 따라 다양한 애플리케이션들은 각자의 인증 정책을 발효한다.

    이 웨어하우징 기업 예제는 사용자 관점에서 SSO가 무엇인지를 설명한 것이다. 다음 질문은 당연히 "SSO를 어떻게 구현하는가?"이다. 여러 방법이 있다. SSO를 포함하여 다양한 보안 서비스를 제공하는 Kerberos에 대해 알아보자.

     

    Kerberos 사용하기

    Kerberos는 Internet Engineering Task Force (IETF) 표준으로서 전형적인 키 교환 메커니즘을 정의한다. 애플리케이션들은 Kerberos 서비스를 사용하여 사용자에게 인증을 주고 암호 키를 교환한다. Kerberos는 티켓 발상에 기인한다. 하나의 티켓은 암호 키와 몇몇 정보 조각을 래핑하는 데이터 구조일 뿐이다. 키 발급 센터(KDC)는 Kerberos 티켓을 인증 사용자에게 발급한다. KDC는 두 가지 유형의 티켓을 발행한다

    • 마스터 티켓(티켓 발행 티켓 (TGT))
    • 서비스 티켓
    KDC는 우선 TGT를 클라이언트에 발행한다. 이 클라이언트는 TGT에 여러 서비스 티켓을 요청할 수 있다. TGT와 서비스 티켓의 작동 방법을 설명해주는 다음의 키 교환 시나리오를 보자:

    1. 클라이언트가 TGT의 발행을 요청하며 KDC에 메시지를 보낸다. 이 요청은 평이한 텍스트 형식(암호화 되어있지 않음)이고 클라이언트의 유저네임이 포함되어있다. 패스워드는 포함되지 않는다.
    2. KDC는 TGT를 클라이언트에 발급한다. TGT는 암호화된 형식안에 세션 키를 포함하고 있다. 이 세션 키를 암호화하려면 KDC는 클라이언트의 패스워드에서 나온 키를 사용한다. 클라이언트만이 TGT의 암호를 해독할 수 있고 세션 키를 가져올 수 있다는 것을 의미한다. 따라서 클라이언트 애플리케이션이 TGT를 요청하기 위해 패스워드를 알 필요는 없어도 TGT를 처리하고 사용할 패스워드는 필요하다.
    3. 클라이언트가 TGT의 암호를 해독하고 여기에서 세션 키를 추출한다. 클라이언트는 서비스 티켓을 위한 요청을 작성한다. 서비스 티켓은 양방간 커뮤니케이션용으로만 가능하다. 즉 클라이언트와 그 클라이언트가 통신하기 원하는 엔터티. 어떤 누구도 이 서비스 티켓을 사용할 수 없다. 따라서 서비스 티켓을 요청하는 동안 클라이언트는 서비스 티켓으로 사용 할 서버 이름을 지정한다. 이 서버는 KDC에 이미 존재하고 있어야 한다.
    4. KDC는 서버용 서비스 티켓을 작성한다. 이 티켓은 클라이언트의 인증 데이터와 새로운 암호 키(하위 세션 키)를 포함하고 있다. KDC는 서버의 비밀 키로 서비스 티켓을 암호화한다. 오직 서버만이 서비스 티켓 암호를 해독할 수 있다.
    5. KDC는 메시지를 작성하고 이 안에 서비스 티켓을 래핑한다. KDC는 이 메시지 내부에 하위 세션 키를 복사한다. 하위 세션 키는 메시지에 두 번 포함되었다는 것을 기억하라.
    6. KDC는 2, 3 단계에서 나온 세션 키로 전체 메시지를 암호화한다. 따라서 오직 클라이언트만 메시지를 해독하고 하위 세션 키와 서비스 티켓을 추출할 수 있다. 클라이언트가 서비스 티켓 암호를 해독하지 못하면 오직 서버만이 할 수 있다. 따라서 어떤 누구도 다른 목적으로 서비스 티켓을 사용할 수 없다. 그런 다음 KDC는 메시지를 클라이언트에 보낸다.
    7. 클라이언트는 KDC에서 받은 메시지를 해독하고 메시지 내부에 있는 하위 세션 키와 서비스 티켓을 가져온다. 서비스 티켓은 서버에 보낸다.
    8. 서버는 서비스 티켓을 받고 이를 해독하여 요청 클라이언트의 인증 데이터와 하위 세션 키를 가져온다. 서버는 클라이언트의 요청을 확인하고 새로운 보안 세션이 클라이언트와 서버 사이에 만들어진다. 클라이언트와 서버 모두 같은 하위 세션 키를 마련하고 있고 서로 보안 통신을 위해 이를 사용할 수 있다.
    클라이언트는 3 단계에서 8 단계 까지 또 다른 서버 애플리케이션에 반복할 수 있다. Kerberos 서비스가 인증 데이터를 공유하는 데 사용될 수 있고 같은 클라이언트가 다른 애플리케이션에도 인증을 받을 수 있다는 것을 의미한다. 이는 SSO를 효과적으로 만든다 .

     

    Java Generic Security Services API

    ETF는 Generic Security Services API (GSS-API)를 고급 보안 API로 정의했다. 이것은 Credential성, 메시지 무결성, 보안 등의 기능을 제공한다. GSS-API는 클라이언트-서버 환경에서 쉽게 작동한다. IETF는 GSS-API를 언어 독립 방식으로 정의했다.

    자바 GSS-API는 자바 스팩 형식의 GSS-API이다. Sun은 Java Community Process를 통해 자바 GSS-API를 개발했고 레퍼런스 구현도 제공했다. 이는 JDK 1.4 버전에 번들 될 것이다. (하단 참고자료 참조).

    보다 간단히 하기 위해 GSS-API를 GSS로 통칭하겠다.

    GSS는 다른 저급 보안 서비스의 상단에 고급 추상 레이어를 제공하는 것이 목적이다. Kerberos는 GSS 추상화에서 사용할 수 있는 기술 중 하나이다. GSS 추상 레이어에서 프로그래머는 보안 메커니즘을 개발 할 수 있다. 보다 낮은 레벨에서 어떤 메커니즘이 작동하고 있는지 걱정하지 않아도 된다. 이 글에서 GSS 하에서 작동하는 저급 보안 메커니즘으로서 Kerberos의 사용법에 초점을 맞추겠다.

    GSS는 GSSName, GSSCredential, GSSContext의 개념에서 작동한다. GSSName은 이메일을 확인하기 위해 입력하는 유저네임 같이 당신을 개별적 존재로 구분한다. GSSCredential은 당신의 존재를 증명하기 위해 내보이는 어떤 것이다. 이메일 확인 시 입력하는 패스워드와 같다. Kerberos 서비스 티켓은 GSSCredential의 또 다른 예제이다. GSSContext는 보안 관련 정보(암호 키)를 캡슐화하는 보안 세션 같은 것이다. GSS 기반의 애플리케이션은 GSSContext를 사용하여 안전하게 통신한다.

     

    GSS 클라이언트/서버 애플리케이션

    이 섹션에서는 GSS 기반 보안 자바 애플리케이션의 실제 구현을 설명하겠다. 재사용 가능한 JavaBeans 컴포넌트 두 개를 개발 할 것이다. 하나의 빈은 요청 클라이언트로서 작동하여 새로운 GSS 세션의 초기화를 요청한다. 다른 빈은 서버로서 작동하여 요청을 리스닝하고 클라이언트에서 오는 요청을 수락한다. 그런 다음 보안 컨텍스트를 만들고 클라이언트와 통신한다.

    이 글에서 SSO를 설명해야하기 때문에 제 삼자에 의해 호스팅되는 애플리케이션으로 KDC를 사용하여 우리 애플리케이션을 단순하게 할 것이다. 클라이언트와 서버 모두 이 KDC를 믿고 있으며 여기에서 오는 인증 데이터를 받아들인다. 따라서 이 글에 쓰인 샘플 코드를 사용하기 위해서는 Kerberos 실행이 필요하다. 하지만 GSS 호완의 KDC를 사용할 수 있다. ("KDC 설정하기" 참조)

     

    KDC 설정하기

    GSS에서의 Kerberos를 구현할 때 특별한 KDC 구현이 필요하지 않다. 따라서 GSS 기반의 모든 코드는 GSS 호완의 KDC로 작동이 될 것이다. (하단 참고자료 참조).

    이 글의 코드를 테스트 하기위해 Microsoft의 KDC 구현을 사용했다. 이것은 Windows 2000 Server 버전 및 이후 버전에서 제공한다. 이 글에 쓰인 모든 코드가 포함되어 있는 j-gss-sso.zip 파일에는 Setup.txt 파일도 포함되어 있어 Microsoft KDC 설정에 필요한 단계를 설명하고 있다.

    Microsoft KDC는 모든 Windows 사용자들을 이 서비스의 사용자로 받아들인다. KDC가 Windows 네트워크에 등록된 모든 사용자를 위해 TGT를 발급할 수 있다는 것을 의미한다

     

    JAAS 인증 클라이언트

    일단 KDC 서비스를 실행하면 계속 진행하여 TGT를 발급하도록 KDC에게 요청할 수 있다. 요청하는 GSS 클라이언트는 KDC에게 TGT를 발급하도록 요청 할 것이다.

    작은 문제가 있다. GSS는 사용자에게서 온 유저네임-패스워드 쌍을 가져올 메소드가 없다. 따라서 GSS 애플리케이션은 다른 비 GSS 메커니즘에 의존하여 로그인 정보를 얻어야 한다. 우리는 여기서 Java Authentication and Authorization Service (JAAS)를 사용하여 요청 클라이언트가 유저네임과 패스워드를 제공하고 TGT를 얻도록 하겠다.

    Listing1에서는 GSSClient 라는 클래스를 보게 될 것이다. 이 클래스는 원격 GSS 서버와 보안 세션의 설치를 원하는 GSS 클라이언트의 기능을 나타낸다. GSSClient 구조체는 많은 매개변수를 취한다:

    1. 클라이언트 피어 이름
    2. 클라이언트 피어의 패스워드
    3. 원격 서빙(serving) 피어의 이름
    4. 원격 서빙(serving) 피어의 주소
    5. 서버의 포트
    6. Kerberos 영역 또는 도메인
    7. KDC 주소
    8. 로그인 설정 파일의 이름과 위치 경로
    9. 클라이언트 설정 이름
    main() 메소드는 간단한 애플리케이션을 시뮬레이팅하고 있다. Listing 1에 이 메소드를 추가했다. 명령행에서 이를 실행하여 이 클래스의 작동을 설명하기 위해서이다. main() 메소드는 명령행에서 매개변수 값을 읽고 GSSClient() 구조체를 호출하면서 매개변수 값을 이 구조체에 보낸다.

    GSSClient() 구조체는 다른 필드에 있는 명령행에서 오는 매개변수를 저장하고 세 개의 시스템 프로퍼티를 지정한다.
    java.security.krb5.realm 시스템 프로퍼티는 KDC 범위를 지정한다. java.security.krb5.kdc 프로퍼티는 KDC 서버의 주소를 지정한다.
    java.security.auth.login.config 프로퍼티는 로그인 설정 파일의 이름과 위치 경로를 지정한다. GSS 프레임웍은 이 속성들을 내부에서 사용 할 것이다.

    GSSClient 객체를 인스턴스화 한 후에, main() 메소드는 GSSClient.login() 메소드를 호출한다. 이 메소드는 LoginContext 객체를 인스턴스화 하는데 JAAS 프레임웍의 일부이다. LoginContext 구조체는 두 개의 매개변수를 취한다. 두 번째는 BeanCallBackHandler 클래스이다. 이 둘을 자세히 살펴보자.

     

    confName과 설정파일

    confName은 JAAS 설정의 이름을 수반한다. 아홉 번째 명령행 매개변수는 클라이언트가 인증용으로 사용할 JAAS를 설정한다. 여덟 번째 매개변수로 지정된 JAAS 설정 파일에는 한 개 이상의 클라이언트 설정이 포함되어 있다. 클라이언트는 이 중 하나를 사용할 수 있다.

    JAAS 설정은 인증에 사용될 메커니즘을 정의한다. 설정 파일은 자바 애플리케이션이 인증 로직과 독립된 인증 메커니즘을 선택할 수 있도록 한다.

    JAAS 설정은 .conf 파일로 저장된다. Listing 2 의 JAAS 설정 파일 샘플에는 두 가지 설정이 있다. GssClient 설정은 다음과 같다:

     

    GSSClient {
    com.sun.security.auth.module.Krb5LoginModule required;
    };

     

    이 JAAS 설정은 com.sun.security.auth.module.Krb5LoginModule 라는 자바 클래스 이름을 지정한다. 이 클래스는 JAAS의 Kerberos 로그인 모듈이고 GSSClient 설정은 로그인에 이것을 사용한다. 따라서 이 설정을 지정한다는 것은 인증 메커니즘으로서 Kerberos를 사용한다는 것을 의미한다.

     

    Listing 1. GSSClient

    /****
    GSSClient.java
    ****/

    import java.io.*;
    import java.util.*;
    import java.security.*;
    import org.ietf.jgss.*;
    import java.net.Socket;
    import javax.security.auth.login.*;
    import javax.security.auth.Subject;

    class GSSClient
    implements java.security.PrivilegedAction {

    //Handles callback from the JAAS framework.
    BeanCallbackHandler beanCallbackHandler = null;

    //The main object that handles all JAAS login.
    LoginContext peerLC = null;

    //Socket and streams used for communication.
    Socket socket = null;
    DataInputStream inStream;
    DataOutputStream outStream;

    //This and remote clients.
    String clientName = null;
    String serverName = null;

    //Address and port of remote server.
    String serverAddress = null;
    int serverPort;

    //The name of the client configuration.
    String confName = null;

    public static void main(String[] args) {
    if (args.length < 9) {
    System.err.println("Usage: java <options> KrberosLogin "
    + " <clientname> <password> <servername> "
    + " <serveraddress> <server port> "
    + " <realm address> <kdc address> "
    + " <name and path of conf file > <conf name ");
    System.exit(-1);
    }

    GSSClient gssClient =
    new GSSClient (args[0]/*client name*/ ,
    args[1]/*password*/,
    args[2]/*clientName*/,
    args[3]/*serveraddress*/,
    Integer.parseInt(args[4])/*serverport*/,
    args[5]/*kerberos realm name*/,
    args[6]/*kdc addres*/,
    args[7]/*name and path of conf file*/,
    args[8]/*conf name*/ );

    GSSContext context = gssClient.login();
    if (context!=null)
    {
    String response = null;
    //Checking confidentiality status of context.
    if (context.getConfState())
    {
    response = gssClient.sendMessage(context,
    "A sample message from client");
    System.out.println ("Server Response "+response);
    }//if

    try {
    gssClient.getLoginContext().logout();
    context.dispose();
    } catch (Exception e) {
    e.printStackTrace();
    }//catch

    }
    else
    System.out.println("Client authentication deined...");
    }//main

    //The GSSClient constructor only sets all the required parameters.
    public GSSClient (String clientName, String password,
    String serverName, String serverAddress,
    int serverPort, String kerberosRealm,
    String kdcAddress, String confFile, String confName)
    {
    //The beanCallbackHandler will require the name and password of the client.
    beanCallbackHandler = new BeanCallbackHandler(clientName, password);
    this.clientName = clientName;
    this.serverName = serverName;
    this.serverAddress = serverAddress;
    this.serverPort = serverPort;
    this.confName = confName;
    System.setProperty("java.security.krb5.realm", kerberosRealm);
    System.setProperty("java.security.krb5.kdc", kdcAddress);
    System.setProperty("java.security.auth.login.config", confFile);

    System.out.println(this.clientName);
    }// KerberoseLoginBean


    public GSSContext login()
    {
    try {
    peerLC = new LoginContext(confName, beanCallbackHandler);
    peerLC.login();

    socket = new Socket(serverAddress, serverPort);
    inStream = new DataInputStream(socket.getInputStream());
    outStream = new DataOutputStream(socket.getOutputStream());
    return (GSSContext) Subject.doAs( peerLC.getSubject(), this);
    }
    catch (Exception e) {
    System.out.println( ">>>> GSSClient....
    Secure Context not established.." );
    e.printStackTrace();
    return null;
    }//catch

    }//establishSecureContextWithServer

    //This is the only method in PrivilegedAction interface.
    //It receives control only in case of successful authentication of the client.
    public Object run() {

    try {
    GSSManager manager = GSSManager.getInstance();
    Oid kerberos = new Oid("1.2.840.113554.1.2.2");
    GSSName clientPeerName = manager.createName(
    //Name of the client for which we want to create this GSSName object.
    clientName ,
    //Type of GSSName. Our client is a Windows user,
    //which we can specifiy using GSSName.NT_USER_NAME property.
    GSSName.NT_USER_NAME);

    GSSName remotePeerName = manager.createName(serverName, GSSName.NT_USER_NAME);
    System.out.println (">>> GSSClient... Getting client credentials");


    GSSCredential peerCredentials = manager.createCredential(
    //The GSSName object of the client.
    clientPeerName,
    //Time for which credentials whill be valid.
    10*60,
    //Kerberos mecahnism identifier.
    kerberos,
    //The client only intiates the secure context request.
    GSSCredential.INITIATE_ONLY);

    System.out.println (">>> GSSClient...
    GSSManager creating security context");
    GSSContext peerContext = manager.createContext(remotePeerName,
    kerberos,
    peerCredentials,
    GSSContext.DEFAULT_LIFETIME);

    peerContext.requestConf(true);
    byte[] byteToken = new byte[0];

    System.out.println (">>> GSSClient...
    Sending token to server over secure context");

    while (!peerContext.isEstablished()) {
    byteToken = peerContext.initSecContext(byteToken, 0, byteToken.length);
    if (byteToken != null) {
    outStream.writeInt(byteToken.length);
    outStream.write(byteToken );
    outStream.flush();
    }//if

    if (!peerContext.isEstablished()) {
    byteToken = new byte[inStream.readInt()];
    inStream.readFully(byteToken );
    }//if
    }//while (!peerContext...)

    return peerContext;

    }//try

    catch(org.ietf.jgss.GSSException ge) {
    System.out.println (">>> GSSClient...
    GSS Exception "+ge.getMessage());
    }

    catch(java.lang.Exception e) {
    System.out.println (">>> GSSClient...
    Exception "+e.getMessage());
    }//catch
    return null;
    }//run

    //Sends a message to the remote server on an already established context.
    //It returns the reply from the remote server.
    public String sendMessage(GSSContext context, String message)
    {
    byte[] serverMessage = null;
    byte[] clientMessage = null;

    MessageProp msgProp = new MessageProp(0, true);

    try {
    System.out.println(">>> GSSClient...
    Client message is ["+message+"]");
    clientMessage = context.wrap (message.getBytes(),
    0, message.getBytes().length, msgProp);
    outStream.writeInt(clientMessage.length);
    outStream.write(clientMessage);
    outStream.flush();

    //Receiving server response and sending back to client.
    serverMessage = new byte[inStream.readInt()];
    inStream.readFully(serverMessage);
    serverMessage = context.unwrap(serverMessage,
    0, serverMessage.length, msgProp);
    System.out.println(">>> GSSClient...
    Server message is ["+serverMessage+"]");
    return new String (serverMessage);
    } catch(Exception e){
    e.printStackTrace();
    return null;
    }

    }//sendMessage

    //It returns the established login context to client.
    public LoginContext getLoginContext()
    {
    return peerLC;
    }//getloginContext

    }//GSSClient

     

    Listing 2. GSSClient와 GSSServer 용 JAAS 로그인 설정

    /****
    Login.conf
    ****/

    GSSClient{
    com.sun.security.auth.module.Krb5LoginModule required;
    };

    GSSServer{
    com.sun.security.auth.module.Krb5LoginModule required
    storeKey=true;
    };

     

    BeanCallBackHandler
    Listing 1의 클라이언트를 다시 살펴보자. LoginContext 구조체 메소드 호출과 함께 전달된 두 번째 매개변수는 인증 과정 중에 콜백을 핸들할 객체를 지정한다. Callback은 자바 애플리케이션이 인증 프로세스 중에 JAAS 구현과 인터랙팅 할 수 있도록 한다. 정상적으로 콜백 기능을 사용하여 유저네임과 패스워드를 Kerberos 인증 모듈에 전달 할 수 있다. BeanCallBackHandler 구조체 call에 있는 username과 password를 전달했다는 것에 주목하라.

    Listing 3은 BeanCallBackHandler 클래스이다. 이 클래스는 CallBackHandler 라는 인터페이스를 구현한다. 이 인터페이스는 JAAS 프레임웍의 일부이고 단 하나의 메소드인 handle()을 포함하고 있다.

     

    Listing 3. JAAS 프레임웍에서 콜백 핸들링하기

    /****
    BeanCallbackHandler.java
    ****/

    import java.io.*;
    import java.security.*;
    import javax.security.auth.*;
    import javax.security.auth.callback.*;

    public class BeanCallbackHandler implements CallbackHandler {

    // Store username and password.
    String name = null;
    String password = null;

    public BeanCallbackHandler(String name, String password)
    {
    this.name = name;
    this.password = password;
    }//BeanCallbackHandler

    public void handle (Callback[] callbacks) throws
    UnsupportedCallbackException, IOException
    {
    for(int i=0; i<callbacks.length; i++) {
    Callback callBack = callbacks[i];

    // Handles username callback.
    if (callBack instanceof NameCallback) {
    NameCallback nameCallback = (NameCallback)callBack;
    nameCallback.setName(name);

    // Handles password callback.
    } else if (callBack instanceof PasswordCallback) {
    PasswordCallback passwordCallback =
    (PasswordCallback)callBack;
    passwordCallback.setPassword(password.toCharArray());

    } else {
    throw new UnsupportedCallbackException(callBack,
    "Call back not supported");
    }//else
    }//for

    }//handle

    }//BeanCallbackHandler

     

    CallBackHandler 객체의 handle() 메소드는 인증이 이루어지는 동안 제어를 자동으로 받는다.

    JAAS 프레임웍은 CallBack 객체의 어레이를 CallBackHandler 인스턴스의 handle() 메소드로 전달한다. CallBack 객체의 어레이에는 다른 유형의 콜백 객체들이 포함되어 있다. 하지만 여기에서는 두 가지 유형(NameCallBack과 PasswordCallBack)을 중심으로 설명하겠다.

    NameCallBack 객체는 JAAS 프레임웍에 username을 제공하는데 사용된다. PasswordCallBack은 인증 동안 password를 수반한다. 핸들 메소드(Listing 3)내부에서 NameCallBack 객체의 setName() 메소드를, PasswordCallBack의 setPassword() 메소드를 호출했다.

    LoginContext 구조체 호출에 따르는 두 개의 매개변수에 대한 위 논의를 요약해보면 사용자 인증에 필요한 LoginContext 객체에 모든 정보를 제공해 줄 수 있다. 따라서 Listing 1의 다음 단계는 LoginContext.login() 메소드를 호출한다. 이는 실제 로그인 프로세스를 수행할 것이다.

    인증 프로세스 중에 무엇인가 잘못된다면-예를 들어, password가 정확하지 않다거나-javax.security.auth.login.LoginException이 던져진다. 로그인 메소드 호출의 결과로 어떤 예외도 없다면 인증 프로세스가 성공했다는 것으로 간주할 수 있다.

    Kerberos 로그인을 사용하고 있기 때문에 JAAS 프레임웍은 내부적으로 KDC와의 모든 통신을 관리하고 Kerberos TGT를 보내면서 고급의 사용이 편리한 JAAS 인터페이스 뒤에 모든 기술적 상세들을 숨긴다.

    성공적인 인증은 LoginContext 객체 안에 Kerberos TGT를 로딩하게 된다. LoginContext 클래스의 getSubject() 메소드를 호출할 수 있다. 이것은 Subject라는 클래스 인스턴스를 리턴한다. Subject 인스턴스는 TGT를 래핑한다.

    이 Subject 클래스를 사용하여 우리가 로그인 했던 액션을 수행할 것이다. 보안 GSS 콘텍스트를 설치하는 것이다. 인증이 성공한 후 필요한 액션을 호출하는 방법을 보자.

    Subject 클래스에는 doAs()라는 정적 메소드가 포함되어 있다. 이것은 두 개의 매개변수를 갖는다. Listing 1의 Subject.doAs() 메소드 호출을 보자. 첫 번째 매개변수 값은 Subject 인스턴스로서 성공적인 인증 후에 획득한 것이다. doAs() 메소드는 인증된 Subject를 사용하여 권한 결정을 내린다. 이 Subject가 주어진 작동을 호출할 권한을 받았는지를 판단하는데 사용하는 것이다.

    지금까지 Subject 클래스는 TGT를 보내는데에만 사용했다. 따라서 GSSClient에 대한 권한 정책을 지정하지 않았다. 어떤 사용자라도 이 클라이언트를 실행할 수 있다. GSSClient는 주어진 작동을 실행할 때 보안 권한을 요구하지 않는다. 하지만 애플릿 같은 웹 클라이언트는 엄격한 보안 콘텍스트 하에서 실행된다. Setup.txt에는 애플릿 기반의 GSS 클라이언트를 위한 권한 정책을 작성하는 지침이 포함되어 있다.

    doAs() 메소드 호출의 두 번째 매개변수 값인 this는 GSSClient 객체(Listing 1)를 지정한다. 이 매개변수는 PriviledgedAction 인터페이스를 노출하는 객체를 기대한다. GSSClient는 그 PriviledgedAction 인터페이스를 구현한다. 하나의 클래스에 모든 클라이언트측 코드를 조합했지만 PriviledgedAction 인터페이스를 구현하는 개별 클래스를 가질 수 있다. 원한다면 말이다. 만일 그렇게 한다면 그 객체를 인스턴스화 하고 doAs() 메소드 호출과 함께 두 번째 매개변수 값으로서 이를 전달한다.

    PriviledgedAction 인터페이스에는 단 하나의 메소드 run()이 포함되어 있다. 권한 정책이 Subject로 하여금 GSSClient 클래스의 run() 메소드에 액세스 할 수 있도록 한다면 그 메소드는 개별 쓰레드 실행에서 제어를 받는다. run() 메소드가 제어를 받으면 보안 콘텍스트(TGT)가 따라온다. GSS 로직은 자동으로 그 보안 콘텍스트를 사용하고 TGT를 보낸다.

     

    GSS 클라이언트 디자인

    Listing 1의 run() 메소드에서 다음 단계를 수행하여 GSS 세션을 만들어야 한다.

    1. GSSManager를 인스턴스화 한다. GSSManager 클래스의 getInstance() 정적 메소드를 호출했던 Listing 1을 주목하라. 이것은 GSSManager 객체를 리턴한다. 이 GSSManager 객체는 적어도 Kerberos 메커니즘을 지원하고 그 밖에 다른 메커니즘도 지원할 것이다. GSSManager 클래스에는 GSS 애플리케이션이 다른 보안 메커니즘을 정의하기 위해 호출할 수 있는 메소드들이 포함되어 있다. 지금은 Kerberos 메커니즘을 중심으로 설명하는 것이니 만큼 getInstance() 메소드를 호출하여 Kerberos 메커니즘을 사용한다. Listing 1의 GSSManager 객체를 인스턴스화 한 후에 kerberos라는 Oid (Object ID) 객체를 인스턴스화 했다. 이것은 Kerberos 메커니즘을 확인한다. ."
    2. GSSName 객체를 만든다. 이것은 GSS 엔터티를 나타낸다. 투웨이 통신을 하는 동안 GSS 엔터티를 통신 피어로 생각할 수 있다. 따라서 두 개의 GSSName 객체를 만드는 것이다. 하나는 요청 클라이언트 피어(clientPeerName)용이고 또 다른 하나는 원격 피어(remotePeerName)용이다.
    3. Credential 세트를 만든다. GSS는 일반적인 보안 메커니즘이다. 따라서 기저의 기술에 의존하여 이러한 Credential을 만든다. Kerberos를 사용하고 있으므로 Kerberos 티켓은 실제 Credential이다. GSSCredential 객체를 얻으려면 GSSManager 클래스의 createCredential() 메소드를 사용한다. createCredential() 메소드는 GSSCredential 인터페이스를 노출하는 객체를 리턴한다.
    4. 보안 GSS 콘텍스트를 만든다. 이것은 두 개의 통신 피어 간 보안 통신을 확립하는데 사용된다. GSSManager의 createContext() 메소드는 GSSContext 인터페이스의 인스턴스를 만들어 리턴한다. GSSContext 객체는 실제 보안 콘텍스트를 래핑한다. Listing 1에서 GSSName과 GSSCredential 객체를 전달했다는 것에 주목하라.
    보안 콘텍스트를 래핑하는 GSSContext 객체를 가졌지만 콘텍스트 그 자체는 아직 설치되지 않았다. GSSContext 객체를 얻은 후 요청 피어가 이것의 requestConf() 메소드를 호출 할 것이다. 이 메소드는 애플리케이션이 기밀성과 데이터 무결성을 요청하게 하여 원격으로 보내는 모든 데이터가 암호화된 형식이 되도록 한다.

    Listing 1에서 requestConf() 메소드를 호출 한 후에 byteToken이라는 바이트의 어레이를 선언했고 이를 제로 사이즈 바이트로 인스턴스화 했다. byteToken 어레이는 GSS 클라이언트가 서버에서 송수신하는 데이터 바이트를 갖고있게된다.

    GSSContext 인터페이스의 initSecContext() 메소드를 while 루프에서 반복적으로 호출한다. 이 메소드는 실제 바이트 교환을 수행하여 요청 피어와 서빙 피어 간 보안 콘텍스트를 확립한다.

    while(!peerContext.isEstablished()) 블록(Listing 1)은 실제로 GSS 클라이언트와 서버 간 투웨이 통신을 수행한다. 이 블록은 보안 콘텍스트가 설치되었을 때에만 종료된다.

    while 루프가 첫번째로 실행될 때 byteToken 어레이에는 데이터가 없다. peerContext.isEstablished() 메소드는 false를 리턴할 것이다. 보안 콘텍스트가 아직 설치되지 않았기 때문이다.

    while 루프 내부에서 우리가 수행하는 첫 번째 일은 byteToken 어레이상에서 initSecContext() 메소드를 전달하는 것이다. 이 메소드는 두 가지의 일을 한다. 클라이언트가 서버에 보내는 바이트를 만들어내고 서버에서 오는 바이트를 받아들인다. 바이트 교환은 GSS 콘텍스트가 확립될 때 까지 계속된다.

    처음에 initSecContext() 메소드를 호출 할 때 전달할 바이트가 없다. 따라서 비어있는 byteToken 어레이에서 첫 번째 호출용 메소드에 전달한다. initSecContext() 메소드는 몇몇 바이트를 리턴하는데 이것은 같은 byteToken 어레이에 저장되었다. 그 다음 byteToken을 아웃풋 스트림에 작성하고 스트림을 플러시 하여 byteToken 어레이가 원격 서버로 보내지도록 한다.

    원격 서버에 보내진 바이트에 대한 응답으로 원격 서버는 어떤 것을 보낼것으로 기대할 수 있다. 따라서 우리는 인풋 스트림에서 바이트를 읽고 같은 byteToken 어레이에 이 바이트를 저장하고 byteToken 어레이를 다시 initSecContext() 메소드에 보낸다. 이 메소드는 다시 바이트 어레이를 리턴한다. 이러한 바이트 교환은 while 루프 내부에서 보안 콘텍스트가 확립되고 peerContext.isEstablished() 메소드가 true를 리턴할 때 까지 지속된다.

    보안 콘텍스트의 설치가 의미하는 것은 적절한 Kerberos 키들이 클라이언트와 서버 양측에서 사용가능한 상태가 된다는 것을 의미한다. 양측에서 이 키들을 사용하여 보안 통신에 쓴다. 보안 콘텍스트가 성립되면 GSSContext 객체를 리턴한다.

     

    GSS 콘텍스트 사용하기

    GSSClient 클래스의 main() 메소드에서 GSSContext 객체를 사용하는 방법을 살펴보겠다. login() 메소드를 호출한 후 login() 메소드가 리턴한 GSSContext 이 null인지를 확인한다. null이 아니라면 보안 콘텍스트가 원격 서버와의 보안 통신에 사용될 수 있다.

    main() 메소드는 원격 서버가 기밀성과 메시지 무결성이라는 클라이언트 요청에 합당한지를 점검한다. 서버가 요청에 합당하면 GSSContext 클래스의 getConfState() 메소드는 true를 리턴한다.

    main() 메소드는 원격 메소드로 보내는 데이터가 무엇이든 보낼 수 있다. sendMessage() 헬퍼 메소드를 작성하여 데이터를 서버에 보냈다. GSSContext 클래스의 wrap() 메소드는 데이터의 바이트 어레이를 받아들이고 바이트 어레이를 리턴한다. sendMessage() 메소드는 플레인 텍스트 데이터를 wrap() 메소드에 보내고 응답으로 암호화된 바이트 어레이를 받는다. 아웃풋 스트림 상에서 바이트 어레이를 작성하여 암호화된 바이트 어레이를 원격 서버로 보낼 수 있다.

    sendMessage() 메소드는 서버에서 오는 인커밍 데이터를 리스닝한다. 서버에서 데이터를 받으면 인커밍 데이터를 GSSContext 클래스의 unwrap() 메소드에 보낼 수 있다. 이것은 평이한 텍스트 형식의 데이터를 리턴한다

     

    GSS 서버 애플리케이션

    지금까지 GSS 클라이언트 애플리케이션이 작동 방법을 보았다. 이제 이것과 인터랙팅 할 서버를 구현해본다. Listing 4는 GSSServer 클래스용 코드이다. GSSServer의 startServer() 메소드는 GSSClient 클래스의 login() 메소드를 설명하면서 거론되었던 것과 같은 기능을 수행한다.

    GSSServer 클래스의 run() 메소드 내부에서 GSSManager와 GSSName 객체를 만들었다. 지금까지 클라이언트측 코드와 서버측 코드 사이에 차이점이 거의 없었다. 하지만 좀더 자세히 보면 서버는 단 하나의 GSSName 객체를 만든다는 것을 알 수 있다. 이것은 서버를 나타낸다. GSSName을 만든 후에 이 서버는 createCredential() 메소드를 호출하여 GSSCredential 객체에 credentials를 로딩한다.

    다음 단계는 GSS 콘텍스트를 만들기 위해 createContext() 객체를 호출하는 것이다. createContext() 메소드 호출은 GSS 클라이언트 애플리케이션에서 이루어진 createContext() 호출과는 다르다. 지금의 createContext() 메소드는 단 하나의 매개변수의 서버의 credential을 갖는다. 이 서버측 콘텍스트가 양측 사이에 있지 않다는 것을 의미한다.

    다음에는 통신용 인풋과 아웃풋 스트림을 만든다. 그런 다음 보안 콘텍스트가 설치될 때까지 계속 루핑 할 while 루프로 들어간다. 이 루프 안에서 요청 클라이언트를 기다려 연결 구축 요청을 보낸다. while 루프 내부의 인풋 스트림 상에서 데이터를 받으면 데이터를 바이트 어레이로 읽고 바이트 어레이 토큰을 GSSContext 클래스의 acceptSecContext() 메소드에 제공한다. acceptSecContext() 메소드는 데이터 바이트를 리턴한다. 이것은 아웃풋 스트림으로 되돌아간다.

    GSSContext의 initSecContext()와 acceptSecContext() 메소드는 결합하여 작동한다. GSS 클라이언트 애플리케이션을 설명하면서 initSecContext() 메소드의 사용법을 설명했다. initSecContext() 메소드는 GSS 클라이언트가 GSS 서버 애플리케이션으로 보내는 초기 바이트를 만들어낸다. acceptSecContext() 메소드는 이러한 인커밍 바이트를 받아들이고 이것의 바이트 어레이를 만들어낸다. 이것을 클라이언트로 보낸다. 이러한 바이트 교환은 보안 GSS 콘텍스트가 구축될 때 까지 지속된다.

    GSS는 모든 통신을 바이트 어레이 토큰으로서 핸들한다. 클라이언트에서 서버로 바이트 어레이를 나르기위해 어떤 유형의 전송 서비스를 사용할 수 있다. GSS는 데이터 전송에 사용되는 것에는 관심이 없다.

    보안 세션의 구축 과정은 요청 클라이언트의 인증으로 끝을 맺는다. GSSContext 클래스의 getSrcName() 메소드를 호출하여 인증된 클라이언트의 GSSName을 보낼 수 있다. 반면 GSSContext 클래스의 getTargName() 메소드는 원격 클라이언트의 요청을 수락한 서버의 GSSName을 리턴한다.

    while (!context.isEstablished()) 루프가 리턴한 후에(Listing 4) run() 메소드는 클라이언트로 부터 통신을 기다린다. 인커밍 데이터를 계속해서 리스닝하고 인커밍 바이트 스트링을 받게되면 GSSContext 클래스의 unwrap() 메소드를 통해 스트링을 보낸다. unwrap() 메소드는 클라이언트에서 온 평이한 텍스트 형식의 메시지를 리턴한다.

     

    Listing 4. GSSServer

    /****
    GSSServer.java
    ****/

    import org.ietf.jgss.*;
    import java.io.*;
    import java.net.Socket;
    import java.net.ServerSocket;

    import java.util.*;
    import java.security.*;
    import javax.security.auth.callback.*;
    import javax.security.auth.login.*;
    import javax.security.auth.Subject;
    import com.sun.security.auth.callback.TextCallbackHandler;

    public class GSSServer implements java.security.PrivilegedAction {

    //Handles callback from the JAAS framework.
    BeanCallbackHandler beanCallbackHandler = null;

    //The main object that handles all JAAS login.
    LoginContext serverLC = null;

    //The context for secure communication with client.
    GSSContext serverGSSContext = null;

    //Socket and streams used for communication.
    ServerSocket serverSocket = null;
    DataInputStream inStream = null;
    DataOutputStream outStream = null;

    //Name and port of server.
    String serverName = null;
    int serverPort;

    //Configuration file and the name of the client configuration.
    String confFile = null;
    String confName = null;

    public static void main(String[] args) throws IOException, GSSException
    {

    if (args.length < 6) {
    System.err.println("Usage: java <options>
    RemoteServer <server name> <port>
    <relam> <kdc> <conf file> <conf name>");
    System.exit(-1);
    }

    GSSContext context = null;
    GSSServer server = new GSSServer (args[0]/*serverName*/,
    args[1]/*password*/,
    Integer.parseInt(args[2])/*port*/,
    args[3]/*kerberos realm name*/,
    args[4]/*kdc address*/,
    args[5]/*confFile*/,
    args[6]/*confName*/);

    //Starting the server.
    server.startServer();

    }//main

    //GSSServer constructor
    public GSSServer (String serverName, String password,
    int serverPort, String kerberosRealm,
    String kdcAddress, String confFile, String confName)
    {
    beanCallbackHandler = new BeanCallbackHandler(serverName, password);
    this.serverName = serverName;
    this.serverPort = serverPort;
    this.confName = confName;
    System.setProperty("java.security.krb5.realm", kerberosRealm);
    System.setProperty("java.security.krb5.kdc", kdcAddress);
    System.setProperty("java.security.auth.login.config", confFile);

    }//GSSServer

    public boolean startServer()
    {
    try {
    serverLC = new LoginContext(confName, beanCallbackHandler);
    serverLC.login();
    Subject.doAs(serverLC.getSubject(), this);
    return true;
    } catch (Exception e) {
    System.out.println(">>> GSSServer...
    Secure Context not established.." );
    return false;
    }//catch

    }//start

    public Object run()
    {
    try {
    serverSocket = new ServerSocket(serverPort);
    GSSManager manager = GSSManager.getInstance();
    Oid kerberos = new Oid("1.2.840.113554.1.2.2");

    System.out.println(">>> GSSServer starts....
    Waiting for incoming connection");

    GSSName serverGSSName = manager.createName(serverName,null);
    GSSCredential serverGSSCreds = manager.createCredential(serverGSSName,
    GSSCredential.INDEFINITE_LIFETIME,
    kerberos,
    //The server accepts secure context request.
    GSSCredential.ACCEPT_ONLY);

    serverGSSContext = manager.createContext(serverGSSCreds);

    Socket clientSocket = serverSocket.accept();
    inStream = new DataInputStream(clientSocket.getInputStream());
    outStream = new DataOutputStream(clientSocket.getOutputStream());

    byte[] byteToken = null;

    while (!serverGSSContext.isEstablished())
    {
    byteToken = new byte[inStream.readInt()];
    inStream.readFully(byteToken);
    byteToken = serverGSSContext.acceptSecContext (byteToken,
    0, byteToken.length);

    if (byteToken!= null)
    {
    outStream.writeInt(byteToken.length);
    outStream.write(byteToken);
    outStream.flush();
    }//if
    }//while (!context.isEstablished())

    String clientName =serverGSSContext.getTargName().toString();
    String serverName = serverGSSContext.getSrcName().toString();
    MessageProp msgProp = new MessageProp(0, false);

    byteToken = new byte[inStream.readInt()];
    inStream.readFully(byteToken);

    //Unwrapping and verifiying the received message.
    byte[] message = serverGSSContext.unwrap(byteToken, 0,
    byteToken.length, msgProp);

    System.out.println(">>> GSSServer Message
    ["+new String(message)+" ] received");

    //Wrapping the response message.
    message = new String(">>> GSSServer Secure Context establish between
    ["+clientName+"] and ["+serverName+"]").getBytes();

    message = serverGSSContext.wrap(message, 0,
    message.length, msgProp);
    outStream.writeInt(message.length);
    outStream.write(message);
    outStream.flush();
    System.out.println(">>> GSSServer Message
    ["+new String(message)+"] sent");

    //Disposing and closing client and server sockets.
    serverGSSContext.dispose();
    clientSocket.close();
    serverSocket.close();
    System.out.println(">>> GSSServer shutdown.... ");
    }//try
    catch(java.lang.Exception e){
    e.printStackTrace();
    }

    return null;

    }//run

    }//GSSServer

     

    브라우저 속의 GSS

    Listing 5는 애플릿이 Listing 1의 GSS 클라이언트를 사용하여 Listing 4의 GSS 서버와의 보안 통신을 구축하는 방법이 나와있다. 이 애플릿은 HTML 페이지에서 실행될 것이다. (Listing 6):

     

    Listing 5. GSSClientApplet

    /****
    GSSClientApplet.java
    ****/

    import java.awt.*;
    import java.awt.event.*;
    import java.applet.Applet;

    import org.ietf.jgss.*;

    public class GSSClientApplet extends Applet {

    //Instance of GSSClient bean and GSS context.
    GSSClient gssClient = null;
    GSSContext context = null;

    //Labels for user input fields.
    Label lblUserName = new Label ("EMarketplace ID :");
    Label lblPassword = new Label ("Password :");

    //Text input fileds for user name and password.
    TextField tfUserName = new TextField (12);
    TextField tfPassword = new TextField (12);

    //Buttons representing emarketplace partners.
    Button buttonPartner1 = new Button(" Login to Partner1 ");
    Button buttonPartner2 = new Button(" Login to Partner2 ");
    Button buttonPartner3 = new Button(" Login to Partner3 ");

    Color bgColor = new Color (204,204,255);

    //TextArea to show login progress.
    TextArea taResponse = null;

    //GSS related parameters.
    String remotePeer = null;
    String kerberosRealm = null;
    String kdcAddress = null;
    String addressOfRemotePeer = null;
    int portOfRemotePeer;
    String confName = null;
    String confFile = null;

    // Intializes applet with appropiate layout and listners.
    public void init()
    {
    setLayout(new FlowLayout(FlowLayout.CENTER));
    add(lblUserName);
    add(tfUserName);
    add(lblPassword);
    add(tfPassword);

    buttonPartner1.setBackground(bgColor);
    buttonPartner2.setBackground(bgColor);
    buttonPartner3.setBackground(bgColor);

    kerberosRealm = "EMARKET.LOCAL";
    kdcAddress = "pak.emarket.local:88";
    addressOfRemotePeer = "pak";

    confFile = "C:/login.conf";
    confName = "GSSClient";

    add(buttonPartner1);
    buttonPartner1.addActionListener ( new ActionListener() {
    public void actionPerformed(ActionEvent evt)
    {
    remotePeer = "partner1";
    portOfRemotePeer = 1080;
    login();
    }
    }//ActionListener
    );

    add(buttonPartner2);
    buttonPartner2.addActionListener ( new ActionListener() {
    public void actionPerformed(ActionEvent evt)
    {
    remotePeer = "partner2";
    portOfRemotePeer = 1082;
    login();
    }//action performed
    }//ActionListener
    );

    add(buttonPartner3);
    buttonPartner3.addActionListener ( new ActionListener() {
    public void actionPerformed(ActionEvent evt)
    {
    remotePeer = "partner3";
    portOfRemotePeer = 1084;
    login();
    }//action performed
    }//ActionListener
    );

    taResponse = new TextArea("[Output Window]....\n\r",12,58);
    taResponse.setBackground(Color.white);
    add (taResponse);

    }//init()

    private void login()
    {
    try {

    if (tfUserName.getText().equals("") @amp;@amp;
    tfPassword.getText().equals(""))
    taResponse.append("Please use your E-Commerce site Id to login..\n\r");
    else
    {
    gssClient = new GSSClient (
    tfUserName.getText()+"@"+kerberosRealm,
    tfPassword.getText(),
    remotePeer,
    addressOfRemotePeer,
    portOfRemotePeer,
    kerberosRealm,
    kdcAddress,
    confFile,
    confName);

    taResponse.append(tfUserName.getText()+" being logged in..\n\r");
    context = gssClient.login();
    if (context!=null)
    {
    //Checking confidentiality status of context.
    if (context.getConfState())
    {
    String message = new String ("A sample message from client");
    taResponse.append("You are successfully logged in.. \n\r");
    taResponse.append("Sending ["+message+"] to server \n\r");
    String response = gssClient.sendMessage(context, message);
    taResponse.append("Server response ... "+response+"\n\r");
    }
    else
    taResponse.append("Context confidentiality failed...\n\r");

    //Closing Login and GSS contexts.
    try {
    gssClient.getLoginContext().logout();
    context.dispose();
    } catch (Exception e) {
    e.printStackTrace();
    }//catch
    }
    else
    taResponse.append("Context establishment failed...\n\r");

    }//else

    }//try
    catch (Exception e) {
    taResponse.append("Exception..."+e.getMessage()+"\n\r");
    }//catch

    }//login

    }//GSSClientApplet

     

    Listing 6. GSS 애플릿을 사용하는 HTML 페이지

    <!--
    E-Commerce Login.html
    -->

    <HTML>
    <HEAD>
    <TITLE>E-Commerce Login... </TITLE>
    </HEAD>
    <BODY>
    <p align="center">
    <table bgcolor="Gray">
    <tr>
    <td align="center">
    <b>E-Commerce Site Login Page </b>
    </td>
    </tr>
    <tr>
    <td>
    <Applet
    CODE="GSSClientApplet.class"
    archive="GSSClientApplet.jar"
    name="GSSClientApplet"
    width="500" height="280">
    </Applet>
    </td>
    </tr>
    </table>
    </p>
    </BODY>
    </HTML>

     

    이 애플릿이 e-커머스 웹 사이트의 메인 페이지에서 실행된다고 생각해보자. 그림 1은 실행 모습을 나타낸 것이다. 두 개의 텍스트 엔트리 필드, 버튼 세 개, 하나의 텍스트 영역이 있다. 세 개의 버튼 각각은 e-커머스 웹 사이트의 파트너의 서버측 구현에 상응한다.

    그림 1. GSS 애플릿 사용



    e-커머스 웹 사이트 고객은 어떤 사이트 파트너라도 인증받을 수 있다. 애플릿의 텍스트 필드에 유저네임과 패스워드를 입력하고 인증을 원하는 파트너 사이트에 해당하는 Login 버튼을 누른다. 이 버튼의 이벤트 핸들러는 GSSClient 구조체에 필요한 매개변수를 제공한다. 나머지 작업은 GSS 클라이언트가 할 일이고 이것은 이미 설명했다.

     

    참고자료

    - developerWorks worldwide 사이트에서 이 기사에 관한 영어원문
    - 소스코드 다운로드.
    - RFC 1510.
    - RFC 1508 & RFC 1964.
    - GSS page: Sun Web site.
    - KDC implementation: MIT.
    - CSG group & Heimdal.
    - "Enhance Java GSS-API with a login interface using JAAS" : Thomas Owusu (developerWorks, November 2001).
    - "Java security, Part 2: Authentication and authorization" : Brad Rubin (developerWorks,July 2002).
    - RFC 2025.
    - developerWorks Java technology zone.

     

    출처 : Tong - 미니아빠님의 개발자료실통


  • '참고자료 > SSO' 카테고리의 다른 글

    SSO(Single Sign On)  (0) 2009.02.13
    Open Source SSO 에 대한 핵심 정리 파워포인트 자료  (0) 2009.02.13
    DB를 이용한 SSO 구현 방법론  (0) 2009.02.13
    Single Sign On  (0) 2009.02.13
    SSO 적용 모델  (0) 2009.02.13

    + Recent posts