2007 년 8월과 9월에 썬 개발자 네트워크의 John O'Conner는 JavaFX 스크립트 프로그래밍 언어
(이 기사에서는 JavaFX 스크립트라고 줄여서 부름)를 시작하는 사용자에게 도움을 주고자
"학습 곡선 일지(Learning Curve Journal)"라는 제목의 시리즈를 기고했습니다.

그 이후로 이 언어의 많은 중요한 부분이 개선되었습니다.
아마도 가장 중요한 변화는 JavaFX 스크립트의 초기 인터프리터 기반 버전을 대신하여 컴파일러 기반 버전을
사용할 수 있게 되었다는 점입니다. 이전의 학습 곡선 일지에서는 인터프리터 기반 버전 사용에 대해 설명했습니다.

업데이트된 학습 곡선 일지에서는 컴파일러 기반 버전의 언어 사용법을 보여줍니다.
최신 내용을 반영하여 다른 변경 사항도 적용되었습니다.

사용자 인터페이스를 정의하기 위해 10년 가까이 자바 프로그래밍 언어를 사용해온 저는

JavaFX 스크립트를 처음 사용해보고 두 가지 환경 사이의 큰 차이점을 금방 느낄 수 있었습니다.

프로그래머는 자바 언어에서 사용자 인터페이스(UI) 정의를 위해 절차적 언어를 사용하지만

JavaFX Script에서는 UI 정의에 선언적 문을 사용할 수 있습니다.

이는 커다란 차이이며 여기에 적응하는 데는 약간의 시간과 노력이 필요할 수 있습니다.

UI 생성을 위한 새로운 선언적 스타일에 대해 알아보고자

기존 애플리케이션 UI를 자바 언어 구현에서 JavaFX 스크립트로 이식하기로 했습니다.

썬 개발자 네트워크자바 언어 허브에 있는 Swingworker 기사에서 만든 이미지 뷰어 애플리케이션을 선택했습니다.

 

원래 애플리케이션은 Java SE 6에서 SwingWorker 클래스의 사용 방법을 보여주기 위한 것이었지만

UI 자체는 JavaFX 스크립트로의 간편한 이전을 제공할 만큼 충분히 단순해 보입니다.


기존 사용자 인터페이스

기존 애플리케이션에서는 사용자가 유명한 Flickr 웹 사이트에서 이미지를 검색, 나열 및 표시할 수 있도록 했습니다.

사용자는 검색어를 입력할 수 있으며 애플리케이션은 REST API를 사용하여 매칭되는 축소판 이미지 목록을 Flickr에 쿼리합니다.

사용자는 축소판 이미지를 하나 선택하여 크고 상세한 이미지를 가져올 수 있습니다.

그림 1은 검색 결과가 나온 기존 애플리케이션을 보여줍니다.

 

그림 1. 검색 결과가 나온 애플리케이션 UI

이 UI는 위에서부터 아래로 다음 구성요소로 이루어져 있습니다.

  • 기본 프레임 창 컨테이너 
  • 검색 레이블 및 검색 텍스트 필드 
  • 검색 매칭 레이블 및 진행 표시줄
  • 매칭되는 축소판 이미지 목록과 짧은 설명 
  • 선택 레이블 및 진행 표시줄
  • 선택한 이미지를 보여주는 레이블 
     

UI는 JFrame, JLabel, JProgressBar, JScrollPaneJList.와 같은 일반 Swing 구성요소로 이루어집니다.

JList 구성요소는 축소판 이미지와 제공되는 짧은 설명을 표시하기 위한 사용자 정의 렌더러를 갖지만 여전히 상대적으로 간단한 UI로서

JavaFX 스크립트의 선언적 UI 측면을 조사하는 데 도움이 될 것입니다.

전체 애플리케이션의 구현을 시도해 보겠지만 현재로는 기존 UI와 적당히 비슷한 정도로도 충분합니다.

작동하는 데모처럼 극적이진 않겠지만 그림 2에 나온 비활성 UI는 JavaFX 스크립트의 선언적 UI에 대한 초기 목표를 보여줍니다.

 

그림 2. 애플리케이션 UI

원래 UI는 NetBeans IDE 6.1과 Matisse GUI 편집기를 사용하여 구현했습니다.

Swingworker 기사에서 모든 원래 코드 및 생성된 UI 주변의 코드를 다운로드 받을 수 있습니다.

코드에서 이 UI를 생성하기 위해 NetBeans IDE가 GroupLayout를 어떻게 사용했는지 볼 수 있습니다.

 

private void initComponents() {
    lblSearch = new javax.swing.JLabel();
    txtSearch = new javax.swing.JTextField();
    lblImageList = new javax.swing.JLabel();
    scrollImageList = new javax.swing.JScrollPane();
    listImages = new JList(listModel);
    lblSelectedImage = new javax.swing.JLabel();
    lblImage = new javax.swing.JLabel();
    progressMatchedImages = new javax.swing.JProgressBar();
    progressSelectedImage = new javax.swing.JProgressBar();

    setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
    setTitle("Image Search");
    lblSearch.setText("Search");
    lblImageList.setText("Matched Images");

    // ...
    // event listeners, models, and cell renderers removed for this example
    //

    lblSelectedImage.setText("Selected Image");

    lblImage.setBorder(javax.swing.BorderFactory.createLineBorder(
            new java.awt.Color(204, 204, 204)));
    lblImage.setFocusable(false);
    lblImage.setMaximumSize(new java.awt.Dimension(500, 500));
    lblImage.setMinimumSize(new java.awt.Dimension(250, 250));
    lblImage.setOpaque(true);
    lblImage.setPreferredSize(new java.awt.Dimension(500, 250));

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
            .addContainerGap()
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                .addComponent(lblImage, javax.swing.GroupLayout.Alignment.LEADING,
                        javax.swing.GroupLayout.DEFAULT_SIZE, 462, Short.MAX_VALUE)
                .addComponent(scrollImageList, javax.swing.GroupLayout.DEFAULT_SIZE,
                        462, Short.MAX_VALUE)
                .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                        .addComponent(lblImageList)
                        .addComponent(lblSelectedImage))
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                        .addComponent(progressMatchedImages, javax.swing.GroupLayout.DEFAULT_SIZE,
                                350, Short.MAX_VALUE)
                        .addComponent(progressSelectedImage, javax.swing.GroupLayout.DEFAULT_SIZE,
                                350, Short.MAX_VALUE)))
                .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                    .addComponent(lblSearch)
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                    .addComponent(txtSearch, javax.swing.GroupLayout.DEFAULT_SIZE,
                            411, Short.MAX_VALUE)))
            .addContainerGap())
    );
    layout.setVerticalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(layout.createSequentialGroup()
            .addContainerGap()
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                .addComponent(lblSearch)
                .addComponent(txtSearch, javax.swing.GroupLayout.PREFERRED_SIZE,
                        javax.swing.GroupLayout.DEFAULT_SIZE,
                        javax.swing.GroupLayout.PREFERRED_SIZE))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addComponent(lblImageList)
                .addComponent(progressMatchedImages, javax.swing.GroupLayout.PREFERRED_SIZE,
                        javax.swing.GroupLayout.DEFAULT_SIZE,
                        javax.swing.GroupLayout.PREFERRED_SIZE))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(scrollImageList, javax.swing.GroupLayout.PREFERRED_SIZE, 235,
                    javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addComponent(lblSelectedImage)
                .addComponent(progressSelectedImage, javax.swing.GroupLayout.PREFERRED_SIZE,
                        javax.swing.GroupLayout.DEFAULT_SIZE,
                        javax.swing.GroupLayout.PREFERRED_SIZE))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(lblImage, javax.swing.GroupLayout.DEFAULT_SIZE, 305, Short.MAX_VALUE)
            .addContainerGap())
    );
    pack();
} 

 

 

NetBeans IDE를 사용한 UI 레이아웃은 쉽습니다. 드래그 앤 드롭이면 충분합니다.
이 예제에서 NetBeans IDE 6.1은 여러 가지 호스트 플랫폼에서 정확한 크기, 위치 및 구성요소의 간격을 제공하는 javax.swing.GroupLayout을 사용하여 UI 코드를 생성했습니다.
결과 코드는 그다지 읽기 쉽진 않지만 도구 지원이 뛰어나 레이아웃 코드를 직접 수동으로 작업할 필요가 없습니다.

 
선언적 JavaFX 스크립트 인터페이스

JavaFX 스크립트용의 설계 도구가 개발 중이지만 아직은 사용할 수 없습니다.
그러나 JavaFX 스크립트 플러그인이 포함된 NetBeans IDE 6.1의 미리보기 기능을 사용하면
수동으로 UI 코드를 입력하고 결과를 즉시 볼 수 있습니다.

이 인터페이스에 필요한 UI 구성요소는 javafx.ext.swing 패키지 및 javafx.scene으로 시작하는

다양한 패키지에 들어 있습니다. UI 구축은 계층 구조적인 Swing 기반 접근 방법을 따릅니다.

이 시리즈의 1편에서 언급한 것처럼 앞으로 JavaFX 개발자는 노드 기반 접근 방법을 사용할 것입니다.

노드 기반 접근 방법으로 UI를 구축하면 javafx.application 클래스의 일부 클래스가 필요할 것입니다.

JavaFX 스크립트는 패키지 구조 및 가져오기 문을 지원한다는 점에서 자바 언어와 유사합니다.

구성요소에 대해 공부하는 동안은 전체 패키지를 가져오는 대신 한 번에 하나씩 특정 구성요소를 가져오겠습니다.

이는 매우 지루하지만 하나씩 사용하게 함으로써 패키지에 어떤 구성요소가 있는지 보여줍니다.

 

JavaFX 스크립트 구성요소는 height, width, textcontent와 같은 속성을 갖습니다.

content 속성은 자녀 구성요소를 갖습니다. 구성요소에 따라 content 속성은 array로 선언되는

여러 개의 구성요소를 포함할 수 있습니다. 이미지 검색 애플리케이션을 위해 사용자 인터페이스를 선언할 때는

JavaFX 스크립트 구성요소의 선택 및 사용 후 속성을 설정합니다.

예를 들어 다음의 짧은 스크립트는 빈 프레임을 정의합니다. 이 코드는 컨텐츠가 없는 프레임 컨테이너를 생성합니다.

import javafx.ext.swing.SwingFrame;

  SwingFrame {
      title: "JFX Image Search"
      height: 500
      width: 500
      visible: true
  }
 

Swing의 JFrame에 해당하는 JavaFX 스크립트는 javafx.ext.swing.SwingFrame입니다.

title 속성은 프레임에 나타나는 텍스트인 창 프레임 제목을 선언합니다.

heightwidth 속성은 픽셀 단위로 크기 규격을 정의합니다.

마지막으로 visible 속성은 Swing의 JFrame 클래스 setVisible 메소드와 유사하게 프레임의 가시성 여부를 선언합니다.


Border Panel 및 Flow Panel
 

javafx.ext.swing 패키지에는 SwingFrame, Label, BorderPanel, FlowPanelList와 같은 구성요소가 포함됩니다.

이러한 이름은 일반적인 Swing 구성요소 같으므로 대상 UI 구현에 이들을 먼저 사용해보겠습니다.

javafx.scene 패키지의 HorizontalAlignment 구성요소와 javafx.scene.paint 패키지의 Color 구성요소도 사용했습니다.

그러나 JavaFX 스크립트 패키지에 ProgressBar 구성요소는 아직 없습니다.

따라서 진행 표시줄을 생성하는 함수를 코딩했습니다. 이 시리즈의 3편에서는 JavaFX 스크립트 함수를 검토합니다.

BorderPanelFlowPanel 등의 구성요소와 진행 표시줄의 함수를 사용하기로 결정하고 다음

JavaFXImageSearchUI1.fx 코드를 생성했습니다.

 

package com.sun.demo.jfx;

  import javafx.ext.swing.SwingFrame;
  import javafx.ext.swing.BorderPanel;
  import javafx.ext.swing.FlowPanel;
  import javafx.ext.swing.Label;
  import javafx.ext.swing.TextField;
  import javafx.ext.swing.List;
  import javafx.ext.swing.Component;
  import javafx.scene.paint.Color;
  import javafx.scene.HorizontalAlignment;
  import java.awt.Dimension;

  function createProgressBar(preferredSize:Integer[]) :Component {
      var jprogressbar = new javax.swing.JProgressBar();
      var comp = Component.fromJComponent(jprogressbar);
      comp.preferredSize = preferredSize;
      comp;
  }

  SwingFrame {
      title: "JFX Image Search"
      content: BorderPanel  {

          top: FlowPanel {
              alignment: HorizontalAlignment.LEFT
              content: [
              Label {
                  text: "Search"
                  },
              TextField {
                  columns: 50
              }
              ]
          }
          center: BorderPanel {
              top: FlowPanel {
                  alignment: HorizontalAlignment.LEFT
                  content: [
                  Label {
                      text: "Matched Images"

                      },
                  createProgressBar([360, 20])
                  ]
              }
              center: List {
                  preferredSize: [100, 200]
              }
              bottom: FlowPanel {
                  alignment: HorizontalAlignment.LEFT
                  content: [
                  Label {
                      text: "Selected Image"
                      },
                  createProgressBar([365, 20])
                  ]
              }
          }
          bottom: Label {
                  preferredSize: [400, 300]

          }

      }
      visible: true
  }

 

NetBeans IDE 6.1에 프로젝트를 생성한 후에 JFXImageSearchUI1.fx 파일에 위의 코드를 입력했습니다. 
미리보기 버튼을 클릭하면 그림 3과 같은 결과가 나타납니다.

그림 3. JavaFX 이미지 검색 UI -- 첫 번째 시도


프레임의 컨텐츠는 BorderPanel 구성요소로, top 속성은 FlowPanel 구성요소에 지정되고
center 속성은 다른 BorderPanel 구성요소에 지정되며 bottom 속성은 Label 구성요소에 지정되었습니다.
FlowPanel 구성요소는 content 등록 정보를 갖습니다. 이 등록 정보에 하나 이상의 구성요소를 넣을 수 있습니다.
여러 개의 구성요소를 삽입할 때는 array를 정의하는 괄호 속에 구성요소를 넣어야 합니다.
다음과 같이 쉼표를 사용하여 컨텐츠 array에서 개별 구성요소를 구분합니다.

content: [
  Label {
     text: "Search"
     },
  TextField {
     columns: 50
  }
  ]
 

상단 FlowPanel에는 Label 구성요소와 TextField 구성요소가 중첩되어 있습니다.

FlowPanel 의 방향 속성은 HorizontalAlignment.LEFT입니다.

이는 FlowPanel 내의 구성요소를 가로와 왼쪽으로 정렬합니다.

가운데의 BorderPanel은 이 영역의 상단에 가로로 정렬된 레이블과 진행 표시줄을, 중앙에 목록을,

하단에 다른 레이블과 진행 표시줄을 레이아웃합니다. 프레임의 하단에는 레이블이 들어갑니다.

FlowPanel 구성요소는 레이블-구성요소 쌍을 만들기에 좋습니다.

여기에서는 여러 개의 FlowPanel을 사용하여 레이블과 TextField 등의 구성요소를 묶었습니다.

createProgressBar 함수는 JavaFX 스크립트를 사용하여 Swing 구성요소로부터

javafx.gui 구성요소를 생성하는 방법을 보여줍니다.

여기에서는 Swing JProgressBar 구성요소로부터 JavaFX 스트립트 진행 표시줄을 생성했습니다.

function createProgressBar(preferredSize:Integer[]) :Component {
          var jprogressbar = new javax.swing.JProgressBar();
          var comp = Component.fromJComponent(jprogressbar);
          comp.preferredSize = preferredSize;
          comp;
  }
 

첫 번째 시도에서 UI는 상당히 잘 작동하지만 원래 UI를 제대로 재현하지는 못합니다. 그래서 다른 접근 방법을 시도해 보겠습니다.


클러스터
 

원래 UI를 복제하려는 두 번째 시도에서는 JavaFX 스크립트 ClusterPanel 구성요소를 활용하겠습니다.

이 구성요소를 사용하여 프레임 내에서 구성요소를 클러스터할 수 있습니다.

JavaFX 스크립트에는 ClusterPanel 내에서 구성요소를 순서대로나

병렬로 정리하기 위해 사용할 수 있는 SequentialClusterParallelCluster도 포함됩니다.

진행 표시줄, 검색 텍스트 필드, 축소판 이미지 목록 및 선택된 이미지 표시 영역의 최대 너비 및 높이를 설정하기 위해

JavaFX 스크립트 Layout 구성요소도 활용하기로 결정했습니다.

Layout 구성요소는 UNLIMITED_SIZE 등의 레이아웃 관련 상수를 제공합니다.

UI의 두 번째 버전은 다음 JFXImageSearchUI.fx 파일에 코딩되었습니다.


   package com.sun.demo.jfx;

import javafx.ext.swing.Component;
import javafx.ext.swing.SwingFrame;
import javafx.ext.swing.Label;
import javafx.ext.swing.TextField;
import javafx.ext.swing.List;
import javafx.ext.swing.Cluster;
import javafx.ext.swing.Layout;
import javafx.ext.swing.ClusterPanel;
import javafx.ext.swing.SequentialCluster;
import javafx.ext.swing.ParallelCluster;
import javafx.scene.paint.Color;
import java.awt.Dimension;
import java.lang.System;

import javax.swing.border.LineBorder;

function createProgressBar() :Component {
var jprogressbar = new javax.swing.JProgressBar();
var comp = Component.fromJComponent(jprogressbar);
comp.hmax = Layout.UNLIMITED_SIZE;
comp;
}

var searchLabel = Label {
text: "Search:"

}
var searchTextField = TextField {
hmax: Layout.UNLIMITED_SIZE
columns: 50
}

var matchedImageLabel = Label {
text: "Matched Images"
}
var matchedImagePB = createProgressBar();


var thumbnailList = List {
preferredSize:[300, 230]
hmax: Layout.UNLIMITED_SIZE
vmax: Layout.UNLIMITED_SIZE
}

var selectedImageLabel = Label {
text: "Selected Image"
}
var selectedImagePB = createProgressBar();

var selectedImageDisplay = Label {
preferredSize: [400,300]
hmax: Layout.UNLIMITED_SIZE
vmax: Layout.UNLIMITED_SIZE
}

selectedImageDisplay.getJComponent().setOpaque(true);
selectedImageDisplay.getJComponent().setBackground(Color.WHITE.getAWTColor());
selectedImageDisplay.getJComponent().setBorder(new LineBorder(Color.BLACK.getAWTColor(), 1, true));

SwingFrame {
title: "JavaFX Image Search"
content:
// main panel within the frame
ClusterPanel {

hcluster: SequentialCluster {
content: [
ParallelCluster{ // mainCol
resizable: true
content: [
SequentialCluster{
content: [
ParallelCluster { //searchLabelCol
content: searchLabel
},
ParallelCluster { //searchTextFieldCol
resizable: true
content: searchTextField
}
]},
SequentialCluster {
content: [
ParallelCluster { //lblCol
content: matchedImageLabel
},
ParallelCluster { //progressBarCol
resizable: true
content: matchedImagePB
}
]},
thumbnailList,
SequentialCluster {
content: [
ParallelCluster { //lblCol
content: selectedImageLabel
},
ParallelCluster { //progressBarCol
resizable: true
content: selectedImagePB
}
]},
selectedImageDisplay
]
}
]

}

vcluster: SequentialCluster {
content: [
ParallelCluster{ //searchRow
content: SequentialCluster {
content: [
ParallelCluster { // row
content: [searchLabel, searchTextField]
}
]
}
},
ParallelCluster{ // matchedProgressRow
content: SequentialCluster {
content: [
ParallelCluster { // row
content: [matchedImageLabel, matchedImagePB]
}
]
}
},
ParallelCluster{ // thumbNailRow
resizable:true
content: thumbnailList
},
ParallelCluster{ // selectedProgressRow
content: SequentialCluster {
content: [
ParallelCluster { // row
content: [selectedImageLabel, selectedImagePB]
}
]
}
},
ParallelCluster{ // imageRow
content: selectedImageDisplay
}

]
}

}
visible: true
}


 
이 코드의 결과는 훨씬 보기 좋습니다.
그림 4에 나온 것처럼 구성요소는 이제 프레임의 왼쪽과 오른쪽으로 완벽하게 정렬되었습니다.
 

그림 4. JavaFX 이미지 검색 UI -- 두 번째 시도


 

프레임의 주 컨텐츠는 ClusterPanel 구성요소입니다.
ClusterPanel 내에서 SequentialCluster 구성요소 그룹의 구성요소는 모두 순서대로 있습니다.
ParallelCluster 구성요소 그룹의 구성요소는 병렬로 있습니다.
예를 들어 다음은 구성요소를 프레임의 상단 영역에 정리합니다. 검색 레이블과 검색 텍스트 필드를 병렬 열로 클러스터합니다.


 
 hcluster: SequentialCluster {
      content: [
          ParallelCluster{ // mainCol
              resizable: true
              content: [
                  SequentialCluster{
                      content: [
                          ParallelCluster {  //searchLabelCol
                              content: searchLabel
                          },
                          ParallelCluster { //searchTextFieldCol
                              resizable: true
                              content: searchTextField
                          }
              ]},
 

SequentialClusterSequentialClusterParallelCluster와 같은 ClusterElement 구성요소를 지정할 수 있는

content 속성을 갖습니다. ParallelCluster 구성요소는 크기 조정이 가능하므로 필요에 따라 프레임 너비를 채웁니다.

인터페이스의 나머지도 같은 패턴을 따릅니다. 인터페이스의 각 5개 영역 내에서 구성요소는 SequentialCluster 구성요소 내에서 ParallelCluster 구성요소를 사용하여 그룹화 및 정리됩니다.

예를 들어 다음 코드는 매칭되는 이미지 레이블과 매칭되는 이미지 진행 표시줄을 병렬 열로 클러스터합니다.

클러스터 다음에는 검색과 매칭되는 이미지의 축소판 목록이 나옵니다.


 
SequentialCluster {
      content: [
          ParallelCluster {  //lblCol
              content: matchedImageLabel
          },
          ParallelCluster { //progressBarCol
              resizable: true
              content: matchedImagePB
          }
      ]},
  thumbnailList,
 

한 가지 주목할 점은 javafx.ext.swing 패키지의 Label 구성요소가 테두리 특성, 불투명도 또는 배경 색상의 설정을 위한 속성을

현재 지원하지 않는다는 것입니다. 따라서 이들 설정을 위해 getJComponent 함수에 대한 Label 구성요소의 지원을 활용했습니다.

함수는 JavaFX 스크립트 구성요소로 캡슐화된 Swing jComponent를 반환합니다.

이 경우에는 Swing Label 구성요소를 반환합니다. 다음 코드를 사용하여 필요한 레이블 속성을 정의할 수 있습니다.


var selectedImageDisplay = Label {
         ...
  }

  selectedImageDisplay.getJComponent().setOpaque(true);
  selectedImageDisplay.getJComponent().setBackground(Color.WHITE.getAWTColor());
  selectedImageDisplay.getJComponent().setBorder(new LineBorder(Color.BLACK.getAWTColor(), 1, true));
 
설계 도구 없이 이미지 검색 사용자 인터페이스를 복제하는 것에 대해 처음에 걱정했던 것과 달리 가장 유용하고 필요한 기능은 
빠르고 쉽게 액세스가 가능합니다. 또한 NetBeans IDE용 JavaFX 플러그인은 구성요소를 사용하여 작업할 때 사용 가능한
속성을 띄우는 문맥 인식 코드 완성을 제공합니다. 코드 완성을 사용하면 UI의 구현이 처음 생각처럼 어렵지만은 않습니다.
그림 5는 IDE에서 Ctrl+spacebar를 입력할 때 활성화되는 일부 팝업 옵션을 보여줍니다.


그림 5. 옵션


요약
 

UI 생성을 위한 선언적 구문을 살펴보기 위해 기존 애플리케이션의 UI를 이식했습니다.

원래 애플리케이션에서는 구성요소의 배치 및 정렬에 Swing의 GroupLayout을 사용했습니다.

NetBeans IDE 6.1은 아직 JavaFX 스크립트를 위한 그래픽 설계 도구를 지원하지 않지만

수동으로 인터페이스를 레이아웃하는 것은 처음 걱정했던것 만큼 어렵지 않았습니다.

ClusterPanel, SequentialClusterParallelCluster 구성요소의 조합을 통해

JavaFX 스크립트 구현은 원래 UI와 사실상 똑같아 보입니다.

이들 조합에 NetBeans IDE용 JavaFX 플러그인 및 문맥 인식 코드 완성이 더해져 작업은 더욱 쉬워졌습니다.


자세한 정보


이 글의 영문 원본은 # Learning Curve Journal, Part 2: Declarative User Interfaces에서 보실 수 있습니다.
 
2007년 8월과 9월에 썬 개발자 네트워크의 John O'Conner는 JavaFX 스크립트 프로그래밍 언어(이 기사에서는 JavaFX 스크립트라고 줄여서 부름)를 시작하는 사용자에게 도움을 주고자 "학습 곡선 일지(Learning Curve Journal)"라는 제목의 시리즈를 기고했습니다.

그 이후로 이 언어의 많은 중요한 부분이 개선되었습니다. 아마도 가장 중요한 변화는 JavaFX 스크립트의 초기 인터프리터 기반 버전을 대신하여 컴파일러 기반 버전을 사용할 수 있게 되었다는 점입니다. 이전의 학습 곡선 일지에서는 인터프리터 기반 버전 사용에 대해 설명했습니다. 업데이트된 학습 곡선 일지에서는 컴파일러 기반 버전의 언어 사용법을 보여줍니다. 최신 내용을 반영하여 다른 변경 사항도 적용되었습니다.

이 전과 마찬가지로 시리즈의 1편은 JavaFX 프로그램, 즉 JavaFX 스크립트 언어로 쓰여진 간단한 프로그램으로 시작합니다. JavaFX 스크립트에서의 프로그래밍을 위한 환경 설정 방법과 JavaFX 프로그램 빌드 및 실행 방법을 배우게 됩니다. 2편은 JavaFX 스크립트에서 사용 가능한 선언적 코딩 스타일에 중점을 둡니다. 이러한 스타일이 어떻게 그래픽 애플리케이션을 더욱 단순하고 직관적으로 만들 수 있는지 볼 수 있을 것입니다. 3편에서는 JavaFX 프로그램에서 작업 구현을 위한 JavaFX 스크립트 함수의 사용 방법을 보여줍니다. 4편에서는 웹 서비스 액세스를 위한 JavaFX 스크립트 사용 방법을 보여줍니다. 그 과정 중에 FX 스크립트에서 Swing 클래스와 같은 자바 기술 슬래스의 액세스가 얼마나 쉬운지도 보여줍니다.

JavaFX 스크립트는 개발자가 동적인 그래픽 컨텐츠 생성에 사용할 수 있는 새로운 스트립팅 언어입니다. 데스크탑에서 언어는 Swing 사용자 인터페이스(UI) 툴킷과 자바 2D API를 편리하게 사용할 수 있는 라이브러리를 제공합니다. Swing 또는 자바 2D를 대체하는 것이 아니고 풍부한 컨텐츠 개발자가 이들 API에 더욱 쉽게 액세스할 수 있도록 하는 것이 목적입니다. 모바일 시스템과 같은 다른 환경에서 JavaFX 스크립트는 Swing 이외의 사용자 인터페이스 기술을 사용합니다. JavaFX 스크립트를 사용하여 여러 가지 플랫폼과 운영 환경에서 실행되는 시각적으로 풍부한 애플리케이션을 만들 수 있습니다.

언어는 선언적 및 절차적 구문을 모두 제공합니다. 선언적으로 풍부한 사용자 인터페이스를 만든 다음 이벤트 처리 루틴과 작업을 추가할 수 있습니다.

그러나 대부분의 사용자는 좀 더 소박하게 시작해야 하며 이 기사의 목적도 그러합니다. 목표는 JavaFX 스크립트를 시작하는 방법을 보여주는 것입니다. 먼저 다음이 필요합니다.



자바 플랫폼 설정

개발자라면 물론 시스템에 JDK가 설치되어 있을 것입니다. 그러나 시스템을 한동안 업데이트하지 않은 경우에는 자바 SE 6이 설치되어 있는지 확인하십시오. 학습 곡선 일지는 JavaFX 스크립트의 컴파일러 기반 버전과 NetBeans IDE 6.1에서의 지원에 중점을 두고 있습니다. JavaFX 기술이 적용된 NetBeans IDE 6.1을 설치 및 사용하려면 시스템에 자바 SE 6의 최신 수준(현재는 자바 SE 6 업데이트 10 베타)을 설치하는 것이 좋습니다. 썬 개발자 네트워크의 자바 SE 다운로드 페이지에서 최신 JDK를 다운로드합니다. Mac OS X를 사용하는 경우에는 Apple Developer Connection의 자바 섹션에서 직접 Apple의 최신 자바 플랫폼 개발 키트(현재는 Mac OS X 10.5, 업데이트 1용 자바)를 다운로드 받을 수 있습니다.


자료 참조

새로운 환경이나 언어를 경험할 때는 교착 상태에 빠지거나 난관에 봉착하게 됩니다. 이는 최첨단 기술을 사용할 때 모두가 겪게 되는 과정입니다. 학습 곡선을 원만하게 하기 위해서는 훌륭한 문서와 예제가 매우 중요합니다. 썬 개발자 네트워크의 JavaFX 기술 허브와 함께 javafx.comProject OpenJFX 웹 사이트는 정확한 정보를 얻을 수 있는 최신 문서와 데모 자료를 제공합니다.

일 부 사용자들은 언어 참조 자료를 읽지도 않고 프로그래밍을 바로 시작하고자 할 수 있습니다. 또다른 사용자들은 JavaFX 스크립트를 실제로 사용하기 전에 모든 자료를 읽을 것입니다. 바로 시작하는 유형의 사용자더라도 일종의 언어 사양이나 자습서부터 시작해야 합니다. 전형적인 "Hello, world" 예제를 쓰기 전에 기본 언어 구문을 알아야 합니다. JavaFX 참조 페이지의 문서부터 시작하는 것이 좋습니다. 여기에서 JavaFX 스크립트 프로그래밍 언어 참조 자료 등의 참조 문서와 JavaFX 기술 시작하기NetBeans IDE를 사용하여 간단한 JavaFX 애플리케이션 생성 등의 많은 기사와 자습서로의 링크를 찾을 수 있습니다.


JavaFX 애플리케이션 생성

일부 언어 참조 문서를 읽었다면 이제는 간단한 JavaFX 애플리케이션을 만들어 볼 차례입니다. 명령줄에서 수동으로 JavaFX 애플리케이션 빌드 및 실행이 가능하긴 하지만 애플리케이션 개발을 단순화하는 많은 기능을 가진 NetBeans IDE 6.1를 사용해 봅시다. NetBeans용 JavaFX 플러그인을 설치해야 합니다.

NetBeans IDE 6.1을 설치하지 않은 경우에는 NetBeans IDE 6.1과 NetBeans용 JavaFX을 포함하는 하나의 패키지인 JavaFX 포함 NetBeans IDE 6.1 다운로드가 가능합니다. NetBeans IDE 6.1을 이미 설치한 경우에는 NetBeans 업데이트 센터에서 JavaFX 플러그인을 설치하여 JavaFX 기술 지원을 추가할 수 있습니다. NetBeans용 JavaFX는 현재 Windows 및 Mac OS/X 환경에서 사용 가능합니다. JavaFX 플러그인을 설치하면 NetBeans IDE 6.1을 사용하여 JavaFX 스크립트의 컴파일러 기반 버전으로 작성된 애플리케이션을 생성, 테스트, 디버그 및 배포할 수 있습니다. 플러그인은 JavaFX 스크립트 파일 포함을 위한 프로젝트 및 편집기 지원을 향상시킵니다. 또한 스크립트 엔진 및 라이브러리를 위한 코어 라이브러리도 제공합니다.

JavaFX 포함 NetBeans IDE 6.1이나 NetBeans용 JavaFX 플러그인을 설치했으면 첫 번째 JavaFX 애플리케이션을 빌드할 준비가 된 것입니다. 물론 "Hello, world!"부터 시작해야겠지요.

다음과 같이 프로젝트 생성을 시작합니다.

  1. 주 메뉴에서 File -> New Project를 선택합니다.
  2. New Project 마법사에서 JavaFX 범주와 JavaFX Script Application 프로젝트 유형을 선택합니다.
  3. Next 버튼을 클릭합니다.
  4. HelloWorldJFX와 같이 프로젝트의 이름을 지정합니다.
  5. 기본 프로젝트 위치를 수락하거나 다른 위치를 선택하여 이동합니다.
  6. Create Main Class 확인란을 선택된 상태로 두고 다른 기본 설정도 변경하지 않습니다.
  7. Finish 버튼을 클릭합니다.

그림 1과 같이 IDE는 지정된 프로젝트 폴더에 프로젝트 디렉토리를 생성하고 프로젝트 이름과 같은 HelloWorldJFX라는 이름을 부여합니다. HelloWorldJFX 프로젝트를 확장합니다. Source Packages 노드의 helloworldjfx 패키지 아래에 Main.fx 클래스 파일이 있습니다. IDE는 프로젝트 생성 시 Create Main Class 확인란이 선택되어 있었으므로 Main.fx 파일을 생성합니다. 이 파일에 애플리케이션의 소스 코드가 들어갑니다.


그림 1.
HelloWorldJFX 프로젝트 파일


  /*
   * Main.fx
   *
   * Created on ...
   */

  package helloworldjfx;

  /**
   * @author ...
   */

  // place your code here
 

// place your code here 라인을 다음 코드로 바꿉니다.

  import javafx.ext.swing.Label;

  Label {
      text: "Hello, world!"
  }
 


JavaFXScript 편집기는 기본 서식 설정과 코드 완성을 제공합니다. 우리와 같이 JavaFX 스크립트에 익숙하지 않은 프로그래머는 언어 구문에 확신이 들지 않을 때도 있으므로 코드 완성이 도움이 됩니다. Ctrl + Space 키를 누르면 편집기에서 코드 완성이 활성화됩니다.

또 한 JavaFX 스크립트 플러그인은 컴파일과 실행을 해 볼 필요 없이 애플리케이션의 결과를 볼 수 있는 미리보기 기능을 제공합니다. 소스 파일에 변경한 내용은 즉시 미리보기 창에 반영됩니다. 미리보기 기능은 현재 Mac OS X 플랫폼에서는 사용할 수 없습니다.

Enable Preview 버튼

을 클릭하여 미리보기 기능을 작동시킵니다. 그림 2와 같이 편집기 바로 위에서 출력을 볼 수 있습니다.



그림 2. 기본적인 "Hello, world!"

별로 놀라지 않으셨나요? 좋습니다. 설정 방법을 보여주려는 것 뿐이었지만 좀 더 재미있는 것을 해보도록 하겠습니다. JavaFX 환경은 모든 Swing UI 구성요소를 구현하므로 레이블에만 한정될 필요는 없습니다. 버튼이나 대화 상자 같은 다른 위젯을 사용할 수도 있습니다.

다음은 버튼의 이벤트 핸들러를 소개하는 예제입니다. 언어 참조 자료에서 actionfunction 구문을 읽었으면 버튼을 누를 때 메시지 상자가 표시되도록 해봅시다.


  import javafx.ext.swing.SwingFrame;
   
import javafx.ext.swing.Button;
   
import javafx.ext.swing.SwingDialog;
   
import javafx.ext.swing.Label;
 
   
SwingFrame {
       content
: Button {
           text
: "Press me!"
           action
: function() {
               
SwingDialog {
                   title
: "You pressed me"
                   content
: Label{ text: "Hey, don't do that!"}
                   visible
: true
               
}
       
}
   
}
 
   visible
: true
   
}



Main.fx 파일에 이를 입력한 후에 Press me! 버튼을 누르면 그림 3과 같은 결과가 나타납니다.

그림 3. "Press me!" 버튼 메시지


앞에서 말한 것처럼 미리보기 기능을 사용하여 코드를 컴파일 및 실행하지 않고도 애플리케이션의 결과를 볼 수 있습니다. 코드를 컴파일하려는 경우에는 Project 창에서 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 Build Project를 선택합니다. 애플리케이션을 실행하려면 Project 창에서 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 Run Project를 선택합니다.


구성요소 기반에서 노드 기반 UI로의 이동

이전 예제에서 Frame과 Dialog의 구성요소가 간단하게 FrameDialog가 아니라 SwingFrameSwingDialog라 는 클래스 이름을 갖는지 궁금하셨을지도 모릅니다. 그 답은 JavaFX 스크립트 개발자가 Swing 기반 구성요소의 계층 구조를 사용하는 기존 접근 방법 대신 노드 기반 접근 방법을 사용하여 UI를 구축하는 향후의 접근 방법에 관련되어 있습니다. 사실 이러한 향후의 접근 방법에 대한 초기 지원은 이미 JavaFX 라이브러리에 포함되어 있습니다. 예를 들면 javafx.application 패키지에는 노드 기반 접근 방법을 지원하는 Frame, DialogWindow 등의 클래스가 포함됩니다. 다른 접근 방법을 지원하는 클래스와의 혼동을 피하기 위해 javafx.application 패키지에 해당 항목이 있는 javafx.ext.swing 패키지는 클래스 이름에 접두어 "Swing"을 추가했습니다.

학습 곡선 시리즈에서는 계층 구조적인 Swing 기반 접근 방법을 사용했지만 javafx.ext.swing 패키지의 SwingFrame, SwingDialogSwingWindow와 같은 클래스는 임시적입니다. JavaFX 팀은 Swing 구성요소와 유사하지만 더욱 유연하고 강력한 새로운 노드 기반 구성요소 집합을 설계 중입니다.

노드 기반 접근 방법을 사용한 UI 구축에 대한 자세한 내용은 장면 그래프를 사용하여 JavaFX 스크립트에서 비주얼 개체 표시 기사를 참조하십시오. NetBeans IDE를 사용하여 간단한 JavaFX 애플리케이션 생성 자습서도 노드 기반 접근 방법을 사용하는 JavaFX 애플리케이션을 설명합니다.


프로파일

JavaFX는 특정 플랫폼이나 장치에서만 사용 가능한 클래스 그룹을 의미하는 프로파일을 지원합니다. javafx.ext.swing 패키지의 클래스는 데스크탑 프로파일에 들어 있으며 데스크탑 환경에서만 가용성이 보장됩니다. 예를 들어 많은 휴대 전화는 Swing 클래스를 갖지 않습니다. 휴대 전화와 TV를 포함한 모든 플랫폼에서 보장되는 클래스를 정의한 일반 프로파일도 있습니다. 새로운 노드 기반 구성요소는 일반 프로파일에 있으므로 모든 화면 및 장치에서 작동합니다.

JavaFX API 문서는 정확한 프로파일 사용을 보장하기 위해 하나의 프로파일에서 다른 프로파일로 전환할 수 있도록 하는 버튼을 제공합니다. 이 기사 시리즈에서는 데스크탑 환경을 위한 애플리케이션을 구축하므로 데스크탑 프로파일의 클래스와 일반 프로파일의 일부 클래스를 사용할 것입니다.


명령줄에서 JavaFX 애플리케이션 빌드 및 실행

다음과 같이 명령줄에서 JavaFX 애플리케이션을 빌드 및 실행할 수 있습니다.

  1. JavaFX Preview SDK 다운로드를 받습니다. SDK에는 JavaFX 스크립트 컴파일러, 문서, 런타임, 라이브러리 및 코드 샘플이 포함됩니다.
  2. 다운로드한 패키지를 확장합니다. 여러 디렉토리 중에 javafxcjavafx 명령을 위한 실행 가능 파일을 포함하는 bin 디렉토리가 보여야 합니다.
  3. fx 확장자를 갖는 파일(예: MyApp.fx)에 애플리케이션의 소스 코드를 저장합니다
  4. 다음과 같이 javafxc 명령을 수행하고 소스 파일을 지정하여 애플리케이션을 컴파일합니다.
       javafxc MyApp.fx

    애플리케이션을 위한 클래스 파일이 생성됩니다.

  5. 다음과 같이 javafx 명령을 수행하고 클래스 파일을 지정하여 애플리케이션을 위한 클래스 파일을 실행합니다.
       javafx MyApp

요약

새로운 기술을 조사할 때는 올바르게 시작하는 것이 중요합니다. 정확한 정보와 도구를 사용하여 시작하도록 하십시오. JavaFX 스크립트에 대한 최선의 방법은 4단계 절차를 따르는 것입니다.

  1. 최신의 Java SE Development Kit를 받습니다.
  2. 정보의 출처로 JavaFX 기술 허브, javafx.com 사이트Project OpenJFX 사이트를 사용합니다.
  3. IDE용 개발 플러그인을 받습니다. JavaFX 포함 NetBeans IDE 6.1은 새로운 JavaFX 스크립트의 컴파일러 기반 버전을 지원합니다. NetBeans IDE 6.1을 이미 설치한 경우에는 NetBeans 업데이트 센터에서 JavaFX 플러그인을 설치하여 JavaFX 기술 지원을 추가할 수 있습니다.
  4. 첫 번째 스크립트 시험에 미리보기 기능을 사용합니다.


자세한 정보

이 시리즈의 2편인 선언적 사용자 인터페이스를 참조하십시오.



이 글의 영문 원본은
Learning Curve Journal, Part 1: Exploring JavaFX Script
에서 보실 수 있습니다.

 

SAML을 이용한 SSO Service의 구현

개발 이야기/Java | 2008. 4. 3. 14:39
Posted by 시반

1. 개요

SAML은 웹 브라우저에서의 SSO문제를 해결하기 위해서 OASIS의 연구결과로 탄생하였다.
인트라넷 레벨에서의 SSO는 다양한 방식들이 이용되어왔고 구현에 있어서도 크게 문제될요소는 적다.
예를 들어 Cookie 기반의 SSO, LDAP기반의 SSO, 인증서 기반의 SSO등이 있다.

그러나 도메인간의 SSO 구현을 위한 방식은 통제할 수 없는 외부 환경을 포함하므로 통일된 하나의 표준방식이 필요하게 되었고
SAML은 이러한 도메인간의 SSO구현을 가능하게하는 XML 표준이다.

사용되는 용어에 대해서 알아보자.
SAML : Security Asserting Markup Language, http://en.wikipedia.org/wiki/SAML 참조
SSO : Single Sign On 하나의 일관된 인증방식으로 여러 서비스에 로그온할 수 있는 방법
ACS : Assertion Consumer Service, Identity Provider에 의하여 인증된 사용자에 대한 정보가 담긴 SAML response정보를 verify 하고 서비스를 제공할 수 있도록 포워딩 한다

SAML에는 3가지 중요한 구성원이 존재한다.
  • Service Provider : 서비스를 제공하는 주체
  • 유저 : 서비스를 이용하는 사용자
  • Identify Provider : 유저에 대한 인증을 담당하는 주체

SAML의 특징은 Cross domain상황에서 표준화된 방식으로 SSO를 구현할 수 있으면서
Platform에 관계없이 다양한 환경에서 표준적인 방법으로 SSO 구현이 가능하다는 것이다.
실제 다양한 iPhone등 다양한 플랫폼에서 테스트해본 결과 잘 동작한다.


2. SAML Basic Steps

아래 그림은 Wikipedia에서 제시한 흐름도이다.


Google에서도 아래 그림을 제시하고 있다.


각 단계별 과정을 설명하면 아래와 같다.
  • 1단계 : 유저는 서비스 제공자(Service Provider)에게 접근한다. (서비스 이용을 위하여)
  • 2단계 : 서비스 제공자는 SAMLRequest를 생성한다. 생성된 SAMLRequest은 XML format의 텍스트로 구성된다.
  • 3단계 : 유저의 브라우저를 이용하여 인증 제공자(Identity Provider)로 Redirect 한다.
  • 4단계 : 인증 제공자(Identity Provider)는 SAMLRequest를 파싱하고 유저인증을 진행한다.
  • 5단계 : 인증제공자(Identity Provider)는 SAML Response를 생성한다.
  • 6단계 : 인증제공자(Identity Provider)는 유저 브라우저를 이용하여 SAMLResponse data를 ACS로 전달한다.
  • 7단계 : ACS는 Service Provider가 운영하게 되는데 SAMLResponse를 확인하고 실제 서비스 사이트로 유저를 Forwarding한다.
  • 8단계 : 유저는 로그인에 성공하고 서비스를 제공받는다.

위 단계중 SAMLResponse가 중요한 역할을 하는데 Identity Provider로서 SAMLResponse에 대하여 전자서명하고 ACS가 전자서명을 검증하여 유효한 Response인지를 확신할 수 있게 된다.

Identity Provider는 자체적인 다양한 방식으로 유저인증을 진행할 수 있으며 서비스 제공자는 Identity Provider를 신뢰하여 인증의 전권을 Identity Provider에 의존하게 되어 Identity Provider의 신뢰 및 책임부분이 중요한 요소이다.


3. SAML SSO 실제 구현

SAML SSO를 실제 구현해가면서 어떻게 동작하는지 살펴보자.


3.1 사전 준비

구현은 Service Provider로서의 단계와 Identity Provider로서의 단계를 모두 포함하여 Full Cycle을 순환할 수 있도록 하지만
실제로는 대부분 Service Provider나 Identity Provider의 역할중 어느 한 역할을 주로 하게 될것이다.


필요한 핵심 Library는 아래와 같다.


OpenSAML 2.0 Library

기타 apache commons의 유용한 Library들을 필요에 맞게 사용한다.


RSA Keypair를 준비한다.

openssl을 이용한 keypair 생성 command
$ openssl genrsa -out sso_private.pem 1024
$ openssl rsa -in sso_private.pem -pubout -ourform DER -out sso_public.der
$ openssl pkcs8 -topk8 -inform PEM -outform DER -in sso_private.pem -out sso_private.der -nocrypt

생성된 2개의 파일은 sso_private.der, sso_public.der이다.

한가지 주의할점은 sso_public.der은 우리가 통상적으로 알고 있는 인증서(certificate)가 아니다.
certifificate는 인증기관으로부터 public key의 유효성을 인정받은 경우에 인증기관의 전자서명이 첨부되어 발행되는데
여기서는 original rsa public key만으로 SSO를 구현한다.

대신 신뢰성을 확보하기 위하여 identity provider의 public key는 신뢰성 있는 방식으로 service provider에게 전달되어야하며
이 전달과정이나 identity provider의 private key 관리에는 주의를 기울여야한다.

SAML에서 이용하는 xmldsig spec이나 구현 사례를 봐도 인증서를 탑재하여 SAML SSO를 분명히 진행할 수 있다.
이 경우에는 identity provider의 public key를 특별한 방법으로 전달할 필요 없이
service provider는 신뢰할 수 있는 CA(Certificate Authority)가 보장하는 전자인증서를 확보하고 인증서 CRL 또는 OCSP checking등을 추가할 수 있을것이다.


3.2 SAMLRequest 생성 및 redirect

이 단계에서는 유저가 서비스 제공자의 사이트에 접속하고 SAML SSO 로그인을 진행하기 위해 SAMLRequest 생성 및 redirect의 과정을 설명한다.


3.2.1 ServiceProviderForm : 실제 유저가 최초로 접근하는 페이지이다. 여기서부터 시작

<form name="ServiceProviderForm" action="https://api.paygate.net/t/sso/saml/CreateRequestServlet.jsp" method="post">

  <input type="hidden" name="loginForm" value="https://api.paygate.net/t/sso/saml/login_form.jsp" />

  <input type="hideen" name="providerName" value="paygate.net" />

  <input type="hidden" name="RelayState" value="https://api.paygate.net/t/sso/saml/result_view.jsp" />

  <input type="hidden" name="acsURI" value="https://api.paygate.net/t/sso/saml/ACS.jsp" />

  <input type="submit" value="Sign On">

</form>

   * loginForm : Identity Provider에 위치하는 인증을 위한 로그인 폼
   * providerName : 서비스를 제공하는 Service Provider Name
   * RelayState : ACS에서 인증을 마친후 최종적으로 Redirecting하게 되는 서비스 제공 페이지 URL
   * acsURI : Identity Provider가 보낸 SAMLResponse를 검증(Verify)하고 실제 서비스 제공사이트로 Forwarding하게 되는 URL


3.2.2 CreateRequestServlet : SAMLRequst 데이터 생성 단계

  • 먼저 Parameter를 받고
    String ssoURL = request.getParameter("loginForm");
    String providerName = request.getParameter("providerName");
    String RelayState = request.getParameter("RelayState");
    String acsURI = request.getParameter("acsURI");

   public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String ssoURL = request.getParameter("loginForm");
        String providerName = request.getParameter("providerName");
        String RelayState = request.getParameter("RelayState");
        String acsURI = request.getParameter("acsURI");
        .....
    }

  • SAMLRequest xml data를 생성
    String SAMLRequest = createAuthnRequest(acsURI, providerName);
    private String createAuthnRequest(String acsURL, String providerName)
            throws SamlException {
        String filepath = getServletContext().getRealPath("t/sso/saml/templates/" + SAML_REQUEST_TEMPLATE);
        String authnRequest = Util.readFileContents(filepath);
        authnRequest = StringUtils.replace(authnRequest, "##PROVIDER_NAME##", providerName);
        authnRequest = StringUtils.replace(authnRequest, "##ACS_URL##", acsURL);
        authnRequest = StringUtils.replace(authnRequest, "##AUTHN_ID##", Util.createID());
        authnRequest = StringUtils.replace(authnRequest, "##ISSUE_INSTANT##", Util.getDateAndTime());
        return authnRequest;
    }

  • Identity Provider로 redirect할 URL생성
    String redirectURL = computeURL(ssoURL, SAMLRequest, RelayState);
    private String computeURL(String ssoURL, String authnRequest,
            String RelayState) throws SamlException {
        StringBuffer buf = new StringBuffer();
        try {
            buf.append(ssoURL);

            buf.append("?SAMLRequest=");
            buf.append(RequestUtil.encodeMessage(authnRequest));

            buf.append("&RelayState=");
            buf.append(URLEncoder.encode(RelayState));
            return buf.toString();
        } catch (UnsupportedEncodingException e) {
            throw new SamlException(
                    "Error encoding SAML Request into URL: Check encoding scheme - "
                            + e.getMessage());
        } catch (IOException e) {
            throw new SamlException(
                    "Error encoding SAML Request into URL: Check encoding scheme - "
                            + e.getMessage());
        }
    }

  • CreateRequestServlet.java 구현 종합
/*
 * CreateRequestServlet
 */
public class CreateRequestServlet extends HttpServlet {

    private static final String SAML_REQUEST_TEMPLATE = "AuthnRequestTemplate.xml";

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String returnPage = "service_proc.jsp"; // 실제 forwarding수행할 JSP
        response.setHeader("Content-Type", "text/html; charset=UTF-8");
        response.setContentType("text/html; charset=UTF-8");
       
        // get PARAMETERS
        String ssoURL = request.getParameter("loginForm");
        String providerName = request.getParameter("providerName");
        String RelayState = request.getParameter("RelayState");
        String acsURI = request.getParameter("acsURI");

        String SAMLRequest;
        String redirectURL;

        try {
            // create SAMLRequest
            SAMLRequest = createAuthnRequest(acsURI, providerName);
            request.setAttribute("authnRequest", SAMLRequest);

            // compute URL to forward AuthnRequest to the Identity Provider
            redirectURL = computeURL(ssoURL, SAMLRequest, RelayState);
            request.setAttribute("redirectURL", redirectURL);

        } catch (SamlException e) {
            request.setAttribute("error", e.getMessage());
        }

        request.getRequestDispatcher(returnPage).include(request, response);
    }
}

  • AuthnRequestTemplate.xml 참조
<?xml version="1.0" encoding="UTF-8"?>
<samlp:AuthnRequest
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    ID="##AUTHN_ID##"
    Version="2.0"
    IssueInstant="##ISSUE_INSTANT##"
    ProtocolBinding="urn:oasis:names.tc:SAML:2.0:bindings:HTTP-Redirect"
    ProviderName="##PROVIDER_NAME##"
    AssertionConsumerServiceURL="##ACS_URL##"/>


3.2.3 service_proc.jsp : Identity Provider로 SAMLRequest를 forward

<%@page import="net.paygate.saml.util.RequestUtil"%>
<%@page import="java.net.*"%>
<%
      String error = (String) request.getAttribute("error");
      String authnRequest = (String) request.getAttribute("authnRequest");
      String redirectURL = (String) request.getAttribute("redirectURL");
%>
<html><head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"><title>SAML-based Single Sign-On Service </title></head>
<%
    if (error != null) {
%>
        <body><center><font color="red"><b><%= error %></b></font></center><p>
<%
    } else {
        if (authnRequest != null && redirectURL != null) {       
%>
        <body onload="document.location = '<%=redirectURL%>';return true;">
          <h1 style="margin-bottom:6px">Submitting login request to Identity provider</h1>
     <%
       } else {
       %>
       <body>
          <center><font color="red"><b>no SAMLRequest or redirectURL</b></font></center><p>
          <%
       }
     }
     %>
</body></html>


3.3 SAMLRequest를 받고 유저인증 진행 단계

이 단계는 Identity Provider에서 진행하게 된다.
유저인증은 각 Identity Provider별로 다양한 방법을 취할 수 있고 여기서는 LDAP 인증을 사용하고 있다.


3.3.1 login_form.jsp : 기본적인 유저 인증 정보를 입력받는다.

  • Parameter를 받고
    String SAMLRequest = request.getParameter("SAMLRequest");
    String RelayState = request.getParameter("RelayState");

  • SAMLRequest를 Parsing한다.
    String requestXmlString = ProcessResponseServlet.decodeAuthnRequestXML(SAMLRequest);
    String[] samlRequestAttributes = ProcessResponseServlet.getRequestAttributes(requestXmlString);
       
  • 사용자 이름과 비밀번호를 입력받고
    <input type="text" name="username" id="username" size="18">
    <input type="password" name="password" id="password" size="18">

  • IdentityProviderForm을 생성한다.
    <form name="IdentityProviderForm" action="....." method="post">
    ....
    </form>

  • login_form.jsp 구현 종합
<%
    String SAMLRequest = request.getParameter("SAMLRequest");
    String RelayState = request.getParameter("RelayState");
    String ServiceProvider = "";
    if (SAMLRequest == null || SAMLRequest.equals("null")) {
        ServiceProvider = "";
    } else {
        String requestXmlString = ProcessResponseServlet.decodeAuthnRequestXML(SAMLRequest);
        String[] samlRequestAttributes = ProcessResponseServlet.getRequestAttributes(requestXmlString);
        String issueInstant = samlRequestAttributes[0];
        ServiceProvider = samlRequestAttributes[1];
        String acsURL = samlRequestAttributes[2];
    }
%>

<html><head><title>SSO Login Page</title></head>
<body>
<h1><%=ServiceProvider%> Service Login</h1>
<form name="IdentityProviderForm" action="https://api.paygate.net/t/sso/saml/ProcessResponseServlet.jsp" method="post">
      <input type="hidden" name="SAMLRequest" value="<%=SAMLRequest%>"/>
      <input type="hidden" name="RelayState" value="<%=RelayState%>"/>
      <input type="hidden" name="returnPage" value="./login_proc.jsp">
username : <input type="text" name="username" id="username" size="18">
<br>
password :
<input type="password" name="password" id="password" size="18"><br>
<input type="submit" value="로그인">
</form>
</body></html>


3.4 유저 확인후 SAMLResponse 생성


3.4.1 ProcessResponseServlet

  • login_form.jsp에서 parameter를 받는다.
    String SAMLRequest = request.getParameter("SAMLRequest");
    String returnPage = request.getParameter("returnPage");
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    String RelayState = request.getParameter("RelayState");

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String SAMLRequest = request.getParameter("SAMLRequest");
        String returnPage = request.getParameter("returnPage");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String RelayState = request.getParameter("RelayState");
        ...
    }

  • SAMLRequest를 parsing한다.
    String requestXmlString = decodeAuthnRequestXML(SAMLRequest);

    public static String decodeAuthnRequestXML(String encodedRequestXmlString) throws SamlException {
        try {
            Base64 base64Decoder = new Base64();
            byte[] xmlBytes = encodedRequestXmlString.getBytes("UTF-8");
            byte[] base64DecodedByteArray = base64Decoder.decode(xmlBytes);
            try {

                Inflater inflater = new Inflater(true);
                inflater.setInput(base64DecodedByteArray);
                byte[] xmlMessageBytes = new byte[5000];
                int resultLength = inflater.inflate(xmlMessageBytes);

                if (!inflater.finished()) {
                    throw new RuntimeException("didn't allocate enough space to hold decompressed data");
                }

                inflater.end();
                return new String(xmlMessageBytes, 0, resultLength, "UTF-8");

            } catch (DataFormatException e) {

                ByteArrayInputStream bais = new ByteArrayInputStream(base64DecodedByteArray);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                InflaterInputStream iis = new InflaterInputStream(bais);
                byte[] buf = new byte[1024];
                int count = iis.read(buf);
                while (count != -1) {
                    baos.write(buf, 0, count);
                    count = iis.read(buf);
                }
                iis.close();

                return new String(baos.toByteArray());
            }

        } catch (UnsupportedEncodingException e) {
            throw new SamlException("Error decoding AuthnRequest: Check decoding scheme - " + e.getMessage());
        } catch (IOException e) {
            throw new SamlException("Error decoding AuthnRequest: Check decoding scheme - " + e.getMessage());
        }
    }


  • SAMLRequest에서 Attribute을 발췌한다.
    String[] samlRequestAttributes = getRequestAttributes(requestXmlString);
    String issueInstant = samlRequestAttributes[0];
    String providerName = samlRequestAttributes[1];
    String acsURL = samlRequestAttributes[2];

    public static String[] getRequestAttributes(String xmlString) throws SamlException {
        Document doc = Util.createJdomDoc(xmlString);
        if (doc != null) {
            String[] samlRequestAttributes = new String[3];
            samlRequestAttributes[0] = doc.getRootElement().getAttributeValue("IssueInstant");
            samlRequestAttributes[1] = doc.getRootElement().getAttributeValue("ProviderName");
            samlRequestAttributes[2] = doc.getRootElement().getAttributeValue("AssertionConsumerServiceURL");
            return samlRequestAttributes;
        } else {
            throw new SamlException("Error parsing AuthnRequest XML: Null document");
        }
    }

  • User 인증을 진행한다.
    boolean isValiduser = login(username, password);

    private boolean login(String username, String password) {
        LdapLoginHandler ldaplh = new LdapLoginHandler();

        if (password.length() < 1) return false;
        if (ldaplh.isValidOfficeUser(username, password)) {
            return true;
        } else {
            return false;
        }
    }
    * 실제 인증은 Identity Provider의 사정에 맞게 다양한 방식으로 진행한다.

  • RSA Keypair loading
    String publicKeyFilePath = keysDIR + "paygate_public.der";
    String privateKeyFilePath = keysDIR + "paygate_private.der";
    RSAPrivateKey privateKey = (RSAPrivateKey) Util.getPrivateKey(privateKeyFilePath, "RSA");
    RSAPublicKey publicKey = (RSAPublicKey) Util.getPublicKey(publicKeyFilePath, "RSA");

  • SAMLResponse의 유효기간정보 설정, 현시점부터 24시간 유효하게 설정함
    long now = System.currentTimeMillis();
    long nowafter = now + 1000*60*60*24;
    long before = now - 1000*60*60*24;

    SimpleDateFormat dateFormat1 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
    java.util.Date pTime = new java.util.Date(now);
    String notBefore = dateFormat1.format(pTime);
    java.util.Date aTime = new java.util.Date(nowafter);
    String notOnOrAfter = dateFormat1.format(aTime);

    request.setAttribute("notBefore", notBefore);
    request.setAttribute("notOnOrAfter", notOnOrAfter);

  • 인증을 거친 로그인 유저네임과 유효기간을 포함한 전자서명전의 SAMLResponse XML data생성
    String responseXmlString = createSamlResponse(username, notBefore, notOnOrAfter);

    private String createSamlResponse(
            String authenticatedUser,
            String notBefore,
            String notOnOrAfter) throws SamlException {
        String filepath = getServletContext().getRealPath("t/sso/saml/templates/" + samlResponseTemplateFile);
        String samlResponse = Util.readFileContents(filepath);
        samlResponse = StringUtils.replace(samlResponse, "##USERNAME_STRING##", authenticatedUser);
        samlResponse = StringUtils.replace(samlResponse, "##RESPONSE_ID##", Util.createID());
        samlResponse = StringUtils.replace(samlResponse, "##ISSUE_INSTANT##", Util.getDateAndTime());
        samlResponse = StringUtils.replace(samlResponse, "##AUTHN_INSTANT##", Util.getDateAndTime());
        samlResponse = StringUtils.replace(samlResponse, "##NOT_BEFORE##", notBefore);
        samlResponse = StringUtils.replace(samlResponse, "##NOT_ON_OR_AFTER##", notOnOrAfter);
        samlResponse = StringUtils.replace(samlResponse, "##ASSERTION_ID##", Util.createID());
        return samlResponse;
    }
    * createID()는 UniqueID를 생성하는 함수임.
    * getDateAndTime()은 SAML date format에 맞게 날짜시간정보를 생성하는 함수임.
    * SAML date format : yyyy-MM-ddThh:mm:ssZ (예: 2008-01-30T23:05:23Z)
    * 시간정보는 Localtime이 아닌 UTC에 맞춰줘야함

  • SAMLResponse에 대하여 전자서명
     String signedSamlResponse = SAMLSigner.signXML(responseXmlString, privateKey, publicKey);
    * 이때 publicKey는 SAMLResponse message에 포함되지만 데이터 암호화에는 사용되지 않는다.
    * xmldsig.jar, xmlsec.jar가 필요함

  • 서명된 SAMLResponse를 포함하여 ACS로 forwarding한다.
    request.setAttribute("samlResponse", signedSamlResponse);
    request.getRequestDispatcher(returnPage).include(request, response);

  • ProcessResponseServlet.java 구현 종합
/*
 * ProcessResponseServlet
 */

public class ProcessResponseServlet extends HttpServlet {

    private final String keysDIR = System.getProperty("PGV3_HOME")
            + SystemUtils.FILE_SEPARATOR + "SSO"
            + SystemUtils.FILE_SEPARATOR + "keys" + SystemUtils.FILE_SEPARATOR;

    private final String samlResponseTemplateFile = "SamlResponseTemplate.xml";
    private static final String domainName = "paygate.net";

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String SAMLRequest = request.getParameter("SAMLRequest");
        String returnPage = request.getParameter("returnPage");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String RelayState = request.getParameter("RelayState");
       
        boolean continueLogin = true;

        if (SAMLRequest == null || SAMLRequest.equals("null")) {
            continueLogin = false;
            request.setAttribute("error","ERROR: Unspecified SAML parameters.");
            request.setAttribute("authstatus","FAIL");
           
        } else if (returnPage != null) {
            try {
               
                String requestXmlString = decodeAuthnRequestXML(SAMLRequest);
                String[] samlRequestAttributes = getRequestAttributes(requestXmlString);
                String issueInstant = samlRequestAttributes[0];
                String providerName = samlRequestAttributes[1];
                String acsURL = samlRequestAttributes[2];

                boolean isValiduser = login(username, password); // 유저인증
               
                if (!isValiduser) {
                    request.setAttribute("error", "Login Failed: Invalid user.");
                    request.setAttribute("authstatus","FAIL");
                } else {
                    request.setAttribute("issueInstant", issueInstant);
                    request.setAttribute("providerName", providerName);
                    request.setAttribute("acsURL", acsURL);
                    request.setAttribute("domainName", domainName);
                    request.setAttribute("username", username);
                    request.setAttribute("RelayState", RelayState);

                    String publicKeyFilePath = keysDIR + "paygate_public.der";
                    String privateKeyFilePath = keysDIR + "paygate_private.der";
                    RSAPrivateKey privateKey = (RSAPrivateKey) Util.getPrivateKey(privateKeyFilePath, "RSA"); 
                    RSAPublicKey publicKey = (RSAPublicKey) Util.getPublicKey(publicKeyFilePath, "RSA");

                    long now = System.currentTimeMillis();
                    long nowafter = now + 1000*60*60*24;
                    long before = now - 1000*60*60*24;
                   
                    SimpleDateFormat dateFormat1 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
                    java.util.Date pTime = new java.util.Date(now);
                    String notBefore = dateFormat1.format(pTime);
                   
                    java.util.Date aTime = new java.util.Date(nowafter);
                    String notOnOrAfter = dateFormat1.format(aTime);
                   
                    request.setAttribute("notBefore", notBefore);
                    request.setAttribute("notOnOrAfter", notOnOrAfter);

                    if (!validSamlDateFormat(issueInstant)) {
                        continueLogin = false;
                        request.setAttribute("error", "ERROR: Invalid NotBefore date specified - " + notBefore);
                        request.setAttribute("authstatus","FAIL");
                    } else if (!validSamlDateFormat(notOnOrAfter)) {
                        continueLogin = false;
                        request.setAttribute("error", "ERROR: Invalid NotOnOrAfter date specified - " + notOnOrAfter);
                        request.setAttribute("authstatus","FAIL");
                    }

                    if (continueLogin) {
                        // 서명전의 SAMLResponse Message 생성
                       
String responseXmlString = createSamlResponse(username, notBefore, notOnOrAfter);
                        // SAMLResponse에 대한 전자서명
                        String signedSamlResponse = SAMLSigner.signXML(responseXmlString, privateKey, publicKey);
                        request.setAttribute("samlResponse", signedSamlResponse);
                        request.setAttribute("authstatus","SUCCESS");
                    } else {
                        request.setAttribute("authstatus","FAIL");
                    }
                }
            } catch (SamlException e) {
                request.setAttribute("error", e.getMessage());
                request.setAttribute("authstatus","FAIL");
            }
        }
        // Forward SAML response to ACS
        response.setContentType("text/html; charset=UTF-8");
        request.getRequestDispatcher(returnPage).include(request, response);
    }
}

  • SamlResponseTemplate.xml 참조
<samlp:Response ID="##RESPONSE_ID##" IssueInstant="##ISSUE_INSTANT##" Version="2.0"
    xmlns="urn:oasis:names:tc:SAML:2.0:assertion"
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </samlp:Status>
    <Assertion ID="##ASSERTION_ID##"
        IssueInstant="2003-04-17T00:46:02Z" Version="2.0"
        xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
        <Issuer>https://www.opensaml.org/IDP
        </Issuer>
        <Subject>
            <NameID
                Format="urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress">
                ##USERNAME_STRING##
            </NameID>
            <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"/>
        </Subject>
        <Conditions NotBefore="##NOT_BEFORE##"
            NotOnOrAfter="##NOT_ON_OR_AFTER##">
        </Conditions>
        <AuthnStatement AuthnInstant="##AUTHN_INSTANT##">
            <AuthnContext>
                <AuthnContextClassRef>
                    urn:oasis:names:tc:SAML:2.0:ac:classes:Password
                </AuthnContextClassRef>
            </AuthnContext>
        </AuthnStatement>
    </Assertion>
</samlp:Response>


3.5 SAMLResponse Message를 Service Provider의 ACS로 forward


3.5.1 login_proc.jsp : forwarding을 담당


<%@ page contentType="text/html; charset=UTF-8"%>
<%
    String acsURL = (String) request.getAttribute("acsURL");
    String samlResponse = (String) request.getAttribute("samlResponse");
    String RelayState = (String) request.getAttribute("RelayState");
    String authstatus = (String) request.getAttribute("authstatus");
    if (authstatus == null) authstatus = "FAIL";
    if (RelayState == null) RelayState = "";
%>
<html>
<head>
<title>forward to ACS</title>
</head>
<%
    if (samlResponse != null && authstatus.equals("SUCCESS")) {
%>
<body onload="javascript:document.acsForm.submit();">
    <form name="acsForm" action="<%=acsURL%>" method="post">
        <div style="display: none">
          <textarea rows=10 cols=80 name="SAMLResponse"><%=samlResponse%></textarea>
          <textarea rows=10 cols=80 name="RelayState"><%=RelayState%></textarea>
        </div>
    </form>
<%
    } else {
        %><script>alert('Login error'); history.back(-2); </script><%
    }
%>
</body>
</html>




3.6 Service Provider이 ACS에서 SAMLResponse를 verify

3.6.1 PublicACSServlet : Service Provider측의 ACS


  • Parameter를 받고
    String SAMLResponse = request.getParameter("SAMLResponse");
    String RelayState = request.getParameter("RelayState");

  • Identity Provider의 public Key를 load
    RSAPublicKey publicKey;
    publicKey = (RSAPublicKey) Util.getPublicKey(publicKeyFilePath,"RSA");

  • Identity Provider가 보내온 SAMLResponse를 서명검증(verify)
    boolean isVerified = SAMLVerifier.verifyXML(SAMLResponse, publicKey);

  • 검증을 거친이후 실제 서비스 사이트로 forward 요청
    request.getRequestDispatcher("./acs_proc.jsp").include(request, response);

  • PublicACSServlet.java 구현 종합
public class PublicACSServlet extends HttpServlet {
    private final String keysDIR = System.getProperty("PGV3_HOME")
            + SystemUtils.FILE_SEPARATOR + "CryptoServer"
            + SystemUtils.FILE_SEPARATOR + "keys" + SystemUtils.FILE_SEPARATOR;
   
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doPost(request, response);
    }
   
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String SAMLResponse = request.getParameter("SAMLResponse");
        String RelayState = request.getParameter("RelayState");

        // acs knows public key only.
        String publicKeyFilePath = keysDIR + "paygate_public.der";

        RSAPublicKey publicKey;
        try {
            publicKey = (RSAPublicKey) Util.getPublicKey(publicKeyFilePath,"RSA");

            boolean isVerified = SAMLVerifier.verifyXML(SAMLResponse, publicKey);

            if (isVerified) {

                String loginid = null;
                Document doc = Util.createJdomDoc(SAMLResponse);

                Iterator itr = doc.getDescendants();

                itr = doc.getDescendants(new ElementFilter());
                while (itr.hasNext()) {
                    Content c = (Content) itr.next();
                    if (c instanceof Element) {
                        Element e = (Element) c;
                        System.out.println("Element:" + e.getName());
                       
                        if ("NameID".equals(e.getName())) {
                            loginid = e.getText().trim();
                            break;
                        }
                    }
                }
                request.setAttribute("mid", loginid);
                request.setAttribute("RelayState", RelayState);
               
                response.setContentType("text/html; charset=UTF-8");
                request.getRequestDispatcher("./acs_proc.jsp").include(request, response);
            } else {
                System.out.println("SAMLResponse is modified!!");
                return;
            }

        } catch (SamlException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.7 완전하게 동작하는 WAR file

  • 화면상의 제약으로 인해 완전하게 동작하는 WAR File을 별도 준비하였다.
  • 파일이 필요한 분은 dev@paygate.net으로 메일요청하십시오.
  • 또한 아래 링크를 통해서 file download 위치를 확인할 수 있다.
    http://docs.google.com/Doc?id=dcxqxct2_4dsh8w4dt

4. SAML SSO 적용 실제 사이트


4.1 PayGate Admin Login

PayGate가 Service Provider 역할을 함.


4.1.1 최초 Service Login Page

  • 이 단계는 SAMLRequest를 생성하기전 서비스 제공자가 제시하는 화면이다.

4.1.2 Identity Provider로서 Login Form 제시


  • 이 화면이 보이기까지 서비스 제공자는 SAMLRequest를 생성하고
  • Identity Provider로 SAMLRequest를 redirect하면
  • 유저인증을 위해서 보여지는 화면이다.
  • 이 화면 이후 로그인 버튼을 누르게 되면 SAMLResponse 메세지를 생성하여 Service Provider의 ACS Site로 forwarding하게 된다.

4.1.3 Service Provider ACS verify를 거친후 마지막 페이지


  • Service Provider에 위치하는 ACS가 SAMLResponse를 verify한 이후 실제 서비스 제공 사이트로 forwarding한 결과이다.

4.2 Google Apps Service

  • google은 service provider역할을 하며
  • paygate는 identity provider역할을 한다.

4.2.1 gmail 서비스 로그인을 위한 인증 화면


  • google에서 SAMLRequest를 생성하여 identity provider인 페이게이트로 redirect하면
  • 페이게이트에서 유저 인증을 위하여 생성한 화면이다.


4.2.2 identity provider의 인증을 거친후 로그인된 화면



  • Identity Provider가 SAMLResponse를 생성하여
  • google acs로 forwarding하면 verify한 이후 실제 서비스 제공 사이트로 연결한다.


5. SAML의 확장


5.1 공인인증기관과 Identity Provider

SAML SSO는 국내에서는 공인인증기관이 Identity Provider역할을 한다면 아주 적당한 business model이 될것 같다.

공인인증기관의 신뢰성을 바탕으로 인증서 발행한 유저에 대한 확인을 직접 수행하고
Service Provider는 공인인증기관의 확인만을 검증하여 그 결과를 신뢰할 수 있다.

더 나아가서는 꼭 공인인증기관이 Identity Provider역할을 하지 않더라도
공인인증서 기반의 Public Identity Provider는 쉽게 예상할 수 있다.


5.2 Payment Gateway와 Identity Provider

결제대행사(Payment Gateway)사가 Identity Provider역할을 하고
쇼핑몰이 Service Provider가 되는 구조를 생각해볼 수 있다.

SAMLRequest는 충분히 확장가능한 구조이므로 Payment 요청에 필요한 필수정보 (상품명, 가격 등)을 포함하여
PG사에 대하여 Identity Provider로서 요청하고
PG사 사이트에서 안전하게 Payment를 처리하고 그 결과를 SAMLResponse format으로 돌려주는 구조이다.

이는 Cross Domain간에 Payment Protocol에 대한 표준이 부재한 현 상황에서
의미있게 시도해봄직한 목표이다.


6. 참고자료

SAML Single Sign On Service for Google Apps


 

OpenSAML


 

SAML at Wikipedia

 

출처  : 한국썬 개발자 네트워크 블로그

기고자: 페이게이트 이동산 이사 mountie@paygate.net



 

 

그동안 cvs로 현상관리를 해오고 있다가 얼마전 svn이라는 것을 접해보고 나서(클라이언트) 이번엔 SVN 서버를 한번 구축해볼까

하는 마음에 찾다가  삽질만이 살 길이다님 님의 통에서 마침 좋은 글을 찾아 올려봅니다

 

1. SVN 서버 설치하기


 

 우선 서브버전(앞으로 SVN이라고 합니다) 서버를 설치하기 위해
 
 
그리고 최신 버전을 다운로드 받습니다.
 
이 글을 작성하는 시점의 최신버전은 1.4.6 입니다.
 
다운로드 받은 svn-1.4.6-setup.exe 파일을 더블 클릭해서 실행합니다.

 

 
SVN 을 설치하겠냐는 질문이지요? [예] 를 눌러 설치를 계속합니다.

 

같은 질문 왜 두번 하는지 모르겠지만, NEXT 를 눌러 설치를 시작해봅니다.


 

SVN 에 대한 라이센스 정보를 읽어보시고, 동의함(I accept the agreement) 에 체크하시고,
 
Next 버튼을 클릭합니다.


 

간략하게, SVN 정보가 나오고, NEXT 를 누릅니다.



SVN 서버를 오토셋 설치 폴더 아래 Server 에 설치합니다.
 
여러분이 원하는 폴더에 설치하시면 됩니다.
 
그리고 Next 버튼을 클릭합니다.


 

SVN 프로그램 폴더명입니다.
 
그대로 두고, NEXT 를 누릅니다.
 
참고로... 굳이 생성하지 않아도됩니다. (실행할 수 있는 아이콘이 생성되지는 않습니다)

 
이 역시, 생성하지 않아됩니다만, 기본 사항에 체크하겠습니다.
 
앞 과정과 현재 나온 과정에서 생성하는 것은 관련 정보를 볼 수 있는 문서(Document)에 대한
 
아이콘 만들기이므로, 필요한 경우에만 체크하시면 됩니다.


 

설치되는 장소 확인하시고, Install 버튼을 눌러 본격적(?)으로 설치를 시작합니다.
 

 

파일이 복사되고...


 

 
윈도우 95, 98, 밀레니엄 사용자의 경우 Autoexec.bat 파일에 SET .... 부분을 추가하라는 안내가
 
표시되고 있습니다.. 윈도우 2000, XP 사용자는 신경쓰시지 않으셔도 됩니다.
 
NT 계열의 경우, 자동으로 SVN 설치 폴더가 PATH 로 잡혀서 어디서든 svn 명령이 실행되게 됩니다.
 
무슨 말인지 모르면 통과!!


 

Finish 를 눌러 설치를 종료합니다.
 
 
 
2. SVN 저장소 만들기 & 서버 시작

 

SVN  서버를 통해 버전 관리를 할 프로그램들이 저장되는 폴더를 생성합니다.

 오토셋 설치폴더 아래 svn_data 라는 폴더로 생성하겠습니다.

 여러분이 원하시는 장소에, 원하시는 폴더 명으로 생성하시면 됩니다.

 다만, 생성하신 경로와 폴더명은 반드시 기억하셔야 합니다.

 



--- 여기서부터 // 표시가 있는 부분까지는 이후 소개되는 토토이즈 SVN 에서 쉽게하실 수 있는 부분입니다 ---

 [시작] - [실행] 으로 가신 후, cmd 를 입력합니다.

 그리고 svn_data 가 있는 폴더로 이동한 뒤,

 svnadmin create --fs-type fsfs [생성할 저장소명] 을 입력합니다.

 여기서는 svnadmin create --fs-type fsfs autosetOrga 라고 입력하였습니다.

 즉, autosetOrga 저장소를 생성하는 것이고 파일시스템 저장소를 사용한다는 의미입니다.

 생성된걸 확인하기 위해, svn checkout file:///D:/AutoSet/svn_data/autosetOrga 를 실행해봅니다.

 체크아웃된 리비전 0. 이라고 나오면 정상적으로 체크아웃됨을 알 수 있습니다.

 // --- 여기까지...

 svnserve -d -r [저장소경로] 라고 입력함으로써 SVN 서버를 가동합니다.

 여기서는 svnserve -d -r D:\AutoSet\svn_data 라고 입력하였습니다.

 

참고사항 : svnserve 명령은 어떠한 폴더에서 실행하든 관계없습니다.

 

주의사항 : svnserve 명령 이후, 아무런 상태변화는 없게 됩니다. 이 상태를 유지하고 계셔야 SVN 서버가 작동하게 됩니다.


 

위 화면은 토토이즈 SVN 을 사용하지 않고 직접 체크아웃을 하는 방법 중, 네트워크를 통한 체크아웃 방법입니다.
 
이런 방법도 있다는 사실만 알고 넘어가시면 됩니다.
 
 
 
3. SVN 사용자 추가하기 (인증 부분)


저장소 루트\추가한 저장소 폴더(여기서는 autosetOrga)\conf\passwd 파일을 EditPlus 나 메모장으로 엽니다.
 
파일의 설명에도 써있듯이 매우 간단한 방법으로 인증 정보를 기입하시면 됩니다.
 
아이디 = 비밀번호 형태로 줄 단위로 입력하시면 됩니다.
 
kinor = autoset 이라고 입력하였으므로, 아이디는 kinor 이 되고, 비밀번호는 autoset 이 됩니다.
 
단, 주의 할점은 [users] 섹션 라벨 이후에 입력해주셔야 합니다.
 
일종의 INI 파일 형태로 보시면 됩니다.
 
그리고, 인증 정보를 구성하였으니 그 정보를 실제로 써야겠지요?
 
anon-access = read 라고 된 것을 anon-access 를 none 로 변경합니다.
 
이 설정은 익명 사용자의 접근시 읽기를 허용한 것을 허용하지 않는 것으로 설정을 변경하는 것입니다.
 
auth-access = write 라는 것은 인증 받은 사용자의 경우, 쓰기를 허용한다는 설정이 됩니다.
 
password-db 부분은 앞서 사용자를 추가한 패스워드 정보가 있는 파일의 위치를 설정합니다.
 
기본 값으로 놔둡니다.
 
그리고 realm = 에는 이 저장소의 인증시 나오는 타이틀을 입력해줍니다.
 
참고 : 그룹 사용자로 묶고자 한다면 authz-db 의 주석을 해제하고, authz 파일을 수정하면 됩니다.
이렇게 사용자 인증 정보 구성까지 마쳤습니다.
 
 
 
4. 거북이 SVN(Tortoise SVN) 설치하기
 
http://tortoisesvn.tigris.org/ 에 접속하여, 최신 버전의 토토이즈 SVN 을 다운로드 받습니다.
download page 를 클릭해서 다운로드 페이지로 이동합니다.



아직까지는 대부분의 PC 가 32비트이고 소프트웨어도 32비트 체제에서 만들어지고 있기 때문에...

다운로드 받을 파일들은 32 비트에 있는 파일들입니다.
 
토토이즈 SVN 설치파일과 아래 쪽으로 내려가서 한국어 언어팩을 다운로드 받습니다.

 

 
 

다운로드 받은 토토이즈 SVN 설치파일을 더블클릭해서 실행합니다.

 
NEXT 버튼을 눌러 설치를 시작합니다.


 
라이센스를 읽어보시고, 동의 함에 체크후, Next 버튼을 누릅니다.


 

역시.. Next 버튼을 누릅니다.


Install 버튼을 눌러 설치를 시작합니다

.

파일 복사가 시작되고...

 

설치가 완료됩니다.
 
Finish 를 눌러 설치를 종료합니다.

 

재 시작을 권유하는 메시지가 나옵니다만,
 
우리가 설치할 프로그램이 더 있으므로 NO 버튼을 과감히 눌러줍니다.
 

다음으로, 한국어 언어팩을 설치합니다.
 
다운로드 받은 파일을 더블클릭합니다.
 

설치 버튼을 눌러 설치를 시작합니다.

 
순식간에 설치가 되는 바람에 파일 복사 장면을 놓쳐버렸네요.
 
마침을 눌러, 패치도 완료합니다.
 

자, 이제 아무대서나..  (탐색기나 바탕화면에서..)
 
마우스 오른쪽을 찍! 클릭합니다.
 
그러면 TortoiseSVN 이라는 메뉴가 생긴 것을 확인할 수 있습니다.
 
어라?? 영어로 나오네요?
 
한국어로 보는 것이 편하겠죠?
 
Settings 를 눌러 언어를 변경합니다.
 


한국어를 선택하시고, [확인] 버튼을 누릅니다.
 

다시 찍! 클릭해보면 이젠 한국어로 잘 나옵니다.
 
 
5. 체크아웃 받기 / 파일 추가 / 업데이트 / 커밋하기
 

빈 폴더 또는 어떤 폴더에 들어가서 마우스 오른쪽을 누르시고,
 
SVN 체크아웃을 클릭합니다.


 

 
그리고 저장소 URL 에 svn://여러분의 IP주소/저장소명을 입력합니다.
 
svn://127.0.0.1/저장소명을 입력하셔도 됩니다.
 
그리고 최신 리비전에 체크된 걸 확인하시고, [확인] 버튼을 누릅니다.
 
 
 
그러면, SVN 서버 접속을 위해 ID와 암호를 묻게 됩니다.
 
passwd 파일에 추가한 정보를 입력해줍니다.
 
 

인증이 완료되고, 체크아웃이 됩니다.
 
아직 저장소에 저장한것이 없기 때문에 체크아웃을 해도 생성되는 파일은 없습니다.
  
 
'오토셋 사용자 설명서.txt' 파일을 생성하고,
 
마우스 오른쪽을 누른 다음 TortoiseSVN - 추가를 눌러 SVN 서버에 파일을 추가합니다.




 
그리고 SVN 커밋을 눌러 SVN 서버로 전송합니다.
 
 

전송시, 간단한 코멘트를 남길 수 있습니다.
 
확인 버튼을 눌러 커밋합니다.


 아래와 같이 내용을 수정하고....
 

 
혹시 모를 다른 사용자에 의한 수정을 확인하기 위해 업데이트를 한번 해주고...
 
사실.. 혼자 쓰기 때문에 굳이 업데이트 할 필요가 없겠지요...

 
수정 된 내용에 대해 간략히 메모하고.. (안쓰셔도 됩니다)
 
확인을 눌러 수정사항을 서버에 적용합니다.

그러면,리비전 2라는 것을 확인하실 수 있습니다.
 
버전이 한단계 올라간것이지요..
 
파일을 선택하고, 수정한 사람 보기를 눌러, 조회할 리비전 범위를 입력 후 확인 버튼을 누르면...
 
 
아래아 같이 각 버전별 수정자와 내용을 확인 할 수 있습니다.


 

 
 
이상으로 SVN 설치와 기본 사용법 소개를 마칩니다..
  
 

@Override 사용하기

개발 이야기/Java | 2008. 1. 24. 13:10
Posted by 시반

jdk 5에서부터 추가된 또하나의 기능인 @Override를 소개하고자 한다.

머 이미 쓰는 사람들은 많이 있겠지만

이제사 1.4를 벗어나려고 버둥대는 터라...*^^*

(쓰고 싶어두 기존 소스와의 유지 및 개발환경에 써보질 못했다는 핑계거리....)

 

@Override란 위에서 말한바와 같이 jdk5에서부터 추가된 annotion의 하나이다.

말그대로 상위 클래스에서 오버라이드한 메소드라는 것을 지칭한다.

 

public class HelloServlet extends HttpServlet{

@Override

public void doGet(HttpServletRequest request,HttpServletResponse response)

    throws IOException,ServletException{

    -- to_DO --

}

}

 

위의 경우 HelloServlet은 HttpServlet에서 정의한 doGet()을 오버라이드 하고 있다.

물론 @Override를 사용하지 않아도 되지만

 

이 때 @Override 를 붙임으로써 doGet() 가 상위클래스에서 오버라이드된 메소드임을 지정함으로써

개발자가 메소드명이나 파라미터를 잘못 쓰고 찾지 못하는 것을 방지해준다.

 

즉 @Override를 상위클래스에 정의되지 않은 메소드에 쓰는 경우 컴파일 에러를 발생하기 때문에

혹여나 발생할수 있는 문제의 소지를 예방할 수 있게 해주기 때문에

오버라이드한 메소드에 대하여 @Override라는  annotation을 써주는 것이 좋다.

 

 

 

 

 

Generic 사용하기..*^^*

개발 이야기/Java | 2008. 1. 23. 17:26
Posted by 시반

JDK 1.5부터 Gerneric라는 개념이 추가되었습니다.

 

이전에는 Collection 객체에 데이터를 넣고 뺄때 Object 타입을 사용했었죠..

즉 Collection 객체에 넣었을 때의 타입으로 캐스팅을 해서 사용합니다.

 

ArrayList list = new ArrayList();

list.add("test");

String value = (String)list.get(0);   // 꺼낼때 캐스팅 해야함.

 

하지만 Generic이라는 개념이 도입되면서 조금 바뀌게 되었네요.

type을 명확하게 정하지 않고도 type을 처리할 수 있는 C++의 템플릿과 비슷한 기능으로 사용법은 비슷합니만

다른 점은 <> 사이에 들어가는 type의 수만큼 class를 만드는 C++과는 달리 java에서는

하나의 class로 여러 type들을 처리합니다

 

실제로 collection 객체를 사용할때 하나의 객체에 서로 다른 타입을 넣어서 사용할 때는 잘 없습니다.

주로 한가지 타입의 객체만을 넣어서 사용합니다.

이때 실수로 다른것 넣을 수도 있습니다. 이런 부분은 컴파일 시에는 잡히지고 않고 실행시에만 발견 됩니다.

 

하지만 Generic 을 사용함으로써 컴파일시에 오류를 잡을 수 있으므로 더욱 견고한 코드를 만들 수 있겠져

이렇게 Generic를 사용하는 첫번째 이유는 생성시 사용할 타입을 지정할 수 있으므로 다른 타입의

객체를 실수로 사용하는것을 컴파일타임에 방지할 수 있다는 점에 있습니다

 

 ArrayList<String> list = new ArrayList<String>();
 list.add("1");
 list.add("2");
 String value = list.get(0);            

 

 

위의 예에서  list엔 String만이 들어갈것을 선언하였기에 값을 집어넣을때도 String형만을 넣을수 있습니다.

반대로 꺼낼때는 String형으로 반환됩니다.

즉  반환타입이 지정되어 있기 때문에 이전과는 달리 꺼낼때 캐스팅이 필요 없다는 겁니다.

 

 

이렇게 Collection에서 객체를 꺼낼때 캐스팅 없이 꺼낼수 있다는 점이 Generic을 사용하는 또하나의 장점이 될 수 있겠죠.

 

Generic을 쓰는 또하나의 이유는 Auto Boxing과 Auto UnBoxing일겁니다

자바는 객체지향언어이지만 기본타입들은 객체가 아닙니다. 그래서 이들에 대한 Wrapper클래스들을 자바에서는 제공하고 있습니다.

 

int num1 = 1;


Integer num2 = new Integer("2");

int num = num1+num2.intValue();

 

 

기존의 경우 기본타입을 객체로 사용하기 위해서 wrapper class에 담고 이후에


다시 기본형값을 사용하고자 할때는 wrapper Class에서 그 값을 반환하는 작업이 필요했지만

5.0 이후부터는 이러한 부분이 자동으로 이루어진다는 점(조아조아)

 


int num1 = 1;


Integer num2 = 2;      //auto Boxing

int num = num1+num2; //auto UnBoxing

 


Generic과 관련해보면


  
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("1", new Integer(1));
map.put("2", 2);            // auto boxing
 
Integer i = map.get("1");   // 따로 캐스팅이 필요 없다.
int j = map.get("2");       // auto unboxing


이렇게 별도의 캐스팅 없이 처리할 수 있다는 점에서 좋아졌네요..

 


 

 

jxl을 통한 엑셀 저장하기

개발 이야기/Java | 2008. 1. 16. 14:08
Posted by 시반

먼저 관련사이트 에서 jexcelapi 를 다운받는다.

압축파일을 열어보면 관련 javadoc 및 소스파일도 함께 있으니 참고하시길...

여하튼 jxl.jar 파일을 관련 프로젝트에 등록한다.

 

<관련 클래스 간략설명>

jxl.write.WritableWorkbook : workbook,sheet등을 생성 및 관리하기 위한 엑셀파일관리 추상 클래스

jxl.Workbook : 기본 엑셀파일 추상 클래스

jxl.write.WritableSheet : sheet를 관리하는 인터페이스

jxl.write.WritableCellFormat : 엑셀 내 cell관련 포맷정보

jxl.write.Label : 엑셀내 필드명(엑셀의 cell에 들어있는 데이타 값이라 생각하면 된다)

jxl.write.Blank : 빈 cell관리 클래스

 

<엑셀 처리>

1. 엑셀파일을 생성한다

 WritableWorkbook testExcel = Workbook.createWorkbook(new File("sample.xls"));

 

  기존의 엑셀파일을 읽어와 처리하고자 하는 경우에는 다음과 같다.

 

 WritableWorkbook testExcel = Workbook.getWorkbook(new File("sample.xls"));

 

2. 생성된 엑셀관리 객체의 sheet를 선택한다

 WritableSheet sheet1 = testExcel.createSheet("1stShell", 0);

  위의 경우 새로이 생성된 엑셀파일내 새로운 sheet를 추가하는 것이라면

 기존의 생성된 특정 sheet를 선택하고자 하는 경우 다음과 같다

 WritableSheet sheet0 = testExcel.getSheet("preShell") ;

 

3. 셀서식을 설정한다     

WritableCellFormat titleFormat= new WritableCellFormat(); 

 - 폰트 설정

WritableCellFormat은 쉘정열 및 테두리설정 및 배경색 등 셀서식과 관련된 설정을 할 수 있다.

폰트 설정의 경우 다음과 같이 처리한다.

 

WritableCellFormat titleFormat
  = new WritableCellFormat(
           new WritableFont (WritableFont.ARIAL,              //폰트 타입.Arial 외 별다른건 없는듯 하다.
                                 20,                                          //폰트 크기
                                 WritableFont.BOLD,                  //Bold 스타일
                                 false,                                      //이탤릭체여부
                                 UnderlineStyle.NO_UNDERLINE, //밑줄 스타일
                                 Colour.WHITE,                         //폰트 색
                                 ScriptStyle.NORMAL_SCRIPT)); //스크립트 스타일

 

폰트 설정의 경우 위와 같이 Font설정 객체를 생성한후 서식객체 생성시 인자로 넣으면 된다.

다양한 생성자를 제공하고 있으니 굳이 위화면과 같이 할 필요는 없다. (API 참조)

 

-셀 서식 설정

기타 셀서식과 관련 주로 사용하는 기능을 설명하면 다음과 같다.

     titleFormat.setAlignment(Alignment.CENTRE);                     // 셀 가로정열(좌/우/가운데설정가능)
     titleFormat.setVerticalAlignment(VerticalAlignment.CENTRE); // 셀 세로정렬(상단/중단/하단설정가능)
     titleFormat.setBorder(Border.ALL, BorderLineStyle.THICK);  // 보더와 보더라인스타일 설정
     titleFormat.setBackground(Colour.ICE_BLUE);                    // 배경색 설정

4. 데이타를 셀에 등록한다

 Label title = new Label(0, 0, "테스트 타이틀",  titleFormat);  //라벨(열,행,"문장",포멧) : 숫자형은 Number를 사용
 sheet1.addCell(title);                                                // sheet1의 1열1행에 "테스트 타이틀"이라는 데이타를 넣는다

 

--> 데이타 등록을 위한 Label 생성시 첫번째 인자는 sheet의 열, 두번째 인자는 행을 의미한다는 것에 주의...

 

5. Sheet 디자인 설정(*^^*)

이부분은 해도 그만 안해도 그만이지만 깔끔한 엑셀저장을 위한 선택코스?

 

-셀 크기 변경

관련메소드는 setColumnView(몇번째 컬럼, 넓이)
   sheet1.setColumnView(0, 20); //sheet1의 첫번째 열의 크기를 20으로 설정한다

또는

  CellView cv = sheet1.getColumnView(0);

  cv.setSize(30);                    

  sheet1.setColumnView(0,cv);    //기존의 열정보 변경. 30으로 변경한다  

 

- 셀병합

   sheet1.mergeCells(0, 0, 5, 0 ); //sheet1의 1열1행의 cell을 6열1행의 셀까지 병합한다.

 

- 빈 셀 처리 

  Blank blank = new Blank(6, 0, titleFormat); // 빈 셀(열,행,포멧)

  sheet1.addCell(blank);                             // 위의 병합셀옆에 같은서식의 빈셀추가

 

6. 데이타를 파일에 저장한다. 

   testExcel.write(); // 쓰고

   testExcel.close(); // 닫자

 

서두에서 밝힌바와 같이 기타 관련 API의 내용은 압축파일내 docs 디렉토리 밑에 나와 있어여..

'개발 이야기 > Java' 카테고리의 다른 글

@Override 사용하기  (0) 2008.01.24
Generic 사용하기..*^^*  (0) 2008.01.23
JAVA API Chm파일 다운로드 링크  (0) 2007.12.21
[JBoss 보안] DataSource 패스워드 암호화  (0) 2007.06.26
[java] 예약어 enum  (0) 2007.06.04
 

JAVA API Chm파일 다운로드 링크

개발 이야기/Java | 2007. 12. 21. 13:51
Posted by 시반

chm 파일로 java api가 있는 곳입니다. 아쉽게도 한글은 아니라는거...

 

J2SE 6 documentation 도 있으니 한번 가서 받아봐두 될듯...

http://www.allimant.org/javadoc/index.php

 

J2SE 5 documentatation 은 한글이 다행히 있더군여..

(okjsp에서 링크를....)

 

6.0이 나온지 꽤 되었건만 사용해봤다기도 머한것이

기존의 패키지를 컨버젼한거 외에는

6.0에서 새로이 추가된 기능을 사용해본적이 없다는 것이 아쉽다..

 

손에 익은거만 쓰려는 이 내 귀차니즘...

 

그래서 마침 플젝도 끝났겠다..API나 훑어 보자는 맘에 함 찾아보았다...

흐음 새로운게 많네요...간만에 공부좀...*^^*

 

 

'개발 이야기 > Java' 카테고리의 다른 글

Generic 사용하기..*^^*  (0) 2008.01.23
jxl을 통한 엑셀 저장하기  (0) 2008.01.16
[JBoss 보안] DataSource 패스워드 암호화  (0) 2007.06.26
[java] 예약어 enum  (0) 2007.06.04
JAVA SE의 정규표현식  (0) 2007.05.30
 

JBoss의 DataSource 설정파일은 XXX-ds.xml 파일로 되어 있으며

해당 데이타베이스의 패스워드는 텍스트파일로 되어 있기 때문에 JBoss에서는 패스워드를 암호화함으로써

패스워드 노출에 대한 위험성을 최소화할수 있는 기능을 제공한다

 

1. ../conf/login-config.xml에서 다음과 같이 설정한다

 

    <application-policy name="encrypted-ds-domain">
      <authentication>
        <login-module code="org.jboss.resource.security.SecureIdentityLoginModule"
          flag="required">
          <module-option name="username">scott</module-option>
          <module-option name="password">69514c88069891884686a773aa4001eb</module-option>
          <module-option name="managedConnectionFactory">

               jboss.jca:service=LocalTxCM,name=MyDatasource</module-option>
        </login-module>
      </authentication>
    </application-policy>   

 

2.  패스워드 생성방법

 

$set CLASSPATH=lib/jboss-jmx.jar:lib/jboss-common.jar:server/default/lib/jboss-jca.jar:server/default/lib/jbosssx.jar

 

$java org.jboss.resource.security.SecureIdentityLoginModule newpassword

Encoded password:69514c88069891884686a773aa4001eb

 

3. 이후 Datasource 설정파일에서 해당 도메인을 사용한다

   즉 XXX-ds.xml 의 security-domain 에  1.에서 설정한 도메인 네임을 적으면 된다

   위의 예를 들면 ../deploy/MyDatasource-ds.xml를 열어 다음과 같이 작성한다

 

  <jndi-name>iscmsDS</jndi-name>
   .......

    <security-domain>encrypted-ds-domain</security-domain>
   .......

  </local-tx-datasource>

 

 

'개발 이야기 > Java' 카테고리의 다른 글

jxl을 통한 엑셀 저장하기  (0) 2008.01.16
JAVA API Chm파일 다운로드 링크  (0) 2007.12.21
[java] 예약어 enum  (0) 2007.06.04
JAVA SE의 정규표현식  (0) 2007.05.30
[본문스크랩] JAVA FTP 프로그램   (0) 2006.12.19
 

[java] 예약어 enum

개발 이야기/Java | 2007. 6. 4. 10:08
Posted by 시반

소스를 정리하다 보니 다음과 같은 Warning이 나오는걸 확인했다.

 

'enum' should not be used as an identifier, since it is a reserved keyword from source level 5.0 on

 

흐음 대충 해석하면 enum은 1.5이상 버젼부터는 예약어로 사용하고 있으니 되도록 사용하지 말것을 권장하고 있다는 것인데...

 

검색해보니 역시 jdk1.5부터 enum 키워드가 추가되었슴을 알게 되었다.

(이궁 그럼 1.5이상으로 변경할 경우 소스 고칠게 많아 지겠군... -_-ㅋ   )

 

enum 키워드는 enumeration 이름과 괄호안으로 들어가야할 가능한 값들을 사용할때 쓰이는데 그 용법은 다음과 같다

enum Color {red, green, blue}

 

예제..


  public class EnumTest {
    public static void main(String args[]) {
      enum Color {red, green, blue};

      // Get collection of values
      System.out.println(Color.VALUES);

      // Check if collection type is java.util.List
      System.out.println(Color.VALUES instanceof java.util.List);

      // Create variable of type
      Color aColor = Color.green;

      // Use enumeration in switch
      switch(aColor) {
        case Color.red:
          System.out.println("Got red.");
          break;
        case Color.green:
          System.out.println("Got green.");
          break;
        case Color.blue:
          System.out.println("Got blue.");
          break;
      }
    }
  }

 

 

 
블로그 이미지

시반

시반(詩伴)이란 함께 시를 짓는 벗이란 뜻을 가지고 있습니다. 함께 나눌수 있는 그런 공간이길 바라며...

카테고리

분류 전체보기 (233)
개발 이야기 (73)
Java (22)
VoIP (19)
이클립스 (22)
ORM (6)
MINA (4)
WEB2.0 (57)
DB2 (24)
MySQL (6)
오라클 (26)
기타 (44)
취미 (0)
잡담 (2)