패턴과 스프링에 관한 추가 서적

Posted at 2010.10.01 03:38 // in OpenSource // by MOOVA 무바㏇
Apress Pro.Java EE Spring Patterns - 2008년도 Apress에서 나온 책이다.
Java EE 디자인 패턴기준으로 스프링의 각 요소에서 쓰이는 기술들을 분석했다.
2008년에 나온 기술이라서 Spring 2.5기반이지만 java EE패턴이 중심이라서 그다지 버전차이에 대한 불편함은 느끼지 못한다. (Java EE 패턴을 스프링으로 체험하고 싶은 분만^^)

2010:10:01 03:15:02

Packt Publishing 에서 나온 Spring Security 3 이다. 2010년에 나온 이북이라서 버전차이에 대한 압박은 느낄 수 없다. 특히 얼마전 Access Control Lists와 관련된 포스트를 진행할 때 많은 도움을 받았던 책이다. 실제 소스코드를 기반으로 실전영역에서 Spring Security 3가 어떻게 활용되는지 많은 영감을 받을 수 있다.

2010:10:01 03:21:27


위의 서적은 이-북이긴 하지만 모두 유료버전이라서 금액에 대한 부담은 조금 있는 편이다.
하지만 잘 찾아보면 얻을 수 있는 루트가 있다~^^

여태까지 나온 스프링 국외 서적은 아마 대부분 읽어본 듯.. 몇 개만 빼고~^^
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
블로그코리아에 블UP하기

댓글을 남겨주세요.

[Spring Security] Domain ACL 실무판

Posted at 2010.08.18 01:04 // in OpenSource // by MOOVA 무바㏇
본 아티클은 ACL Spring Security tutorial 의 내용을 대부분 의역, 개선해서 편집하였습니다.

아직 한국에서는 Spring Security의 쓰임이 비교적 많지 않고, 실무에 쓰일 적당한 예제나 문서가 매우 빈약한 편입니다.
얼마전에 자바지기님이 블로그에 스프링 시큐리티에 대한 연재를 한 것과 동시에, 저도 지속적인 공유를 하기위해 스프링 시큐리티와 관련된 아티클을 정리해서 올려두었습니다.

스프링 시큐리티를 그대로 쓸 수 없는 빈약한 한국의 환경에도 불구하고 여전히 스프링 시큐리티가 가지고 있는 수 많은 옵션은 매우 매력적인 부분이 많이 있습니다. 관공서, SDS, LGT등 다수의 프로젝트를 진행하면서 종종 보안과 관련된 작업을 하면서 겉잡을 수 없을 많큼 중복되는 코드와 구 공통 개발자들이 만들어놓은 교착상태의 코드를 보고 스프링 시큐리티의 도입이 절실히 필요하다고 느꼈습니다. 차 후 보안 시스템이 복잡하게 되더라도 스프링 시큐리티가 제공하는 많은 옵션을 사용하면 예상치 못한 크리티컬한 문제점에 매우 유연하게 처리할 수 있으리라 기대해 봅니다.

시작

이 아티클은 PostrgeSQL을 사용하여 Spring Security 웹 애플리케이션으로 구축할 수 있는 간단한 방법을 보여주고 있습니다.. 효과적인 Spring Security를 위한 학습은 상당히 까다로운 편이여서 우리는 매우 초보적인 단계에서부터 독자들을 이해시키기 위해 본 아티클을 쓰기로 했습니다. 필자는 범위성 및 확장성있는 PostgreSQL의 완고한 팬으로서 본문을 공유하는 것이 매우 즐겁습니다. 대부분의 웹 어플리케이션(application)은, 인가되지 않은 사람들과 인가된 사람들의 행동을 분리하기 위해서 보안 시스템을 도입합니다.

보안 시스템의 가장 간단한 구조는 등록된 사용자가 메시지를 투고하거나, 미등록 사용자가 원하는 자원을 읽게 되거나, 관리자가 사용자를 차단하거나 자원을 수정, 삭제 금지하는 행위를 기본적으로 포함하고 있습니다. 보다 복잡한 애플리케이션에서는 확실한 접근을 위해 사용자가, 사용하게 될 클래스에 영역의 결정을 그리 쉽게 처리할 수 없는 문제입니다.
운이 좋게도 스프링 시큐리티는 위와같은 기본적인 구조를 가지고 있습니다. 추가로 ACL란 용어에 대해서 기본적으로 알아야 할 필요가 있는데 이것은 주어진 사용자가 필요한 도메인 오브젝트에 접근하기 위한 행동양식을 리스트로 나타낸 표와 같습니다. 우리는 스프링 시큐리티 ACL의 튜토리얼을 BSD라이센스인 spring starter 애플리케이션을 활용할 것입니다. 주어진 url로 부터 해당 샘플을 다운로드 받아 소스코드를 빌드하고 배포해 봅시다. (배포방법은 생략)

이 애플리케이션에서 구현하게 될 것은 간단한 인터넷뱅킹 시스템입니다.. 인터넷뱅킹을 주로 이용하는 사용자는 직원과 고객입니다.. 두 종류의 사용자는 애플리케이션에서 공통의 User라는 개념에 포함되지만 사실은 뱅킹시스템에 접근하는 목적이 다릅니다. 두 가지 타입을 분류해 놓기 위해서 User 를 상속한 Clerk와 Customer 클래스로 타입을 분류해 놓습니다.
Clerk와 Customer는 은행예금계좌(bank account)에 접근할 수 있고, 각각의 은행예금계좌는 은행업무와 관련된 로직을 포함하고 있습니다.. (예금이나 철회등)

Clerk는 Full Admin권한으로 고객은행예금계좌(create,read,update,delete)를 완전하게 관리할 수 있지만,  은행예금계좌의 조작은 read-only로 제한을 받습니다.

다음의 클래스 다이어그램에서 AbstractSecureObject를 베이스로한 상속관계를 살펴볼 수 있습니다.

Figure 1 . AbstractSecureObject를 상속한 도메인 오브젝트s

위의 그림에서 Clerk, Customer, BankAccount, BankAccountOpperation 클래스는 각각의 의미를 가지고 있습니다. Clerk와 Customer는 Authority-s의 리스트를 가지고 있는 Users로 정확하게 대응됩니다. User와 Authority는 N:N 관계로서 하나의 Users는 다수의 Authority를 포함할 수 있고, 하나의 Authority는 다수의 Users를 포함할 수 있습니다. 클래스다이 그램은 users와 authority를 단일의 클래스로 나타낼 수 있지만 데이터베이스는 3개의 테이블(Users, Authority, Users_Authority)이 필요할 것입니다. Customer객체는 BankAccountOperation-s의 집합체를 가지고 있는 BankAccount의 키와 연관이 됩니다. 애플리케이션에 걸쳐 보안 관련 정보의 식별을 처리하기 위해서, Clerk,Customer,BankAccount, BankAccountOperation는 AbstractSecureObject라고 하는 공통된 추상화 클래로부터 상속되어집니다. 이것은 Spring Security에서 연동을 위한 DomainObject를 단일화하고 통일화하기 위한 설계입니다.

자..이제 Spring Security와 연동하기 위해서 비즈니스 로직을 작성해야 할 필요가 있다. Spring Security는 일련의 보안관련정보를 저장하기 위해서 데이터베이스 테이블을 이용하고 테이블을 생성할 것입니다.

Springstarter application에서 acl.sql파일을 postgresql로 임포트를 하면 apring acl에 관련된 테이블들이 작성되어집니다. ( 만약 데이터베이스가 postgre이외의 것, mysql이나 sqlite등의 데이터베이스라면 우리는 그에 적절한 드라이버를 다운로드 받아 애플리케이션 WEB-INF/lib의 위치에 넣어 두어야 합니다. 그리고 해당 데이터베이스 환경에 맞게, hibernate.properties를 수정해야합니다. 본고에서는 기본으로 설정되어 있는 SpringStarter 애플리케이션의 수정없이 환경을 동일하게 적용하여 진행하기로 합니다.

http://www.postgresql.org/ 최신버전
hibernate.properties 의 데이터베이스 설정환경 (springstarter 데이터베이스를 생성하고, 유저는 postgres를 그대로 사용함)

hibernate.connection.url=jdbc:postgresql://localhost:5432/springstarter
hibernate.connection.driver_class=org.postgresql.Driver
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
hibernate.connection.username=postgres
hibernate.connection.password=

데이터베이스 테이블 설명 :

DROP TABLE ACL_ENTRY;
DROP TABLE ACL_OBJECT_IDENTITY;
DROP TABLE ACL_CLASS;
DROP TABLE ACL_SID;

ACL_SID 테이블은 Spring security를 사용하는 모든 사용자(Users)들을 sid기준으로 목록화합니다. SID는 사용자가 요구한 오브젝트를 수행하기 위해서 ACL에서 사용될 수 있으며, 사실상 sid는 애플리케이션에 있어서 액션을 수행하는 유저와 Role로 구별될 수 있습니다. 이 두가지 구별은 ACL_SID 테이블에 principal 컬럼의 boolean값으로 구별합니다. principal이: true라면 sid가 user의 키값으로 적재될 수 있으며, false라면 granted authority 를 의미합니다. 알아두어야 할 부분은 이 테이블은 사용자의 세부정보(이름,패스워드,연관된 자세한 사용자 정보들..)를 포함하지 않는다. 그것은 단지 users나 authority의 id를 사용할 뿐이다.

CREATE TABLE ACL_SID (
    id BIGSERIAL NOT NULL PRIMARY KEY,
    principal BOOLEAN NOT NULL,
    sid VARCHAR(100) NOT NULL,
    CONSTRAINT UNIQUE_UK_1 UNIQUE( sid, principal)
);


사용자를 목록화하고, 그밖에 사용자가 수행하는 오브젝트를 목록화 하기 위해, 우리는 도메인 오브젝트와 관련된 자바 클래스와 그 클래스가 인스턴스된 상태의 값을 데이터베이스에 적재해야합니다. 이는 Spring Security ACL에서 권장하고 있는 ACL_CLASS 테이블과 ACL_OBJECT_IDENTITY테이블로 모델링 할 수 있습니다. 이렇게 ACL를 위한 기본적인 테이블 생성은 Springstarter 애플리케이션을 위해, 또는 Spring Security가 인식할 수 있는 고 수준의 기법입니다.

ACL_CLASS 테이블안에 class컬럼은 자바의 풀 패키지명과 클래스이름을 기록합니다.(예 : com.denksoft.springstarter.BankAccount ) 이것은 Domain Object의 유일한 식별이름입니다. 각각의 풀 클래스이름에 구별될 수 있는 id를 지정하는 이유는 Spring Security에서 키를 사용해서 이 테이블을 접근 할 수 있기 때문입니다.

CREATE TABLE ACL_CLASS (
        id BIGSERIAL NOT NULL PRIMARY KEY,
        class VARCHAR NOT NULL,
        CONSTRAINT UNIQUE_UK_2 UNIQUE(class)
);

보안하고자 하는 시스템의 오브젝트는 ACL_OBJECT_IDENTITY 테이블에 단일하게 식별되어어서 등록됩니다. 해당테이블은 ACL_CLASS의 식별자 id를 참조키로 지정한 object_id_class 컬럼과, 실제 클래스가 수행된(예:BankAccount 테이블) 테이블의 참조키로 지정된 object_id_identity 컬럼, 각각의 오브젝트의 책임자인 owner_id컬럼이 준비되어 있습니다. 만약에 준비된 오븐젝트에 중첩된 구조를 표현하고자 한다면, parent_object와 entries_inheriting컬럼을 활용합니다.

CREATE TABLE ACL_OBJECT_IDENTITY (
        id BIGSERIAL NOT NULL PRIMARY KEY,
        object_id_class BIGINT NOT NULL,
        object_id_identity BIGINT NOT NULL,
        owner_sid BIGINT,
        parent_object BIGINT,
        entries_inheriting BOOLEAN NOT NULL,
        CONSTRAINT UNIQUE_UK_3 UNIQUE ( object_id_class , object_id_identity ),
        CONSTRAINT FOREIGN_FK_1 FOREIGN KEY ( parent_object ) REFERENCES ACL_OBJECT_IDENTITY(id),
        CONSTRAINT FOREIGN_FK_2 FOREIGN KEY ( object_id_class ) REFERENCES ACL_CLASS(id),
        CONSTRAINT FOREIGN_FK_3 FOREIGN KEY ( owner_sid ) REFERENCES ACL_SID(id)
);


최종적으로, 모든 사용자와 모든 보안관련 테이블을 참조해서 요청을 한 사용자로 하여금 각각의 오브젝들을 취급(create,update,delete,read)가능하게 해 줄 ACL_ENTRY테이블을 설계합니다.

(필드acl_object_identity)은 ACL_OBJECT_IDENTITY를 참조키로 등록하고, 그것을 수행할 수 있는 유저의 SID또한 sid컬럼에 참조키로 연관짖습니다. Mask는 별도로 관리된 비트숫자로 등록할 수 있습니다. Mask는 퍼미션에 관련된 컬럼으로써 예를들어 8비트의 00000101이 등록되어 있으면, 수행하는 액션은 0과 2를 허락하고 1은 허락하지 않습니다. Mask지정하는데 좋은 참고가 되는 것은 OS에서 사용되는 Security 관리 방법입니다. 이것은 리눅스나 유닉스, 윈도우환경에서 모두 비슷한 처리방식을 가지고 있기 때문에 OS의 보안관련사항을 학습하시면 Mask적용방법을 이해하는데 수월할 것으로 예상됩니다.

2010:08:17 07:31:55

 ACL_ENTRY테이블에서 granting 컬럼이 true로 지정되어 있으면 mask로 지정된 허가를 수행하며, false일 경우 그것을 취소하고 블록킹합니다. 운좋게도 ace_order필드는 접근 권한 엔트리의 우선순위를 결정하는 컬럽입니다.

CREATE TABLE ACL_ENTRY (
        id BIGSERIAL NOT NULL PRIMARY KEY,
        acl_object_identity BIGINT NOT NULL,
        sid BIGINT NOT NULL,
        mask INTEGER NOT NULL,
        ace_order INT NOT NULL,
        granting BOOLEAN NOT NULL,
        audit_success BOOLEAN NOT NULL,
        audit_failure BOOLEAN NOT NULL,
        CONSTRAINT UNIQUE_UK_4 UNIQUE(acl_object_identity,ace_order),
        CONSTRAINT FOREIGN_FK_4 FOREIGN KEY(acl_object_identity) REFERENCES ACL_OBJECT_IDENTITY(id),
        CONSTRAINT FOREIGN_FK_5 FOREIGN KEY(sid) REFERENCES ACL_SID(id)
);

아래 그림은 AbstractSecurityobject를 상속한 클래스들과 데이터베이스에 구축한 테이블의 데이터들이 어떻게 유기적으로 맞물리고 있는지 알기쉽게 표현한 그림입니다.

Spring Security configuration
security-config.xml 파일에 ACL의 기본적인 구축을 위해 사전에 필요한 설정을 해야합니다. 본 설정의 보다 자세한 사항은 다음의 포스트를 참조해 주십시오.

( Spring Security - URL 권한을 DB로 관리하기 1. Spring Security - URL 권한을 DB로 관리하기 2 )

<?xml version="1.0" encoding="UTF-8"?>
    <beans:beans xmlns="http://www.springframework.org/schema/security"
                 xmlns:beans="http://www.springframework.org/schema/beans"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:p="http://www.springframework.org/schema/p"
                 xsi:schemaLocation="http://www.springframework.org/schema/beans
                 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                 http://www.springframework.org/schema/security
                 http://www.springframework.org/schema/security/spring-security-2.0.1.xsd">
         <global-method-security secured-annotations="enabled"/>
         <authentication-provider user-service-ref="userDetailsServiceWrapper" />
         <http>
             <intercept-url pattern="/app/clerk/**" access="ROLE_CLERK"/>
             <intercept-url pattern="/app/customer/**" access="ROLE_CUSTOMER"/>
             <intercept-url pattern="/app/public/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
             <anonymous/>
             <form-login login-processing-url="/login" login-page="/login.jsp" default-target-url="/app/index.task" />
             <logout logout-url="/logout" logout-success-url="/app/index.task" />
         </http>
</beans:beans>

위의 설정으로 기본적인 인증과 인가에 관련된 로그인 매커니즘을 스프링 시큐리티가 알아서 제공해 줍니다.

Authorization configuration file

다음으로 설정될 항목은 objectManagerSecurity입니다. 이 빈의 등록으로 보안 매커니즘을 시작할 수 있는 인터셉터가 생긴 것과 다름없다고 볼 수 있습니다. 이것은 스프링시큐리티에서 ACL을 시작할 수 있는 뿌리가 됩니다.objectManagerSecurity 빈은  org. springframework. security. intercept. AbstractSecurityInterceptor을 확장한 클래스입니다. 자세한 학습을 위해 API를 참조할 수도 있겠지만, 지금은 이 빈을 등록함으로써 모든 보안 요청을 가로채기에 충분함으로 api에 대한 자세한 설명은 레퍼런스를 참고하시기 바랍니다.

<bean id="objectManagerSecurity" class="org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor" autowire="byType">
        <property name="accessDecisionManager" ref="businessAccessDecisionManager"/>
        <property name="afterInvocationManager" ref="afterInvocationManager"/>
        <property name="objectDefinitionSource" ref="objectDefinitionSource"/>
</bean>
<bean id="objectDefinitionSource" class="org.springframework.security.annotation.SecuredMethodDefinitionSource" />

또한, 이 설정을 가능하게 하기 위해 secure-config.xml에 다음과 같이 설정을 합니다.

objectManagerSecurity, 즉 businessAccessDecisionManager에 의해 사용된 Access Decison Manager는, 요청을 한 사용자가, 요청될 오브젝트에 조작을 실행할 수 있는지 아닌지를 판단합니다. 조작 허용의 판단 여부는 아래 그림에서 볼 수 있듯이 Voter들에 의해서 결정됩니다.


<bean id="businessAccessDecisionManager" class="org.springframework.security.vote.UnanimousBased">
        <property name="allowIfAllAbstainDecisions" value="true"/>
        <property name="decisionVoters">
            <list>
                <ref local="roleVoter"/>
                <ref local="aclObjectReadVoter"/>
                <ref local="aclObjectWriteVoter"/>
                <ref local="aclObjectDeleteVoter"/>
                <ref local="aclObjectAdminVoter"/>
            </list>
        </property>
</bean>


Voter의 상위 타입인 AccessDecisionVoter 인터페이스를 살펴보면 정적인 상수 필드를 vote메소드를 통해서 최종 값으로 리턴하고 있습니다. 정적인 상수 필드는 ACCESS_ABSTAIN, ACCESS_DENIED, ACCESS_GRANTED의 3개가 기본으로 준비되어 있습니다.

public interface AccessDecisionVoter {

    int ACCESS_GRANTED = 1;
    int ACCESS_ABSTAIN = 0;
    int ACCESS_DENIED = -1;
    boolean supports (ConfigAttribute attribute);

    boolean supports (Class clazz);
    int vote (Authentication authentication, Object object, ConfigAttributeDefinition config);
}

각각의 votwer는, 그것을 동작하게 만드는 퍼미션 리스트를 가지고 있습니다  그리고 ACL-s를 조회하기 위해서 사용되는 aclService를 참조합니다.

각각의 voter는 퍼미션 리스트를 가지고 있고, ACL을 조회하기 위해서 사용되는 aclService를 참조합니다. 이 퍼미션의 정의는 aclService에 의해 되돌려진 데이터에 기초를 두고 있습니다. 아래 코드에서 보는 바와 같이 voter는 하나의 상수값을 리턴하고, 현재 요청에 대해 접근을 가능하게 할 것인지 말 것인지 그 의견을 이야기합니다.

org.springframework.security.vote.AclEntryVoter 클래스의 vote 메소드.

// Obtain the OID applicable to the domain object
ObjectIdentity objectIdentity = objectIdentityRetrievalStrategy.getObjectIdentity(domainObject);

// Obtain the SIDs applicable to the principal
Sid[] sids = sidRetrievalStrategy.getSids(authentication);

Acl acl;

try {
   // Lookup only ACLs for SIDs we're interested in
 acl = aclService.readAclById(objectIdentity, sids);
} catch (NotFoundException nfe) {
if (logger.isDebugEnabled()) {
   logger.debug("Voting to deny access - no ACLs apply for this principal");
}

return AccessDecisionVoter.ACCESS_DENIED;
}

try {
if (acl.isGranted(requirePermission, sids, false)) {
 if (logger.isDebugEnabled()) {
    logger.debug("Voting to grant access");
 }

 return AccessDecisionVoter.ACCESS_GRANTED;
} else {
 if (logger.isDebugEnabled()) {
    logger.debug(
     "Voting to deny access - ACLs returned, but insufficient permissions for this principal");
 }

 return AccessDecisionVoter.ACCESS_DENIED;
}
} catch (NotFoundException nfe) {
if (logger.isDebugEnabled()) {
   logger.debug("Voting to deny access - no ACLs apply for this principal");
}

  return AccessDecisionVoter.ACCESS_DENIED;
}



이 리턴된 결과로 접근 및 부정 접근에 관한 최종적인 결정은, voter들의 리턴된 값을 가지고 있는 decision manager에 의해 다시한번 결정권한을 이관합니다. 여기에서 사용된 빈은 org.springframework.security.vote.AbstractAccessDesicionManager을 상속한 org.springframework.security.vote.UnanimousBased빈 입니다. AbstractAccessDesicionManager 빈은 다시한번 AccessDecisionManager을 상속하므로 이 빈은 모든 voter들에게 접근이 허용되었을 때 접근 가능의 유무를 판단하는 로직이 담겨있습니다.

allowIfAllAbstainDecisions를 true로 설정되어 있으면 checkAllowIfAllAbstainDecisions() 메소드에 의해 최종 접근에 대한 접근을 허용합니다. 만약 voter들이 모두 기권을 선언할지라도 이 프로퍼티의 설정이 true로 설정되어 있으면 그것들의 의견을 무시하고 통과 시켜버립니다.

각각의 AclEntryVoter는 생성자에 중요한 세개의 파라미터를 받습니다.
첫번째 파라미터는 aclService, 두번째는 보안된 액션의 상수 값, 세번째는 액션을 실행할 때 참조하는 퍼미션의 리스트입니다.

processDomainObjectClass는 Spring Security Domain ACL에서 보안을 통제하고 싶은 도메인 오브젝트를 지정하는 프로퍼티입니다. 위의 클래스 다이어그램에서 볼 수 있듯이 보안과 관련된 모든 도메인 오브젝트는 AbstractSecureObject를 상속하고 있으므로 com.denksoft.springstarter.entity.AbstractSecureObject의 풀이름을 넣어줍니다.

<bean id="aclObjectReadVoter" class="org.springframework.security.vote.AclEntryVoter">
        <constructor-arg ref="aclService"/>
        <constructor-arg value="ACL_OBJECT_READ"/>
        <constructor-arg>
            <list>
                <ref local="administrationPermission"/>
                <ref local="readPermission"/>
            </list>
        </constructor-arg>
        <property name="processDomainObjectClass" value="com.denksoft.springstarter.entity.AbstractSecureObject"/>
</bean>

우리의 애플리케이션에는 위의 aclObjectReadVoter와 같은 형태로 생긴 3개의 decision voter들이 준비되어 있습니다. 만약 오브젝트를 쓰기위한 퍼미션을 설절하려면 ACL_OBJECT_READ의 값을 ACL_OBJECT_WRITE로 변경해 주고 생성자에 writePermission과 administrationPermission으로 변경해 줍니다.

일반적인 role-based voter의 roleVoter는 다른 voter들과 명확하게 분리되어 있습니다.현재 principal의 인증된 권한을 증명하기 위해 ROLE_*된 설정을 읽어들입니다.

<bean id="roleVoter" class="org.springframework.security.vote.RoleVoter"/>

public class RoleVoter implements AccessDecisionVoter {
       private String rolePrefix = "ROLE_";
       ....생략....
}


우리는 애플리케이션에 걸쳐 사용될 ACL 퍼미션(admin,read,write,delete)을 빈으로 등록하고 그것을 사용하는 AclEntryVoter의 생성자에 넘겨줄 수 있습니다. org.springframework.security.acls.Permission에 상수로 정의되어 있는 스태틱 필드의 Permission을, 이 값을 조회하는 FieldretrievingFactoryBean에 등록하여 사용합니다.

<bean id="administrationPermission" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
        <property name="staticField" value="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION"/>
</bean>

<bean id="readPermission" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
 <property name="staticField" value="org.springframework.security.acls.domain.BasePermission.READ"/>
</bean>

<bean id="writePermission" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
 <property name="staticField" value="org.springframework.security.acls.domain.BasePermission.WRITE"/>
</bean>

<bean id="deletePermission" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
 <property name="staticField" value="org.springframework.security.acls.domain.BasePermission.DELETE"/>
</bean>

AclService는, ACL-secured instance를 보조해 주는 역할을 합니다. MutableAclService는, 데이터베이스의 정보를 작성하고, 축적하기 위해서 사용됩니다.  Spring security의 디폴트 implementation 인 JdbcMutableAclService는 기본적인 일련의 쿼리를 포함하고 있지만, 이 쿼리는 PostgreSQL을 위한 쿼리가 아니고 세터인젝션이나 게터인젝션이 마련되어 있지 않습니다. 그래서 MutableAclService를 구현한 별도의 사용자 정의 서비스를 작성합니다. PostgreSqlJdbcMutableAclService는 SpringStarter application에서 찾아 볼 수 있습니다.

<bean id="aclService" class="com.denksoft.springstarter.util.security.PostgresqlJdbcMutableAclService">
        <constructor-arg ref="dataSource"/>
        <constructor-arg ref="lookupStrategy"/>
        <constructor-arg ref="aclCache"/>
</bean>

추가로 데이터 소스(data source) 프로퍼티를 설정하기 위해서, hibernate.properties파일의 내용을 수정하여 등록하여 줍니다.

<context:property-placeholder location="WEB-INF/classes/hibernate.properties"/>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
          p:driverClassName="${hibernate.connection.driver_class}"
          p:url="${hibernate.connection.url}"
          p:username="${hibernate.connection.username}"
          p:password="${hibernate.connection.password}"/>

lookupStrategy 는 데이터베이스로부터 직접 ACL의 리스트를 조회합니다. 우리는 스프링 시큐리티의 BasicLookupStrategy를 사용합니다. 또한 dataSource 와 aclCache그리고 ConsoleAuditLogger를 생성자에 등록해 줍니다. 또 하나 생성자에 넘겨주어야 할 것은 aclAuthorizationStrategy입니다, 이것은 ACL 리스트로부터 다루고자 할 admin 메소드를 콜하는 것을 허용할지 말지 결정합니다.  이 같은 경우 수정할 수 있는 권한을 가진 ROLE_ADMIN 으로 메소드 호출을 위한 허가를 받을 수 있습니다.

현재까지 설명한 모든 설정은 securityAuthorizationContext. Xml에 기록합니다. aclAuthorizationStrategy 는 3개의 필요한 파라미터를 가지고 있습니다 첫째 파라미터는 소유권을 변경에 필요한 권한이며, 두번째 파라미터는 감사 상세를 수정에 필요한 권한이며 마지막 파라미터는 다른 ACL와 ACE의 상세를 변경하는 것을 필요로 하는 권한입니다.(기본적으로 모두 ROLE_ADMIN으로 설정했습니다.) securityAuthorizationContext에 생성자의 인수로 받는 GrantedAuthority[] auths는 다음과 같이 각각의 GrantedAuthority필드로 구성되어 있습니다.

public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy {

    private GrantedAuthority gaGeneralChanges; // 소유권 변경에 필요한 권한
    private GrantedAuthority gaModifyAuditing; // 감사 상세를 수정에 필요한 권한
    private GrantedAuthority gaTakeOwnership; //다른 ACL과 ACE의 상세를 변경하는 권한
    private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();

    public AclAuthorizationStrategyImpl(GrantedAuthority[] auths) {
        Assert.notEmpty(auths, "GrantedAuthority[] with three elements required");
        Assert.isTrue(auths.length == 3, "GrantedAuthority[] with three elements required");
        this.gaTakeOwnership = auths[0];
        this.gaModifyAuditing = auths[1];
        this.gaGeneralChanges = auths[2];
    }
    ..생략..
}



 

<bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
        <constructor-arg ref="dataSource"/>
        <constructor-arg ref="aclCache"/>
        <constructor-arg ref="aclAuthorizationStrategy"/>
        <constructor-arg>
            <bean class="org.springframework.security.acls.domain.ConsoleAuditLogger"/>
        </constructor-arg>
</bean>
<bean id="aclCache" class="org.springframework.security.acls.jdbc.EhCacheBasedAclCache">
        <constructor-arg>
            <bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
                <property name="cacheManager">
                    <bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
                </property>
                <property name="cacheName" value="aclCache"/>
            </bean>
        </constructor-arg>
</bean>
<bean id="aclAuthorizationStrategy" class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
        <constructor-arg>
            <list>
                <bean class="org.springframework.security.GrantedAuthorityImpl">
                    <constructor-arg value="ROLE_ADMIN"/>
                </bean>
                <bean class="org.springframework.security.GrantedAuthorityImpl">
                    <constructor-arg value="ROLE_ADMIN"/>
                </bean>
                <bean class="org.springframework.security.GrantedAuthorityImpl">
                    <constructor-arg value="ROLE_ADMIN"/>
                </bean>
            </list>
        </constructor-arg>
</bean>

이제 위에서 설정한것들을 사용하는 좋은 예제를 살펴보기로 합니다. 이것을 고려하기 위해 두가지 방법을 생각해 볼 수 있습니다. 하나는 서비스 메소드가 단일 오브젝트를 리턴하는 것과 서비스 메소드가 콜랙션 오브젝트를 리턴하는 경우입니다. 단일 오브젝트를 리턴하는 간단한 공통의 서비스 레이어 메소드부터 이해를 해 보기로 하죠.

Customer getCustomer(long id);

이 경우 주어진 고객을 읽기 위한 퍼미션을 가지고 있어야 있는 principal이 필요합니다. 이것을 체크하기 위해서 Customer instance는 AclEntryAfterInvocationProvider를 통과하고 조회힙니다. 만약 인증된 오브젝트가 그것을 읽는 권한을 가지고 있지 않으면 프로바이더는 AccessDeniesException을 던집니다.
이것은 제일 처음 설정한 objectManagerSecurity 빈에 afterInvocationManager로 등록되어 있습니다.

<bean id="objectManagerSecurity"
  class="org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor" autowire="byType">       
 <property name="accessDecisionManager" ref="businessAccessDecisionManager"/>
 <property name="afterInvocationManager" ref="afterInvocationManager"/>       
 <property name="objectDefinitionSource" ref="objectDefinitionSource"/>
</bean>

<bean id="afterInvocationManager"
  class="org.springframework.security.afterinvocation.AfterInvocationProviderManager">
<property name="providers">
    <list>
 <ref local="afterAclRead"/>
 <ref local="afterAclCollectionRead"/>
    </list>
</property>
</bean>




다음은 AFTER_ACL_READ액션의 설정입니다.(AFTER_ACL_READ의 상수값은 AclEntryAfterInvocationProvider의 생성자부분에서 발견되어 집니다.)

<bean id="afterAclRead" class="org.springframework.security.afterinvocation.AclEntryAfterInvocationProvider">
        <constructor-arg ref="aclService"/>
        <constructor-arg>
            <list>
                <ref local="administrationPermission"/>
                <ref local="readPermission"/>
            </list>
        </constructor-arg>
</bean>


다음으론 자바의 콜랙션 객체를 취득하는 방법을 보겠습니다. afterAclCollectionRead는 콜랙션을 조회하는데 필요합니다. 그것은 AFTER_ACL_COLLECTION_READ액션을 핸들링합니다. (AFTER_ACL_COLLECTION_READ액션은 AclEntryAfterInvocationCollectionFilteringProvider에 생성자에서 발견할 수 있습니다.)

public List getCustomers();

<bean id="afterAclCollectionRead" class="org.springframework.security.afterinvocation.AclEntryAfterInvocationCollectionFilteringProvider">
        <constructor-arg ref="aclService"/>
        <constructor-arg>
            <list>
                <ref local="administrationPermission"/>
                <ref local="readPermission"/>
            </list>
        </constructor-arg>
</bean>


Hierarchical roles and custom user details

SpringStarter어플리케이션(application)에 있어서, 우리들은 계층적인 Role을 사용합니다 ([4]).  역할 히이라키(hierarchy)는, 특정한 Role을 갖고 있는 사용자가 중첩된 인가처리를 하기 위해서 정해진 role보다 더 낮은 모든 행동을 수행하기 위한 인가 매커니즘을 갖고 있습니다. 이경우 더 높은 role은 더 낮은 role에 대해 덮어씌우기 때문에 낮은 Role에 대해서는 자원을 허용하는 규칙을 담고 있습니다.

계층적인 역할(role)을 사용하는 것에 더해서, 우리들은, 하이버네이트를 사용하여 데이터를 조회하고 콘트롤합니다. 그리고 각각의 Role은 그것에 부속되어 있었던 특정한 데이터 타입을 가지게 됩니다.

첫번째로, 스프링 시큐리티의로부터 UserDetailsWrapper를 확장함으로써, 사용자와 관련된 추가적인 정보를 포함하고 있는 CustomUserDetailsWrapper를 정의합니다.

public class CustomUserDetailsWrapper extends UserDetailsWrapper {

        private Object userInfo;

        public CustomUserDetailsWrapper(UserDetails userDetails, RoleHierarchy roleHierarchy) {
            super(userDetails, roleHierarchy);
        }

        public CustomUserDetailsWrapper(UserDetails userDetails, RoleHierarchy roleHierarchy, Object userInfo) {
            super(userDetails, roleHierarchy);
            this.userInfo = userInfo;
        }

        public Object getUserInfo() {
            return userInfo;
        }
}



우리는 방금 생성한 CustomUserDetailsWrapper를 컨트롤하기 위해 커스텀 사용자 서비스 래퍼가 필요합니다. (CustomUserDetailsServiceWrapper) 이것은 스프링시큐리티에 준비되어 있는 기본의 UserDetailsService를 구현합니다. 기본 UserDetailsService는 인증과 인가의 상세 작업을 자동적으로 처리하고 있습니다. 동시에 데이터베이스 조작부분은 하이버네이트를 사용했습니다.

public class CustomUserDetailsServiceWrapper extends HibernateDaoSupport implements UserDetailsService {

 private UserDetailsService userDetailsService = null;
 private RoleHierarchy roleHierarchy = null;
 private Class[] userInfoObjectTypes;

 public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
     this.roleHierarchy = roleHierarchy;
 }

 public void setUserDetailsService(UserDetailsService userDetailsService) {
    this.userDetailsService = userDetailsService;
 }

 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
     UserDetails userDetails = userDetailsService.loadUserByUsername(username);

     for(Class clazz: userInfoObjectTypes) {
  DetachedCriteria query = DetachedCriteria.forClass(clazz).add(Restrictions.eq("user.username", username));
  try {
      Object info = getHibernateTemplate().findByCriteria(query).get(0);
      return new CustomUserDetailsWrapper(userDetails, roleHierarchy, info);
  } catch (IndexOutOfBoundsException ex) {
      //do nothing

  }
     }
     return new CustomUserDetailsWrapper(userDetails, roleHierarchy);
 }

 public UserDetailsService getWrappedUserDetailsService() {
     return userDetailsService;
 }

 public void setUserInfoObjectTypes(Class[] userInfoObjectTypes) {
     this.userInfoObjectTypes = userInfoObjectTypes;
 }
}



사용자 상세 서비스는 security-config.xml파일에 authentication provider 에 연결됩니다.

<bean id="userDetailsServiceWrapper"
          class="com.denksoft.springstarter.util.security.CustomUserDetailsServiceWrapper"
          p:roleHierarchy-ref="roleHierarchy"
          p:userDetailsService-ref="usersDetailServiceJdbc"
          p:sessionFactory-ref="sessionFactory">
        <property name="userInfoObjectTypes" >
            <list>
                <value>com.denksoft.springstarter.entity.Clerk
                <value>com.denksoft.springstarter.entity.Customer
            </list>
        </property>
</bean>

<bean id="roleHierarchy"
          class="org.springframework.security.userdetails.hierarchicalroles.RoleHierarchyImpl">
        <property name="hierarchy">
            <value>
                ROLE_CLERK > ROLE_CUSTOMER
            </value>
        </property>
</bean>

<bean id="usersDetailServiceJdbc"
          class="org.springframework.security.userdetails.jdbc.JdbcDaoImpl"
          p:dataSource-ref="dataSource"
          p:authoritiesByUsernameQuery=" select users_username as username, authority from users_authorities inner join authorities on authorities_id=id where users_username = ? "/>

<bean id="hibernateProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"
          p:location="WEB-INF/classes/hibernate.properties"/>

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
          p:hibernateProperties-ref="hibernateProperties"
          p:configLocation="classpath:hibernate.cfg.xml"/>

userDetailsServiceWrapper를 정의하기 위해 우리는 몇가지 옵셔으로 추가된 프로퍼티에 대해서 알아둘 필요가 있습니다.

roleHierarchy - 이 경우 ROLE_CLERK가 ROLE_CUSTOMER를 포함합니다.
userDetailsService – 기본 사용자 상세의 jdbc implementation. 우리의 테이블이 구조가 기본과 약간 다르므로, 우리들은 authoritiesByUsernameQuery를 재정의할 필요가 있습니다.
userInfoObjectTypes - 인증된 users에 결합되고 싶은 특별한 정보를 포함하는 entity들의 리스트
sessionFactory - 이 session properties는 PropertiesFactoryBean으로부터 로딩됩니다

hibernate.cfg.xml 파일은 session factory를 사용한 entity class를 포함합니다.

<?xml version='1.0' encoding='UTF-8'?>
    <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
     "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
 <session-factory>
     <mapping class="com.denksoft.springstarter.entity.Clerk" />
     <mapping class="com.denksoft.springstarter.entity.security.User" />
     <mapping class="com.denksoft.springstarter.entity.security.Authority" />
     <mapping class="com.denksoft.springstarter.entity.BankAccount" />
     <mapping class="com.denksoft.springstarter.entity.AbstractSecureObject" />
     <mapping class="com.denksoft.springstarter.entity.BankAccountOperation" />
     <mapping class="com.denksoft.springstarter.entity.Customer" />
 </session-factory>
</hibernate-configuration>



ACL management

우리는 컨트롤할 오브젝트를 위해 주어진 유저에 퍼미션을 쉽게 관리하기를 원합니다.ACL_Entry 테이블에 데이터를 추가할 필요가 있습니다. 일일히 손으로 관리하는 것을 피하기 위해서 aclSecurityUtil  bean을 프록시 인터페이스로 작성하고, 이 프로세스에 의헤서 퍼미션 관리를 자동화 할 수 있습니다.

aclSecurityUtil과 관련된 정의는 다음과 같습니다.

<bean id="aclSecurityUtil" class="org.springframework.aop.framework.ProxyFactoryBean">
        <qualifier value="aclSecurity"/>
        <property name="proxyInterfaces" value="com.denksoft.springstarter.util.security.AclSecurityUtil"/>
        <property name="interceptorNames">
            <list>
                <idref local="transactionInterceptor"/>
                <idref local="aclSecurityUtilTarget"/>
            </list>
        </property>
</bean>
<bean id="aclSecurityUtilTarget" class="com.denksoft.springstarter.util.security.AclSecurityUtilImpl" p:mutableAclService-ref="aclService"/>

<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"
          p:transactionManager-ref="jdbcTransactionManager">
        <property name="transactionAttributeSource">
            <value>
                com.denksoft.springstarter.util.security.AclSecurityUtil.deletePermission=PROPAGATION_REQUIRED
                com.denksoft.springstarter.util.security.AclSecurityUtil.addPermission=PROPAGATION_REQUIRED
            </value>
        </property>
</bean>
<bean id="jdbcTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"/>



aclSecurityUtil bean은 com.denksoft.springstarter.util.security.AclSecurityUtil 인터페이스 구현한 AOP 프록시입니다. 이것은 두가지 인터셉터를 가지고 있습니다.:

transactionInterceptor :  transactionInterceptor는 spring security가 acl의 리스트를 조회하기 위해서 JDBC를 사용합니다.
aclSecurityUtilTarget : aclSecurityUtilTarget은 프록시 객체입니다.

AclSecurityUtil 인터페이스는 퍼미션을 추가,삭제할 수 있는 기본 메소드를 제공하고 있습니다. (addPermission,deletePermission)

public interface AclSecurityUtil {

        public void addPermission(AbstractSecureObject securedObject, Permission permission, Class clazz);

        public void addPermission(AbstractSecureObject securedObject, Sid recipient, Permission permission, Class clazz);

        public void deletePermission(AbstractSecureObject securedObject, Sid recipient, Permission permission);

    }



 

public class AclSecurityUtilImpl implements AclSecurityUtil {
        private static Log logger = LogFactory.getLog(AclSecurityUtil.class);
        private MutableAclService mutableAclService;

        public void setMutableAclService(MutableAclService mutableAclService) {
            this.mutableAclService = mutableAclService;
        }

         public void addPermission(AbstractSecureObject secureObject, Permission permission, Class clazz) {
            addPermission(secureObject, new PrincipalSid(getUsername()), permission, clazz);
        }

 
        public void addPermission(AbstractSecureObject securedObject, Sid recipient, Permission permission, Class clazz) {
            MutableAcl acl;
            ObjectIdentity oid = new ObjectIdentityImpl(clazz.getCanonicalName(), securedObject.getId());

            try {
                acl = (MutableAcl) mutableAclService.readAclById(oid);
            } catch (NotFoundException nfe) {
                acl = mutableAclService.createAcl(oid);
            }

            acl.insertAce(acl.getEntries().length, permission, recipient, true);
            mutableAclService.updateAcl(acl);

            if (logger.isDebugEnabled()) {
                logger.debug("Added permission " + permission + " for Sid " + recipient + " securedObject " + securedObject);
            }
        }

 

        public void deletePermission(AbstractSecureObject securedObject, Sid recipient, Permission permission, Class clazz) {
            ObjectIdentity oid = new ObjectIdentityImpl(clazz.getCanonicalName(), securedObject.getId());
            MutableAcl acl = (MutableAcl) mutableAclService.readAclById(oid);

            // Remove all permissions associated with this particular recipient (string equality used to keep things simple)
            AccessControlEntry[] entries = acl.getEntries();

            for (int i = 0; i < entries.length; i++) {
                if (entries[i].getSid().equals(recipient) && entries[i].getPermission().equals(permission)) {
                    acl.deleteAce(i);
                }
            }

            mutableAclService.updateAcl(acl);

            if (logger.isDebugEnabled()) {
                logger.debug("Deleted securedObject " + securedObject + " ACL permissions for recipient " + recipient);
            }
        }

 

        protected String getUsername() {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if (auth.getPrincipal() instanceof UserDetails) {
                return ((UserDetails) auth.getPrincipal()).getUsername();
            } else {
                return auth.getPrincipal().toString();
            }
        }
    }



addPermission(AbstractSecureObject securedObject, Sid recipient, Permission permission, Class clazz) 함수는 securedObject의 ACL을 조회하거나 생성하고 현재 인증된 user에 퍼미션을 추가하는 메소드를 호출합니다. deletePermission(AbstractSecureObject securedObject, 시도 수령자, 허가 허가, 클래스clazz) 메소드는, 퍼미션을 제거합니다.  이곳에 인자로 넘겨준 sid는 principal,sid,GrantedAuthoritySid가 될 수 있습니다.

 우리들은 어떻게 이 API를 사용할 수 있는지 그 방법을 알아야 합니다. 우리들이 ACL조작을 수행 (코드에 걸쳐 산란하고, 카피또는 붙여넣기조작을 가지고 있는 것과 대비되게))하는 정확한 포인트를 원하기 때문에 퍼미션 메니지먼트를 관리하는 SecurityService인터페이스를 정의합니다. 이 구현체는 aclSecurityUtil을 autowired annotation을 써서 묶어서 활용합니다.

public class SecurityServiceImpl implements SecurityService {
   @Autowired
   @Qualifier("aclSecurity")
   private AclSecurityUtil aclSecurityUtil;
   public void setCustomerPermissions(Customer customer) {
      Sid sid = new PrincipalSid(customer.getUser().getUsername());
      Sid sidAdmin = new GrantedAuthoritySid("ROLE_CLERK");
      aclSecurityUtil.addPermission(customer, sid, BasePermission.ADMINISTRATION, Customer.class);
      aclSecurityUtil.addPermission(customer, sidAdmin, BasePermission.ADMINISTRATION, Customer.class);
   }
}


이 코드의 의미는, ROLE_CLERK와 함께 등록된 customer를 위해 administration 퍼미션을 추가합니다.
다음으로, 애플리케이션 레벨의 서비스 인터페이스인 CustomerService에 대해서 살펴봅니다. 그것을 보안하기 위해서 우리는 applicationContext.xml파일안에 objectManagerSecurity를 주입해야합니다. 또, 우리들은 주변의 AOP프록시를 구축하기 위해서  서비스를 구축하기 위해서 ProxyFactoryBean클래스를 사용합니다

<bean id="customerServiceTarget" class="com.denksoft.springstarter.service.impl.CustomerServiceImpl"/>
<bean id="customerService" class="org.springframework.aop.framework.ProxyFactoryBean">
 <qualifier value="customerService"/>
 <property name="proxyInterfaces" value="com.denksoft.springstarter.service.CustomerService"/>
 <property name="interceptorNames">
     <list>
  <idref bean="objectManagerSecurity"/>
  <idref local="customerServiceTarget"/>
     </list>
 </property>
</bean>

이 인터페이스는 자바 5의 어노테이션을 활용했습니다.

public interface CustomerService {
        @Secured({"ROLE_CUSTOMER","AFTER_ACL_READ"})
        public Customer getCustomer(long id);
        @Secured({"ROLE_CUSTOMER","ACL_OBJECT_ADMIN"})
        public void modifyBankAccount(BankAccount bankAccount);
        @Secured({"ROLE_CUSTOMER","AFTER_ACL_COLLECTION_READ"})
        public Collection getCustomerBankAccounts();
}

예를들어 getCustomer를 호출했을 때 그것을 호출한 principal은 만드시 ROLE_CUSTOMER 권한을 가지고 있어야 합니다. 일단 오브젝트가 조회되면, objectManagerSecurity가 해당 흐름을 가로채고, 인증된 유저가 리턴된 값을 갖기 전에 afterInvocationManager로 흐름을 가로챕니다.만약 그 때 사용자가 필요한 Role을 가지고 있으면, 오브젝트는 요청한 사용자에게 필요한 상태를 리턴할 것입니다. 이와 반대로 인증된 사용자가 아니라면 예외를 던지게 됩니다.

만약 사용자가, 은행예금계좌를 수정한다면 그가 BankAccount instance를 위한 ROLE_ADMIN 권한과 ROLE_CUSTOMER를 가지고 있지 않으면 안됩니다.

AFTER_ACL_READ, AFTER_ACL_COLLECTION_READ 와 ACL_OBJECT_READ, ACL_OBJECT_ADMIN은 각각 다른 의미를 가지고 있습니다.

1.     AFTER_ACL_READ, AFTER_ACL_COLLECTION_READ : 보안된 오브젝트를 되돌리는 함수를 호출하기 위해서 사용됩니다.
2.    ACL_OBJECT_READ, ACL_OBJECT_ADMIN : 보안된 object type의 파라미터를 가지고 있는 함수를 호출하기 위해사용됩니다.

작성자 결론

효과적인 사용을 위해 스프링 시큐리티를 셋업하는 것은 절대로 쉬운 수준의 작업이 아닙니다. 확실히, 상대적으로 간단한 어플리케이션(application)을 위해서, 이것들을 손으로 설정하는 것을 생각해보면 상당히 무거운 작업임이 틀림없습니다. Spring security를 사용함에 있어서 모든 버그가 확실히 알려진것도 없고 문서가 턱없이 부족한 상태지만, 그 속엔 매우 매력적인 옵션들이 많이 있습니다. 스프링본래의 매력과 같은 여러가지 옵션을 자유자재로 취할 수 있다는 것에 첫번째 이점을 둘 수 있고. 그 수많은 옵션중에서 적절히 자신의 애플리케이션에 필요한 것을 추가하고, 삭제해 나가기 때문에 매우 유연하고 차 후에 대비한 애자일 스러운 작업임이 틀림없습니다. 또한 차 후 복잡하게 변하게 될 보안 시스템의 미래에 대해서 여러가지 피치못할 치명적인 오류에 대비하기 위해 스프링 시큐리티를 채택하는 것은 맹우 바람직한 모습이라고 할 수 있습니다.

우리는 SpringStarter 애플리케이션이 앞으로 개선해 나갈 수 있도록 당신의 피드백을 받을 것이고, 이 애플리케이션을 더 쉽게 이해시키기 위해 지속적인 노력을 할 것입니다. 감사합니다.

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
블로그코리아에 블UP하기
  1. 2010.08.18 09:38 [수정/삭제] [답글]

    비밀댓글입니다

  2. Favicon of http://starplatina.tistory.com BlogIcon June

    2010.08.26 22:08 신고 [수정/삭제] [답글]

    Postgre SQL 사용시에..
    샘플 파일은 너무 버전이 오래되서... ;;
    시큐리티 문서에 있는 디비 스키마 대로 생성하고 문서 밑에 있는
    classIdentityQuery, sidIdentityQuery 만 JdbcMutableAclService 빈에다가 set 해주면
    잘 동작 합니다. ㅎ

    • Favicon of http://moova.tistory.com BlogIcon MOOVA 무바㏇

      2010.08.27 09:27 신고 [수정/삭제]

      원문 번역이라 버전이 낮을 수도 있었겠네요. 중요한것은 버전이 아니라 원리겠지요?
      제가 학생들한테 하고 싶은 말은, 먼저 원리를 깨우치는게 중요하지 않을까 합니다.
      (Domain ACL이 현업에서 어떻게 , 어디서, 무엇때문에 쓰여야 하는지부터 생각하는게 좋을 듯 하구요, 스터디뿐만이 아닌 활용가치에 대해서도 생각하면서 공부하는게 좋을 듯합니다. )

      근데. 뉘신지??

  3. Favicon of http://starplatina.tistory.com BlogIcon 김제준

    2010.08.27 09:20 신고 [수정/삭제] [답글]

    검색해서 타고 들어왔습니다. ^^

댓글을 남겨주세요.

근무환경

Posted at 2010.08.10 20:57 // in OpenSource // by MOOVA 무바㏇
2010 해외 IT 잡 그래프

 

일회용 개발자의 삶 ( 아이티 클라우드 중 )

 

2010:08:10 20:30:00

우릴 어제먹은 잼처럼 버려버리지!

2010:08:10 20:30:19

어제먹은 쨈! 그들에겐 우리가 쨈이야!
2010:08:10 20:30:41

사실말이야 그건좀 알이 안되는것 같다.
2010:08:10 20:30:51

잼은 몇년 먹자나,..


미국에서 자바 강사로 활동중인 S.Lim군과 이런저런 IT에 대한 이야길 나누다 한국의 열악한 근무환경에 대한 이야기가 나와서 몇 자 적어본다. 이 친구는 웹일을 해 오던 오래전부터 이클립스나, 스프링, 아파치, 구루,누크등 프로그래밍언어와 관련된 오픈소스를 같이 공부하던 친구였는데, 어느 날 한국땅에선 앞날이 보이지 않는다는 예견으로 영어공부하고 짐싸들고 부랴부랴 떠난 친구다.

지금 이 넘 말로는 기회가 되면 한국에서 나와 해외에서 활동하는 게 득이 되면 됐지 손해 볼 것은 없다고 한다. 해외 아이티도 경제난국의 흐름을 타고 있는지라 그동안 침체분위기 였지만 앞으로 밝은 전망때문에 여기저기 손길을 원하는 곳이 많아지고 있고, 특히 클라우드관련 업에서 일거리가 무궁무진하게 창출되고 있다고 하니, 국내와는 사뭇 다른 풍토다. 이 즈음 되면 한번 해외 진출의 기회를 노려볼 만도 하다. 일하는 문화도 일할 때 일하고 놀 때는 노는 위주라서 퇴근시간 눈치 볼 일도 없이 자신의 할 일만 정확하게 하면 된다는 자연스런 풍토가 자리잡혀 있다고 한다. 딱 내 스타일이다. 물론 아이티라는 환경이 세계 어느나라 막론하고 힘든 일인것은 사실이지만 근무환경만 보다라도 국내의 시설과는 많이 다르다는 것을 확실히 느끼게 해주는 대목이기도 하다. 

뭐랄까.. 그들과 비교해보면 이 씁쓸한 기분은....

2010:08:10 20:01:24


동시에 아는 후배들, 동료들과 메신저로 이야기를 나누면 이들에게 긍정과 열정은 이미 없어진 지 오래다. 해외분위기와 한국의 아이티 분위기는 달라도 너무 다르다. 대부분은 아직도 야근으로 시달리고 있고 밝은 미래는 약속받지 못한 상태에서 불철주야 공부하면서 일을 하고 있다. 야근수당이나 주말수당은 대부분 받지 못하는 상태이고, 직급이 되는 애들은 고객의 무리한 요구때문에 항상 스트래스를 받으면서 지낸다. 이들에게 합당한 추가비용에 대한 댓가는 단 한번도 찾아볼 수 없었다. 프로젝트를 시작하게 되면 응당 추가해야 할 기능목록을 계약서상에 기입해서 착수하는 게 당연지사지만 그렇게 하는 곳은 난 한번도 본적이 없었고, 워낙 요구가 다양한 한국인들이다 보니 제일 중요한것은 "빨리빨리"가 목적이 아니던가? 그나마 다행인것은 한 줄의 빛을 잃지 않으려고 오늘도 밤늦게까지 일하면서 공부하는 후배들이 있다. 내년이나 내 내년에 꼭 해외에 나갈 꿈을 기르고자 그렇게들 열심이다. 이들에게는 하고자 하는 의욕이 명확해서 나 자신도 다시한번 반성을 해 줄 수 있는 계기가 되곤한다.

이런 상황들이 비일비재하니 자포자기 상태가 아닌 또 하나의 결심을 하게 된다. 눈을 감기전에 꼭 한국의 아이티환경을 바꿔보고 싶다. 그전엔 죽지 못하겠다.

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
블로그코리아에 블UP하기
  1. Favicon of http://minimonk.net BlogIcon 구차니

    2010.08.11 21:11 신고 [수정/삭제] [답글]

    다음글에서 쨈이 생각난 이유가 이 미드 때문이군요 ㅋㅋ

댓글을 남겨주세요.

Spring Security - URL 권한을 DB로 관리하기 2.

Posted at 2010.07.18 10:10 // in OpenSource // by MOOVA 무바㏇
필자는 그동안 경험했던 정보나 팁들, 또는 지식을 언제부터인가 온라인보다 오프라인으로 공유하는 것을 즐겨했습니다.
상당수가 프로젝트 보안이 걸려있는 문제이기도 하겠지요, 그 모든 부분을 온라인에 공유할 수 없는 심정, 이루 말할 수 없겠죠?
오픈소스와 관련된 사항도 프로젝트와 연관이 있으면 보안이라는 타이틀이 걸려있습니다. 하지만 공개할 것은 공개하자라는 근본 원칙은 항상 마음속에 간직하고 있습니다.
"버려야 새로운 것을 얻는다." 바로 이 정신. 참 아릅답습니다.~~


그림1. 커스터마이징 할 filterSecurityInterceptor의 인터페이스와 클래스 사용도입니다.


그림2. FilterSecurityInterceptor가 의존하는 두개의 매니저 클래스 ( Authentication Manager, AccessDecision Manager )

FilterSecurityInterceptor는 Spring Security의 중요 필터중 하나입니다. FilterSecurityInterceptor는 AuthenticationManager를 의존하며 User와 관련된 (인증 Authentication) 프로세스를 수행하고, 동시에 수행될 자원에 인가여부를 따지는 AccessDecisionManager를 의존하여 투표를 실시합니다.

<beans:bean id="filterSecurityInterceptor"
class="org.springframework.security.intercept.web.FilterSecurityInterceptor"
autowire="byType">
<custom-filter before="FILTER_SECURITY_INTERCEPTOR" />
<beans:property name="authenticationManager" ref="authenticationManager"></beans:property>
<beans:property name="accessDecisionManager" ref="accessDecisionManager" />
<beans:property name="objectDefinitionSource" >
                    <security:intercept-url pattern="/secure/super/**" access="ROLE_WE_DONT_HAVE"/>
                    <security:intercept-url pattern="/secure/**" access="ROLE_SUPERVISOR,ROLE_TELLER"/>
             </beans:property>
</beans:bean>

FilterSecurityInterceptor의 objectDefinitionSource필드를 커스터마이징 해야합니다. 바로 이 부분이 앞에서 모델링한 테이블과 연동을 해야 할 부분입니다. 위의 objectDefinitionSource를 다음과 같이 바꾸어 줍니다.

<beans:property name="objectDefinitionSource"  ref="coolInvocationDefinitionSource"/>

objectDefinitionsource에 reference될 bean을 등록해야합니다.

<beans:bean id="coolInvocationDefinitionSource"
class="com.moova.secure.filterInvocation.CoolObjectDefinitionSourceFactoryBean">
<beans:property name="dataSource" ref="springSecurityDataSource" />
<beans:property name="resourceQuery"
value="
                                 SELECT URL.URL_SPRING, R.NAME
FROM ROLE ROLE JOIN URL_ROLE UR ON ROLE.ID = UR.ROLE_ID JOIN PROGRAM ON PROGRAM.ID = UR.PROGRAM_ID JOIN URL_REPOSITORY REPO ON PROGRAM.ID = REPO.ID
        " />
</beans:bean>

FilterSecurityInterceptor의 objectDefinitionSource의 API를 살펴보면 다음과 같이 setter injection으로 되어 있는 것을 볼 수 있습니다.

Spring Security 2.0

Spring Security 3.0
 SecurityMetadataSource obtainSecurityMetadataSource() 
           
 void setObjectDefinitionSource(FilterInvocationSecurityMetadataSource newSource) 
          Deprecated. use setSecurityMetadataSource instead
 void setObserveOncePerRequest(boolean observeOncePerRequest) 
           
 void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) 

Spring Security 2.0 과 3.0 API를 살펴보면 크게 변화된 부분이 바로 이 objectDefinitionSource입니다. 3.0에선 objectDefinitionSource가 deplecated되었습니다. 이것이 securitymetadataSource로 변경이 되어 있네요. 2.0의 objectDefinitionSource의 Type은 FilterInvocationDefinitionSource 이고 3.0의 objectDefinitionSource의 Type은 FilterInvocationSecurityMetadataSourcen 인터페이스입니다.

이를 확인하고 각 버전에 따라 작성해야하는 클래스가 다를 수 있다는 것에 주목해 주십시오. 여기선 그대로 objectDefinitionSource를 사용합니다.

새로 제작할 CoolObjectDefinitionSourceFactoryBean는 FactoryBean을 implements합니다. FactoryBean을 implements 하면 어떤 객체라도 Spring lifecycle안에서 DI할 수 있게 해줍니다. 이는 단순 객체가 아닌 Factory 클래스로써 getObject()와 getObjectType()를 구현해 주기만 하면 새롭게 사용할 클래스를 반환시켜줍니다. 추가로 데이터베이스와 연계가 필요하니  "org.springframework.jdbc.core.support.JdbcDaoSupport"를 상속합니다.

사용할 구조

public class CoolObjectDefinitionSourceFactoryBean extends JdbcDaoSupport implements FactoryBean {
private String resourceQuery;

public boolean isSingleton() {
return true;
}

public Class getObjectType() {
return FilterInvocationDefinitionSource.class;
}

public Object getObject() {
return new DefaultFilterInvocationDefinitionSource(
this.getUrlMatcher(), this.buildRequestMap());
}
}

데이터베이스 연계부분은 MappingSqlQuery을 사용합니다. MappingSqlQuery 사용법은 여기를 참조해 주세요.

private class UrlRepository {
private String url;
private String role;

public UrlRepository(String url, String role) {
this.url = url;
this.role = role;
}

public String getUrl() {
return url;
}

public String getRole() {
return role;
}
}

private class UrlRepositoryMapping extends MappingSqlQuery {
protected UrlRepositoryMapping(DataSource dataSource, String resourceQuery) {
super(dataSource, resourceQuery);
compile();
}

protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
String url = rs.getString(1);
String role = rs.getString(2);
UrlRepository resource = new UrlRepository(url, role);

return resource;
}
}

"com.moova.secure.filterInvocation.CoolFilterInvocationDefinitionSourceFactoryBean"의 원본 파일

더보기




executeResourceMap()메소드에서 URL과 ROLE 관련된 매핑부분을 key와 Value로 조합해야 합니다. 
DefaultFilterInvocationDefinitionSource의 두 번째 인자는 LinkedHashMap입니다. 
당연히 HampMap값을 넘겨주어야 하기 때문에 buildRequestMap에서는 LinkedHashMap를 new해서 반환해 주고 있습니다. 
DefaultFilterInvocationDefinitionSource의 첫 번째 인자는 AntUrlPathMatcher를 new해서 주입하고 있습니다.

스키마를 새로 개정하거나 생성해서 사용해도 무방합니다.
하지만 URL과 Role에 대한 모델링 연관은 그다지 바뀔게 없다고 봅니다.


저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
블로그코리아에 블UP하기

댓글을 남겨주세요.

Spring Security - URL 권한을 DB로 관리하기 1.

Posted at 2010.07.18 10:08 // in OpenSource // by MOOVA 무바㏇
필자는 오픈소스나 자/타 제품을 접할 때 먼저 해당 제품의 생명주기(LifeCycle)을 먼저 파악합니다. 서로 연결된 인터페이스나 외부에 공개된 컴포넌트의 연관관계를 파악하고, 자주 사용하는 기능이나 중요하다고 생각되는 기능이 생명주기에 의존되어 있는지를 파악합니다. 전체 생명주기나 구조적 관점을 늘 중요시 하다보면, 치명적인 이슈를 제외한 세부적인 기술이슈까지도  그때 그때 처리할 수 있으리라 봅니다.
<http auto-config='true'>
    <intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" /> 
    <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
    <intercept-url pattern="/**" access="ROLE_USER" />
    <form-login/>
    <logout logout-success-url="/pages/logout-redirect.jsp"
invalidate-session="true" />
    <remember-me key="moovaSecureRMKey" user-service-ref="userDetailsService" />
</http>


Spring Security에서 url path에 대한 권한 부여는 보통 위와 같이 설정을 해야합니다. (굵게 표기한 부분)
( 권한을 가지고 있는 사용자가 요청한 url 패턴을 기준으로, 해당 자원을 접속할 수 있는지를 따지게 하여 주는 설정입니다. ) 하지만 안타깝게도 보통 실무에서는, url path나 문서 조회권한에 관련된 설정 파일은 따로 관리하지 않습니다. 

User,Member,Group등 중요 인증 및 인가에 관련된 테이블을 데이터베이스로 관리한다면 URL 권한도 데이터베이스로 관리되어야 차 후 유지보수나 확장에도 이점을 얻을 수 있습니다. Spring Security에선 위의 intercept-url설정이 기본기능으로 구성되어 있습니다만, 이것을 데이터베이스로의 관리방법으로 변경해 보도록 할까 합니다.

하고자 하는것은 매우 단순합니다. Spring Security의 중요 Filter중 하나인 FilterSecurityInterceptor의 objectDefinitionSource를 커스터마이징 할 objectDefinitionSource로 바꿔치기하는 것입니다.



1. 테이블 구축


그림1. 실제 실무에서 사용된 인증/인가와 관련된 테이블 ( 축소공개형 ) 



실제 Magpie 오픈소스로 구축중인 테이블 스키마의 축소 일부입니다. 
User와 Role N:N관계, Role과 Program도 N:N관계, Program과 Url_Repository는 1:1관계로 되어 있습니다.
Role과 Url_Repository를 N:N관계로 모델링 해도 무방하지만 보통 URL은 동적인 속성이 강한 부분이니 카테고리로 분류할 수 있는 Program이란 Entity를 따로 생성하여 url port같은 기능을 하도록 설정해 두었습니다.

사용해둘 데이터를 미리 뽑아 보고, 기존 설정에서 사용했던 포멧으로 데이터가 축출 가능한지 확인합니다.


그림 2. 기존 Spring Definition 설정 위치와 비교한 데이터의 위치.

2. 데이터 확인 및 데이터 추출
SELECT URL.URL_SPRING, R.NAME FROM ROLE ROLE JOIN URL_ROLE UR ON ROLE.ID = UR.ROLE_ID JOIN PROGRAM ON PROGRAM.ID = UR.PROGRAM_ID JOIN URL_REPOSITORY REPO ON PROGRAM.ID = REPO.ID RESULT : ROLE_USER | /** ROLE_ANONYMOUSLY | /** ROLE_ADMIN | /admin.jsp ROLE_USER | /login.jsp ROLE_ANONYMOUSLY | /login.jsp ROLE_MANAGER | /manager/index.jsp
"Spring Security URL 권한을 DB로 관리하기"에서 사전 준비해야 할 재료는 모두 준비된 상태입니다. 이제 Spring Security의 일부분을 커스터마이징해야 합니다. ( "org.springframework.security.intercept.web.FilterSecurityInterceptor" )

참고 문서 :  
http://static.springsource.org/spring-security/site/docs/3.0.x/reference/springsecurity.pdf

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'OpenSource' 카테고리의 다른 글

근무환경  (2) 2010.08.10
Spring Security - URL 권한을 DB로 관리하기 2.  (0) 2010.07.18
Spring Security - URL 권한을 DB로 관리하기 1.  (0) 2010.07.18
심심풀이 납품용  (2) 2010.06.05
[Guide] Vaadin Korean Guide (ver01)  (3) 2010.05.11
[Share Document Note] Evernote  (0) 2010.01.24
블로그코리아에 블UP하기

댓글을 남겨주세요.

심심풀이 납품용

Posted at 2010.06.05 00:43 // in OpenSource // by MOOVA 무바㏇

작년 12월 달인가... 어떤 업체에 개인적으로 의뢰받아 납품한 프로젝트 소스다.

SpringMVC로 구현한 소스인데. 워낙 기간이 짧았고 쿼리는  @NamedQuery 를 사용했다.
예전 IBatis로 구현했던 멀티용 dataSource와 JPA의 dataSource를 혼용하고 사용하고 있다.
Transaction도 두개의 DataSource를 사용하고 있으니 중첩 Transaction이 가능한 외부 라이브러리를 사용했다. 중첩 Transaction이 궁금하면 500페이지 되는 그 책 이름이 뭐였더라.. 기억나면 공유해야 하는데..

이런 의뢰건들에서 느꼈던 20대때의 재미는 꽤 재미가 솔솔했다.

문제는 팀프로젝트나 큰 프로젝트 같은 경우 이런 오픈프레임웍은 선호하지 않는다. 역시 SI는 사업이니 돈이 안 되면 안 하는게 좋다는 생각이겠지만..

10년 넘게 CS프로그램으로 끈질기게 물고 늘어진  프로젝트 소스를(참고하라 : 뷰티풀 아키텍처 거미줄 다이어그램 프로젝트 1장과 매우 흡사하니) AS-IS 하나 설명도 못들었고, 자신들이 분석 못한 거 떠넘겨받아 했던 프로젝트. 새로운 플젝의 공통코드로 집어넣고 산재된 Transaction을 하나로 묶는데 성공했던 케이스를 들추어 보면... 어차피 라이센스 지불해도 벤더측에서 AS는 좀 처럼 잘 안되지 않는가?

AS와서 쩔쩔매는걸... 왜 내가 고쳐줘야 하는데? 내가 자기네 벤더사람이야??? 이건 아니자나~~응?

차 후 집어넣었던 메시지 코드라던지 변경된 아키텍처에 의해 처 넣었던 LOG 정보라던지.. 확 바꿔서 AOP로 구현하고 싶었지만.. 놔두자. 내가 뭐 맨날 바꾸러 다니는 사람도 아니니 물흘러가듯 놔두는 거다.

모르는 척 하는 것도 정말 어려운 것이야...

이럴때는 진짜 모른척 해야하는거지. 심지어는 지네들이 분석도 못한 통계쿼리나 해피콜관련해서 50개 이상 테이블에서 쏟아져 나오는 10년 이상된 냄새나는 코드 규약을 다 묶어서 쿼리로 만들어 주지 않았는가?

무늬만 설계했던 사람은 지금은 뭐하고 있는지 모르겠다. 80%이상 완성된거 공짜로 가져가서 지가했다고 떵떵 거렸겠지. 후배들 다 나가 떨어지는데 목숨 부지 하려고 지만 살자고 했던 액션들 기억하면 아직도 역겹지만.. 뭐 그렇게 살게 내버려 두는거지. 뭐 일단 왈가하고..말이지.

근데 웃긴건 난 지금은 SpringMVC는 쓰지 않는다.
Struts2가 더 편의성이 있고 공통 아키텍쳐 구성요소가 여러가지로 더 집약되어 있다~ 이건 확실하다.
써보고 더 편하다는데 무슨 말이 필요할까.

코딩은 작년 12월까지만~~하고 이제 관점을 바꿀 준비를 하다가.. 큰 수술을 받게 되었고.. 또 한 차례 이런 집착의 끈을 놔두게 되었던 근 3개월의 시간이었다. 나이가 되어 이제 진짜배기 컨설팅업체에서 연락이 왔고 그것을 하게 될 즈음 되니...코드에 집착은 이제 살짝 내려놔야지..안카나.

그나저나 이 병치례만 잘 넘겼으면 하는 바램이다. 수빈형님과의 약속도 지켜야 하고, CCC형님과의 원대한 목표도 시작해야 하니 말이다.

이 소스는 내가 코드에 집착을 완전히 버리게 될 즈음 100% 공개할 예정이다.
먼저 열심히 따라와 주었고 뒤에서 열심히 응원해 준 후배들 먼저~

자~ 이 포스팅을 계기로 코딩에 대한 집착은 당분간 내려놓았다. 몇년이 될 지 모르겠다. 좀 더 큰 세상을 바라보기 위해~! Go








저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
블로그코리아에 블UP하기
  1. Favicon of http://minimonk.net BlogIcon 구차니

    2010.06.05 10:03 신고 [수정/삭제] [답글]

    하아.. 전 이런거 언제쯤해볼려나요 ^^;
    항상 밑바닥만 긁고 있어서 위로 가보고 싶은 욕심도 들긴하는데 너무나 멀어보여요 ㅠ.ㅠ

    • Favicon of http://moova.tistory.com BlogIcon moova

      2010.06.07 17:56 신고 [수정/삭제]

      안녕하세요~~ 차니님~
      차니님 블로그에 가보니 충분히 가치있는 학습을 하고 계시던데요. 오히려 제가 배울점이 있다는^^

      전 기술 좀 많이 안다고 사람이 사람 위에 선다던지..아니면 자신을 신격화 한다던지 하는 것을 매우 싫어하는 사람이예요...

      어차피 기술이 발전해봤자..다~ 사람을 위한 발전이지 기술을 위한 발전은 아니니까요.
      기술 하나 쓴다고 자신을 뽐낸다던지 우상화한다던지..여튼.. 제 비위에 안 맞아요.

      Spring..그냥 쓰면 됩니다. 단지 먼저 사용했고 먼저 알고 사용햇을 뿐이죠. 이건 그냥 제 취미이고 누구에게 뽐내려고 올렸던 포스팅은 아니예요~.

      너무 급하게 마음 먹지 말자구요. 천천히 야금 야금 씹어서 소화합시다~^^

댓글을 남겨주세요.

[Guide] Vaadin Korean Guide (ver01)

Posted at 2010.05.11 21:28 // in OpenSource // by MOOVA 무바㏇
쉬엄쉬엄 작성되었습니다. 오타나 수정할 사항이 있다면 paradozz@paran.com 으로 연락주시기 바랍니다. 이 문서는 계속 업데이트 됩니다.:) - 본고는 제가 하는 일과 무관한 오픈 활동입니다.




 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'OpenSource' 카테고리의 다른 글

Spring Security - URL 권한을 DB로 관리하기 1.  (0) 2010.07.18
심심풀이 납품용  (2) 2010.06.05
[Guide] Vaadin Korean Guide (ver01)  (3) 2010.05.11
[Share Document Note] Evernote  (0) 2010.01.24
ASM. Parsing Classes  (0) 2008.07.16
ASM ClassVisitor  (0) 2008.07.10
블로그코리아에 블UP하기
  1. soriwa

    2010.10.15 13:44 신고 [수정/삭제] [답글]

    문서 잘 봤습니다.
    GWT를 살펴보다 Vaadin까지 보게 되었는데 작성하신 문서를 통해 삽질없이(?) 수월하게 접근하게 되었습니다. 앞으로도 계속 관련 포스팅 기대합니다. ^^
    고맙습니다.

  2. Favicon of http://locust.tistory.com BlogIcon locust

    2011.04.07 18:40 [수정/삭제] [답글]

    관리자의 승인을 기다리고 있는 댓글입니다

  3. Favicon of http://ddakker.tistory.com BlogIcon ddakker

    2012.05.11 17:35 [수정/삭제] [답글]

    관리자의 승인을 기다리고 있는 댓글입니다

댓글을 남겨주세요.

[Share Document Note] Evernote

Posted at 2010.01.24 14:59 // in OpenSource // by MOOVA 무바㏇
http://www.evernote.com/

최근에 애용하는 note프로그램입니다. onenote도 버릴 때가 된 것 같군요;;;
웹+클라이언트 동시 저장 기능이 탁월합니다.( 녹봇이나 PC 포멧시. 자료를 백업 받아 놓는 번거로움을 없앨 수 있습니다. )
기존 에디터가 지원하는 그림 첨부,스냅샷기능, 태그관리, 빠른 검색, 링크 기능이 적절히 포함되어 있습니다.
twitter,google note 와 연동도 됩니다.~~ 오호라 띵똥!


 

[Download]
 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'OpenSource' 카테고리의 다른 글

심심풀이 납품용  (2) 2010.06.05
[Guide] Vaadin Korean Guide (ver01)  (3) 2010.05.11
[Share Document Note] Evernote  (0) 2010.01.24
ASM. Parsing Classes  (0) 2008.07.16
ASM ClassVisitor  (0) 2008.07.10
Class Structure  (0) 2008.07.10
블로그코리아에 블UP하기

댓글을 남겨주세요.

ASM. Parsing Classes

Posted at 2008.07.16 11:37 // in OpenSource // by MOOVA 무바㏇

ASM에서는 클래스를 해석하는 유일한 필수 컴퍼넌트가 있다. 바로 ClassReader 컴퍼넌트.

ClassVisitor를 구현하고 클래스의 정보를 간략하게 출력하는 클래스

ClassPrinter.java

public class ClassPrinter implements ClassVisitor {

       public void visit(int version, int access, String name,
              String signature,
String superName, String[] interfaces) {

             System.out.println(name + " extends " + superName + " {");

       }

 

       public void visitSource(String source, String debug) {

       }

 

       public void visitOuterClass(String owner, String name, String desc) {

       }

 

       public AnnotationVisitor visitAnnotation(String desc,
                                               
boolean visible) {

             return null;

       }

 

       public void visitAttribute(Attribute attr) {

       }

 

       public void visitInnerClass(String name, String outerName,

                    String innerName, int access) {

       }

 

       public FieldVisitor visitField(int access, String name, String desc,

                    String signature, Object value) {

             System.out.println(" " + desc + " " + name);

             return null;

       }

 

       public MethodVisitor visitMethod(int access, String name,
                    String desc,
String signature, String[] exceptions) {

             System.out.println(" " + name + desc);

             return null;

       }

 

       public void visitEnd() {

             System.out.println("}");

       }

}

두번째 코드는 ClassReader와 ClassPrinter를 조합해야한다.
ClassPrinter cp = new ClassPrinter();
ClassReader cr = new ClassReader("java.lang.Runnable");
cr.accept(cp, 0);


결과는 다음과 같다.

java/lang/Runnable extends java/lang/Object {

run()V

}


신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'OpenSource' 카테고리의 다른 글

심심풀이 납품용  (2) 2010.06.05
[Guide] Vaadin Korean Guide (ver01)  (3) 2010.05.11
[Share Document Note] Evernote  (0) 2010.01.24
ASM. Parsing Classes  (0) 2008.07.16
ASM ClassVisitor  (0) 2008.07.10
Class Structure  (0) 2008.07.10
블로그코리아에 블UP하기

댓글을 남겨주세요.

ASM ClassVisitor

Posted at 2008.07.10 19:10 // in OpenSource // by MOOVA 무바㏇
아래의 구성은 ASM의 Document를 참고하였습니다.

asm-guide Monitor this package

 
  1.0   2007-02-21
asm-guide.pdf 1,265.9 Any pdf



ASM Vistor패턴으로 만들어진 라이브러리이다.
(분석을 통한 점진적인 스터디, 실제로 바이너리 코드를 읽을 때 어떤 형태로 읽어들이는지에 대한 간략한 메모입니다.)

방문자를 구현한 실제 클래스에 재귀적 호출을 사용하는
단순/복잡한 구조를 가지고 있다.

API를 보게되면 ClassVisitor Interface기반으로 되어 있는 것을 볼 수 있다.
실제로 ClassVisitor의 interface는 다음과 같다.

public interface ClassVisitor {

        void visit(int version, int access, String name, String signature,

                       String superName, String[] interfaces);

        void visitSource(String source, String debug);

        void visitOuterClass(String owner, String name, String desc);

        AnnotationVisitor visitAnnotation(String desc, boolean visible);

        void visitAttribute(Attribute attr);

        void visitInnerClass(String name, String outerName, String innerName,

                       int access);

        FieldVisitor visitField(int access, String name, String desc,

                       String signature, Object value);

        MethodVisitor visitMethod(int access, String name, String desc,

                       String signature, String[] exceptions);

        void visitEnd()
}

단순히 클래스의 Structure정보만을 얻고 싶을때는 visit>visitSource>visitOuterClass>visitAttribute>visitInnerClass>visitEnd 의 순으로 재귀적 호출이 이루어진다. 더 복잡한 정보를 얻고 실제로 클래스의 구조자체를 동적으로 운용하기 위해서는
위의 순서말고도 visitAnnotation,visitField,visitMethod 의 순으로 각각에 맞는 인터페이스를 돌려주어 복잡한 처리과정을 visit 패턴으로 단순화 시켰다.(실제로 위의 시그너처에서 알 수 있듯이
복잡한 처리과정에 필요한 3가지 메소드는 Visitor 보조 인터페이스를 리턴타입으로 가지고 있다.

예 FieldVisitor (보조 visitor인터페이스)

public interface FieldVisitor {

       AnnotationVisitor visitAnnotation(String desc, boolean visible);

       void visitAttribute(Attribute attr);

       void visitEnd();

}


다음은 javadoc에 나와 있는 ClassVisitor의 메소드 호출 순서이다.

org.objectweb.asm
Interface ClassVisitor

All Known Implementing Classes:
ASMifierClassVisitor, CheckClassAdapter, ClassAdapter, ClassNode, ClassWriter, EmptyVisitor, RemappingClassAdapter, SAXClassAdapter, SerialVersionUIDAdder, StaticInitMerger, TraceClassVisitor

public interface ClassVisitor

A visitor to visit a Java class. The methods of this interface must be called in the following order: visit [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* (visitInnerClass | visitField | visitMethod )* visitEnd.

Author:
Eric Bruneton
사용자 삽입 이미지


ASM은 클래스를 만들고 변환하기 위해 중요한 3가지 ClassVisitor 인터페이스를 제공한다.
(document 참조)


-
     
ClassReader 클래스는 byte 로 된 배열로 얻어지는 컴파일 된 클래스를 파싱한다. 이것은 유사한 visitXXX 메소드를 호출하고 또한 Event producer와 같다


-
     
ClassWriter 클래스는 ClassVisitor를 구현한 클래스다. 이것은 byte된 컴파일 된 클래스를 출력하고 toByteArray 메소드로 복구할 수 있다. 이것은 event. Consumer와 같다.


-
     
ClassAdapter 클래스는 다른 ClassVisitor 인스턴스를 받는 deletete 클래스다. 이것은 event filter와 같다.



다음은 컴퍼넌트가 어떻게 클래스를 분석하고 변환하는지 구체적인 예제를 통해서 이해하고 싶다.
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'OpenSource' 카테고리의 다른 글

심심풀이 납품용  (2) 2010.06.05
[Guide] Vaadin Korean Guide (ver01)  (3) 2010.05.11
[Share Document Note] Evernote  (0) 2010.01.24
ASM. Parsing Classes  (0) 2008.07.16
ASM ClassVisitor  (0) 2008.07.10
Class Structure  (0) 2008.07.10
블로그코리아에 블UP하기

댓글을 남겨주세요.

Class Structure

Posted at 2008.07.10 11:34 // in OpenSource // by MOOVA 무바㏇
AOP를 쉽게 구동해주는 바이너리 코드의 조작 라이브러리 (ASM)

컴파일 된 클래스의 전체적인 구조는 사실상 단순하다는 인식을 하곤한다.
실제로 컴파일 된 클래스는 소스코드 대부분의 특징과 구조적인 정보를 담고 있기 때문이다.

실제로 컴파일 된 클래스를 열어보면 다음의 3가지 구조적인 정보를 파악할 수 있다.

1. 한정자(modifiers)를 포함하고 있다.
    그리고 슈퍼클래스에 대한 정보, 인터페이스, annotation의 정보도 찾을 수 있다.
2. 필드(변수)에 대한 정보가 담겨져 있다.
3. 생성자(constructor)와 method(메소드)의 정보 그리고
    return type, parameter type, annotation methos에 관한 구조를 볼 수 있다.
몇가지 더 봐야 할 사항은 컴파일 된 class파일과 source코드 사이의 몇가지 다른 내용을 살펴봐야한다.
소스파일이 여러개의 class파일을 포함할 수 있는 동안에 (inner class) main class파일은 inner class파일의 레퍼런스 정보를 포함하고 있고 ,inner class파일을 감싸고 있는 메소드 정보도 포함이 된다.
또한 우리는 주석처리는 class파일에 포함되지 않는다는 것을 알고 있고 package와 import구문을 포함하지 않는다는 것을 알 수 있다.

사용자 삽입 이미지


다음은 컴파일된 클래스의 전반적인 구조다.

Modifiers, name, super class, interfaces

Constant pool: numeric, string and type constants

Source file name (optional)

Enclosing class reference

Annotation*

Attribute*

Inner class*

Name

Field*

Modifiers, name, type

 

Annotation*

 

Attribute*

Method*

Modifiers, name, return and parameter types

 

Annotation*

 

Attribute*

 

Compiled code


1. 컴파일 된 클래스의 구조적 정보


대부분의 경우 type은 class나 interface type으로 정해져 있다. 예를 들어 String의 컴파일 된 구조적 정보는
.class파일에 다음과 같이 컴파일링 된다. Ljava/lang/String

다음은 java type들 다시말해 wrapper type과 privitive type들에 관한 컴파일 구조적 정보표다.

Java type

Type descriptor

boolean

Z

char

C

byte

B

short

S

int

I

float

F

long

J

double

D

Object

Ljava/lang/Object;

int[]

[I

Object[][]

[[Ljava/lang/Object;


2. 자바 타입의 구조적 정보

Method declaration in source file

Method descriptor

void m(int i, float f)

(IF)V

int m(Object o)

(Ljava/lang/Object;)I

int[] m(int i, String s)

(ILjava/lang/String;)[I

Object m(int[] i)

([I)Ljava/lang/Object;


3. 메소드의 구조적 정보


위의 간략한 정보로 .class파일을 쉽게 읽을 수 있다는 것을 알 수 있다.
예를 들어 다음과 같은 소스코드의 구조는 클래스파일의 다음으로 컴파일된다.

사용자 삽입 이미지

정리..생략...

그렇다면. 우리는 하이레벨언어의 세상에서 .class파일의 구조적정보를 알 필요가 있을까?

Spring에서는 주로 사용되는 Aspectj나 interceptor기능은 신기하게도 기존 소스하나 건드리지 않고
설정만으로 클래스가 생기거나 메소드가 생기거나 하는 일이 가능하다. hivemind나 구글주스에서도
위의 정보로 바이너리 코드를 조작하는 라이브러리들을 사용하곤 한다.

그렇다면..
왜 바이너리 코드를 조작하여야만 할까?(<-- 어떤 친구가 던진 질문입니다.)


OOP의 한계를 넘어선 AOP의 관점지향을 우리는 왜 알아야 하며, 이해하고 있어야 할까?

이런 궁금증은 ASM라이브러리를 파헤쳐가면서 하나하나 알아가 보기로 했다.
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'OpenSource' 카테고리의 다른 글

심심풀이 납품용  (2) 2010.06.05
[Guide] Vaadin Korean Guide (ver01)  (3) 2010.05.11
[Share Document Note] Evernote  (0) 2010.01.24
ASM. Parsing Classes  (0) 2008.07.16
ASM ClassVisitor  (0) 2008.07.10
Class Structure  (0) 2008.07.10
블로그코리아에 블UP하기

댓글을 남겨주세요.

티스토리 툴바