'Open Source/Spring'에 해당되는 글 10건

Custom Role in Spring Security :: 2009/09/19 22:25

Spring Security에서 role은 기본적으로 "ROLE_"로 시작해야 한다. 그렇지 않으면 오류를 뱉어낸다. 만약 "PRIV_" 등 다른 접두사를 사용하거나 특별한 네임 규칙이 없는 다른 role명을 사용하던 기존 시스템에 Spring Security를 연결해야 한다면 추가적인 설정이 필요하다.

다음과 같이 Access Decision Manager를 정의하고, RoleVoter에 prefix를 재설정하면 된다.
<security:http auto-config="true"
    access-decision-manager-ref="accessDecisionManager">
...
</security:http>

<bean id="accessDecisionManager"
    class="org.springframework.security.vote.AffirmativeBased">
  <property name="allowIfAllAbstainDecisions" value="false" />
  <property name="decisionVoters">
    <list>
      <bean class="org.springframework.security.vote.RoleVoter">
        <property name="rolePrefix" value="" /> <-- ROLE_ -->
      </bean>
    </list>
  </property>
</bean>

Spring Security 2.0.x Configuration :: 2009/09/13 23:06

Spring Security는 인증(Authentication)과 권한부여(Authorization)라는 기본 보안 기능 외에 다양한 시스템과의 인터페이스 및 확장 기능을 제공하는 보안 프레임워크이며, 구 Acegi Security 프레임워크에서 확장된 것이다.

Spring Security 프레임워크는 상당히 복잡하며 비즈니스 요구사항에 따라 매우 다양하게 구성할 수 있다. Java 기반 웹어플리케이션 보안을 위해서 기본적으로 16개의 보안 필터들을 제공하는데, 이들을 프로퍼티 등만 재지정하여 그대로 사용할 수도 있고, 상속하여 커스텀 필터를 작성 후 재지정하여 사용할 수도 있다. 일반적으로 이들 필터들을 모두 사용하지 않고 보안 요구사항에 따라 일부 필터들만을 지정하여 사용할 것이다.

Spring Security를 사용하기 위해서는 다음과 같이 web.xml에 보안 필터 체인 설정을 한다. 동시 로그인 세션수를 제어를 할 수 있는 Concurrent Session Filter를 사용하기 위해 웹 세션 이벤트를 읽는 리스너도 등록할 수 있다.
<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

<!--
<listener>
  <listener-class>
    org.springframework.security.ui.session.HttpSessionEventPublisher
  </listener-class>
</listener>
-->

다음은 적용할 각 보안 필터들에 대한 스프링 컨텍스트 설정이 필요하다. 아래는 가장 단순한 형태의 Spring Security 설정의 예이다.
<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"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
  http://www.springframework.org/schema/security
  http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">
  
  <http auto-config='true'>
    <intercept-url pattern="/**" access="ROLE_USER" />
  </http>

  <authentication-provider>
    <user-service>
      <user name="user" password="user" authorities="ROLE_USER" />
    </user-service>
  </authentication-provider>

</beans:beans>

위 설정에서 http 요소에 auto-config="true"를 지정하면, 스프링은 아래와 같은 서블릿 필터들을 등록을 하게 되며, 이 필터들은 정해진 순서대로 순차적으로 각 필터의 역할을 수행하게 된다.
  • HttpSessionContextIntegrationFilter
  • LogoutFilter
  • AuthenticationProcessingFilter
  • DefaultLoginPageGeneratingFilter
  • BasicProcessingFilter
  • SecurityContextHolderAwareRequestFilter
  • RememberMeProcessingFilter
  • AnonymousProcessingFilter
  • ExceptionTranslationFilter
  • SessionFixationProtectionFilter
  • FilterSecurityInterceptor

아래는 같은 보안 설정에 대한 3가지의 다른 형태로 작성해 본 것이다. (Spring Security 2.0.x 기준).

Example 1
<?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"
  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.4.xsd">

  <!-- ===== HTTP AUTO CONFIG ======================================== -->

  <http auto-config="true" realm="My Security Realm"
    access-denied-page="/errors/accessDenied.jsp">
    <intercept-url pattern="/" filters="none" />
    <intercept-url pattern="/index*" filters="none" />
    <intercept-url pattern="/images/**" filters="none" />
    <intercept-url pattern="/scripts/**" filters="none" />
    <intercept-url pattern="/styles/**" filters="none" />
    <intercept-url pattern="/login.jsp*" access="ROLE_ANONYMOUS" />
    <intercept-url pattern="/admin**" access="ROLE_ADMIN" />
    <intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN" />
    <intercept-url pattern="/**" access="IS_AUTHENTICATED_REMEMBERED" />

    <!--
    <concurrent-session-control max-sessions="1" exception-if-maximum-exceeded="true" />
    -->
    <logout logout-url="/j_security_logout" logout-success-url="/" />
    <form-login login-processing-url="/j_secutiry_check" login-page="/login.jsp"
      authentication-failure-url="/login.jsp?login_error=1"
      default-target-url="/" always-use-default-target="false" />
    <remember-me services-ref="rememberMeServices" key="someUniqueKey" />
    <http-basic />
    <anonymous />
  </http>

  <!-- ===== AUTHENTICATION PROVIDER ================================= -->

  <!--
  <authentication-provider>
    <user-service>
      <user name="admin" password="admin" authorities="ROLE_ADMIN,ROLE_USER" />
      <user name="user" password="user" authorities="ROLE_USER" />
    </user-service>
  </authentication-provider>

  <authentication-provider>
    <password-encoder hash="sha" />
    <user-service>
      <user name="admin" password="d033e22ae348aeb5660fc2140aec35850c4da997"
        authorities="ROLE_ADMIN,ROLE_USER" />
      <user name="user" password="12dea96fec20593566ab75692c9949596833adc9"
        authorities="ROLE_USER" />
    </user-service>
  </authentication-provider>

  <authentication-provider>
    <password-encoder hash="sha" base64="true" />
    <user-service>
      <user name="admin" password="0DPiKuNIrrVmD8IUCuw1hQxNqZc=" 
        authorities="ROLE_ADMIN,ROLE_USER" />
      <user name="user" password="Et6pb+wgWTVmq3VpLJlJWWgzrck="
        authorities="ROLE_USER" />
    </user-service>
  </authentication-provider>
  -->

  <!--( md5 | sha )-->
  <authentication-provider user-service-ref="userService">
    <password-encoder hash="sha" base64="true" />
  </authentication-provider>

  <beans:bean id="rememberMeServices"
    class="org.springframework.security.ui.rememberme.TokenBasedRememberMeServices">
    <beans:property name="userDetailsService" ref="userService" />
    <beans:property name="key" value="someUniqueKey" />
    <beans:property name="parameter" value="rememberMe" />
  </beans:bean>

  <!-- ===== LOGGER LISTENER ========================================= -->

  <beans:bean id="loggerListener"
    class="org.springframework.security.event.authentication.LoggerListener" />

</beans:beans>

Example 2
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:security="http://www.springframework.org/schema/security"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  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.4.xsd">

  <!-- ===== HTTP CONFIG ============================================= -->

  <security:http realm="My Security Realm"
    entry-point-ref="authenticationProcessingFilterEntryPoint"
    access-denied-page="/errors/accessDenied.jsp">

    <security:intercept-url pattern="/" filters="none" />
    <security:intercept-url pattern="/index*" filters="none" />
    <security:intercept-url pattern="/images/**" filters="none" />
    <security:intercept-url pattern="/scripts/**" filters="none" />
    <security:intercept-url pattern="/styles/**" filters="none" />
    <security:intercept-url pattern="/login.jsp*" access="ROLE_ANONYMOUS" />
    <security:intercept-url pattern="/admin**" access="ROLE_ADMIN" />
    <security:intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN" />
    <security:intercept-url pattern="/**" access="IS_AUTHENTICATED_REMEMBERED" />

    <!--
    <security:concurrent-session-control max-sessions="1" exception-if-maximum-exceeded="true" />
    -->
    <security:http-basic />
    <security:anonymous />
  </security:http>

  <!-- ===== AUTHENTICATION MAMANGER & PROVIDER ====================== -->

  <security:authentication-manager alias="authenticationManager" />

  <security:authentication-provider user-service-ref="userService">
    <security:password-encoder hash="sha" base64="true" />
  </security:authentication-provider>

  <!-- ===== LOGOUT_FILTER =========================================== -->

  <bean id="logoutFilter" class="org.springframework.security.ui.logout.LogoutFilter">
    <!-- Sets this as the logout filter over the default -->
    <security:custom-filter position="LOGOUT_FILTER" />
    <constructor-arg value="/" />
    <constructor-arg>
      <list>
        <ref bean="rememberMeServices" />
        <bean
          class="org.springframework.security.ui.logout.SecurityContextLogoutHandler" />
      </list>
    </constructor-arg>
    <property name="filterProcessesUrl" value="/j_security_logout" />
  </bean>

  <!-- ===== AUTHENTICATION_PROCESSING_FILTER =======================  -->

  <bean id="authenticationProcessingFilterEntryPoint"
    class="org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint">
    <property name="loginFormUrl" value="/login.jsp" />
    <property name="forceHttps" value="false" />
  </bean>

  <bean id="authenticationProcessingFilter"
    class="org.springframework.security.ui.webapp.AuthenticationProcessingFilter">
    <!-- Sets this as the authentication filter over the default -->
    <security:custom-filter position="AUTHENTICATION_PROCESSING_FILTER" />
    <property name="authenticationManager" ref="authenticationManager" />
    <property name="authenticationFailureUrl" value="/login.jsp?login_error=1" />
    <property name="defaultTargetUrl" value="/" />
    <property name="filterProcessesUrl" value="/j_security_check" />
    <property name="rememberMeServices" ref="rememberMeServices" />
    <property name="exceptionMappings">
      <props>
        <prop key="org.springframework.security.BadCredentialsException">/login.jsp?login_error=2</prop>
        <prop key="org.springframework.security.DisabledException">/login.jsp?login_error=3</prop>
        <prop key="org.springframework.security.LockedException">/login.jsp?login_error=4</prop>
        <prop key="org.springframework.security.AccountExpiredException">/login.jsp?login_error=5</prop>
        <prop key="org.springframework.security.CredentialsExpiredException">/login.jsp?login_error=6</prop>
        <prop key="org.springframework.security.concurrent.ConcurrentLoginException">/login.jsp?login_error=7</prop>
      </props>
    </property>
  </bean>

  <!-- ===== REMEMBER_ME_FILTER ====================================== -->

  <bean id="rememberMeProcessingFilter"
    class="org.springframework.security.ui.rememberme.RememberMeProcessingFilter">
    <!-- Sets this as the remember me filter over the default -->
    <security:custom-filter position="REMEMBER_ME_FILTER" />
    <property name="authenticationManager" ref="authenticationManager" />
    <property name="rememberMeServices" ref="rememberMeServices" />
  </bean>

  <bean id="rememberMeServices"
    class="org.springframework.security.ui.rememberme.TokenBasedRememberMeServices">
    <property name="userDetailsService" ref="userService" />
    <property name="key" value="someUniqueKey" />
    <property name="parameter" value="rememberMe" />
  </bean>

  <bean id="rememberMeAuthenticationProvider"
    class="org.springframework.security.providers.rememberme.RememberMeAuthenticationProvider">
    <security:custom-authentication-provider />
    <property name="key" value="someUniqueKey" />
  </bean>

  <!-- ===== LOGGER LISTENER ========================================= -->

  <bean id="loggerListener"
    class="org.springframework.security.event.authentication.LoggerListener" />

</beans>

Example 3
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:security="http://www.springframework.org/schema/security"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  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.4.xsd">

  <!-- =============================================================== -->
  <!-- Spring Security Filter Chain                                    -->
  <!-- =============================================================== -->

  <bean id="springSecurityFilterChain" class="org.springframework.security.util.FilterChainProxy">
    <security:filter-chain-map path-type="ant">
      <security:filter-chain pattern="/" filters="none" />
      <security:filter-chain pattern="/index*" filters="none" />
      <security:filter-chain pattern="/images/**" filters="none" />
      <security:filter-chain pattern="/scripts/**" filters="none" />
      <security:filter-chain pattern="/styles/**" filters="none" />
      <security:filter-chain pattern="/**"
        filters="httpSessionContextIntegrationFilter,
                logoutFilter,
                authenticationProcessingFilter,
                basicProcessingFilter,
                securityContextHolderAwareRequestFilter,
                rememberMeProcessingFilter,
                anonymousProcessingFilter,
                exceptionTranslationFilter,
                filterSecurityInterceptor" />
    </security:filter-chain-map>
  </bean>

  <!-- =============================================================== -->
  <!-- FILTERS                                                         -->
  <!-- =============================================================== -->

  <!-- ===== CONCURRENT_SESSION_FILTER =============================== -->
  <!--
  <bean id="concurrentSessionFilter"
    class="org.springframework.security.concurrent.ConcurrentSessionFilter">
    <property name="sessionRegistry" ref="sessionRegistry" />
    <property name="expiredUrl" value="/" />
  </bean>

  <bean id="concurrentSessionController"
    class="org.springframework.security.concurrent.ConcurrentSessionControllerImpl">
    <property name="maximumSessions" value="1" />
    <property name="exceptionIfMaximumExceeded" value="true" />
    <property name="sessionRegistry" ref="sessionRegistry" />
  </bean>

  <bean id="sessionRegistry" class="org.springframework.security.concurrent.SessionRegistryImpl" />
  -->

  <!-- ===== SESSION_CONTEXT_INTEGRATION_FILTER ====================== -->

  <bean id="httpSessionContextIntegrationFilter"
    class="org.springframework.security.context.HttpSessionContextIntegrationFilter" />

  <!-- ===== LOGOUT_FILTER =========================================== -->

  <bean id="logoutFilter" class="org.springframework.security.ui.logout.LogoutFilter">
    <constructor-arg value="/" />
    <constructor-arg>
      <list>
        <ref bean="rememberMeServices" />
        <bean
          class="org.springframework.security.ui.logout.SecurityContextLogoutHandler" />
      </list>
    </constructor-arg>
    <property name="filterProcessesUrl" value="/j_security_logout" />
  </bean>

  <!-- ===== AUTHENTICATION_PROCESSING_FILTER =======================  -->

  <bean id="authenticationProcessingFilter"
    class="org.springframework.security.ui.webapp.AuthenticationProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager" />
    <property name="authenticationFailureUrl" value="/login.jsp?login_error=1" />
    <property name="defaultTargetUrl" value="/" />
    <property name="filterProcessesUrl" value="/j_security_check" />
    <property name="rememberMeServices" ref="rememberMeServices" />
    <property name="exceptionMappings">
      <props>
        <prop key="org.springframework.security.BadCredentialsException">/login.jsp?login_error=2</prop>
        <prop key="org.springframework.security.DisabledException">/login.jsp?login_error=3</prop>
        <prop key="org.springframework.security.LockedException">/login.jsp?login_error=4</prop>
        <prop key="org.springframework.security.AccountExpiredException">/login.jsp?login_error=5</prop>
        <prop key="org.springframework.security.CredentialsExpiredException">/login.jsp?login_error=6</prop>
        <prop key="org.springframework.security.concurrent.ConcurrentLoginException">/login.jsp?login_error=7</prop>
      </props>
    </property>
  </bean>

  <bean id="formAuthenticationEntryPoint"
    class="org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint">
    <property name="loginFormUrl" value="/login.jsp" />
    <property name="forceHttps" value="false" />
  </bean>

  <!-- ===== BASIC_PROCESSING_FILTER ================================= -->

  <bean id="basicProcessingFilter"
    class="org.springframework.security.ui.basicauth.BasicProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager" />
    <property name="authenticationEntryPoint" ref="basicAuthenticationEntryPoint" />
  </bean>

  <bean id="basicAuthenticationEntryPoint"
    class="org.springframework.security.ui.basicauth.BasicProcessingFilterEntryPoint">
    <property name="realmName" value="My Security Realm" />
  </bean>

  <!-- ===== SERVLET_API_SUPPORT_FILTER ============================== -->

  <bean id="securityContextHolderAwareRequestFilter"
    class="org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter" />

  <!-- ===== REMEMBER_ME_FILTER ====================================== -->

  <bean id="rememberMeProcessingFilter"
    class="org.springframework.security.ui.rememberme.RememberMeProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager" />
    <property name="rememberMeServices" ref="rememberMeServices" />
  </bean>

  <bean id="rememberMeServices"
    class="org.springframework.security.ui.rememberme.TokenBasedRememberMeServices">
    <property name="userDetailsService" ref="userService" />
    <property name="key" value="someUniqueKey" />
    <property name="parameter" value="rememberMe" />
  </bean>

  <!-- ===== ANONYMOUS_FILTER ======================================== -->

  <bean id="anonymousProcessingFilter"
    class="org.springframework.security.providers.anonymous.AnonymousProcessingFilter">
    <property name="key" value="anonymous" />
    <property name="userAttribute" value="anonymous,ROLE_ANONYMOUS" />
  </bean>

  <!-- ===== EXCEPTION_TRANSLATION_FILTER ============================ -->

  <bean id="exceptionTranslationFilter" 
      class="org.springframework.security.ui.ExceptionTranslationFilter">
    <property name="authenticationEntryPoint" ref="formAuthenticationEntryPoint" />
    <property name="accessDeniedHandler">
      <bean class="org.springframework.security.ui.AccessDeniedHandlerImpl">
        <property name="errorPage" value="/errors/accessDenied.jsp" />
      </bean>
    </property>
  </bean>

  <!-- ===== FILTER_SECURITY_INTERCEPTOR ============================ -->

  <bean id="filterSecurityInterceptor"
    class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
    <property name="authenticationManager" ref="authenticationManager" />
    <property name="accessDecisionManager" ref="accessDecisionManager" />
    <property name="objectDefinitionSource">
      <security:filter-invocation-definition-source
        id="filterInvocationDefinitionSource" lowercase-comparisons="true"
        path-type="ant">
        <security:intercept-url pattern="/login.jsp*" access="ROLE_ANONYMOUS" />
        <security:intercept-url pattern="/admin**"
          access="ROLE_ADMIN" />
        <security:intercept-url pattern="/**"
          access="ROLE_USER,ROLE_ADMIN" />
      </security:filter-invocation-definition-source>
    </property>
  </bean>

  <bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
    <property name="allowIfAllAbstainDecisions" value="false" />
    <property name="decisionVoters">
      <list>
        <bean class="org.springframework.security.vote.RoleVoter" />
      </list>
    </property>
  </bean>

  <!-- =============================================================== -->
  <!-- AUTHENTICATION MANAGER & PROVIDER                               -->
  <!-- =============================================================== -->

  <bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager">
    <property name="providers">
      <list>
        <ref local="daoAuthenticationProvider" />
        <ref local="anonymousAuthenticationProvider" />
        <ref local="rememberMeAuthenticationProvider" />
      </list>
    </property>
    <!--<property name="sessionController" ref="concurrentSessionController" />-->
  </bean>

  <bean id="daoAuthenticationProvider"
    class="org.springframework.security.providers.dao.DaoAuthenticationProvider">
    <property name="userDetailsService" ref="userService" />
    <property name="passwordEncoder" ref="passwordEncoder" />
  </bean>

  <bean id="anonymousAuthenticationProvider"
    class="org.springframework.security.providers.anonymous.AnonymousAuthenticationProvider">
    <property name="key" value="anonymous" />
  </bean>

  <bean id="rememberMeAuthenticationProvider"
    class="org.springframework.security.providers.rememberme.RememberMeAuthenticationProvider">
    <property name="key" value="someUniqueKey" />
  </bean>

  <!--( ShaPasswordEncoder | Md5PasswordEncoder )-->
  <bean id="passwordEncoder"
    class="org.springframework.security.providers.encoding.ShaPasswordEncoder">
    <!--<constructor-arg value="256" />-->
    <property name="encodeHashAsBase64" value="true" />
  </bean>

  <!-- ===== LOGGER LISTENER ========================================= -->

  <bean id="loggerListener"
    class="org.springframework.security.event.authentication.LoggerListener" />

</beans>

SpringSource Tool Suite for Free! :: 2009/05/21 23:17

얼마 전 Eclipse 3.4.2 기반의 SpringSource Tool Suite (STS) 2.0.2 가 릴리즈 되었다. 스프링 기반의 어플리케이션 개발에 필요한 플러그인이나 기능 등을 모아 놓았고, 개발용으로 공짜로 사용할 수 있다. SpringSource dm Server를 통한 OSGi 런타임 환경 및 Spring Web Flow 등에 대한 지원과 Spring IDE, Maven 2.0, AspectJ Development Tools (AJDT), EclEmma 등의 플러그인들이 기본으로 포함되어 있다. 내가 즐겨쓰는 Subversive 플러그인은 빠져있다. SVN 클라이언트는 개발자들 간에 아직도 여러 버전을 사용해서 그런가? 아니면 라이센스 문제인가?

User inserted image

참조: http://www.springsource.com/products/sts

dbcp와 c3p0 커넥션 풀 유지하기 :: 2009/02/17 18:09

JDBC 커넥션 풀을 지원하는 대표적인 오픈소스 중에 아파치 DBCPC3P0가 있다. 이들은 Spring, Hibernate 등과 통합되어 DB 커넥션 풀을 제공하는 DataSource를 구성하여 자주 쓰인다.

오라클이나 MySQL 등 DBMS들은 기본적으로 특정 시간동안 실행이 없으면 해당 세션을 종료하게 된다. 이렇게 종료된 커넥션은 어플리케이션에서 오류를 발생시키게 되므로 커넥션을 유지하기 위한 별도 설정을 필요로 하게 된다. 커넥션을 얻어올 때 커넥션 테스트를 수행하고 실패하면 새로운 커넥션을 생성할 수 있다. 또한 idle 타임에 주기적으로 커넥션 테스트를 수행할 수도 있다.

아래는 dbcp를 이용하여 구성한 스프링 DataSource 설정의 예이다.

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
    <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
    <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="defaultAutoCommit" value="true"/>
    <property name="initialSize" value="5"/>
    <property name="maxActive" value="30"/>
    <property name="maxIdle" value="5"/>
    <property name="maxWait" value="30000"/>
    <property name="validationQuery" value="SELECT 1 FROM DUAL"/>
    <property name="testOnBorrow" value="true"/>
    <property name="testOnReturn" value="false"/>
    <property name="testWhileIdle" value="true"/>
    <property name="timeBetweenEvictionRunsMillis" value="60000"/>
</bean>

아래는 c3p0를 이용하여 구성한 스프링 DataSource 설정의 예이다.
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
        destroy-method="close">
    <property name="driverClass" value="org.gjt.mm.mysql.Driver" />
    <property name="jdbcUrl" value="jdbc:mysql://localhost/testdb" />
    <property name="user" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
    <property name="initialPoolSize" value="5" />
    <property name="maxPoolSize" value="30" />
    <property name="minPoolSize" value="5" />
    <property name="acquireIncrement" value="3" />
    <property name="acquireRetryAttempts" value="30" />
    <property name="acquireRetryDelay" value="1000" />
    <property name="idleConnectionTestPeriod" value="60" />
    <property name="preferredTestQuery" value="SELECT 1" />
    <property name="testConnectionOnCheckin" value="true" />
    <property name="testConnectionOnCheckout" value="false" />
</bean>

참조:
  - DBCP Configuration: http://commons.apache.org/dbcp/configuration.html
  - C3P0 Configuraion: http://www.mchange.com/projects/c3p0/index.html

Spring + Apache Mina + JMX :: 2009/01/22 11:31

네트워크 어플리케이션 프레임워크인 Apache Mina를 이용하여 소켓 서버를 구현할 수 있다. Mina는 Spring DI를 이용하여 쉽게 인테그레이션 될 수 있고, 또한 Spring에서 지원하는 JMX 기능을 사용하여 서버에 등록된 Bean들을 모니터링 및 관리를 할 수 있다.

1. Apache Mina의 IoHandler를 구현한 클래스와 소켓서버의 설정을 아래와 같이 설정한다.
<!-- java.net.InetSocketAddress objects by Spring -->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
  <property name="customEditors">
    <map>
      <entry key="java.net.SocketAddress">
        <bean class="org.apache.mina.integration.spring.InetSocketAddressEditor" />
      </entry>
    </map>
  </property>
</bean>

<!-- The IoHandler implementation -->
<bean id="ioHandler" class="net.java2go.server.MyIoHandler" />

<!-- thread pool -->
<bean id="threadModel"
    class="org.apache.mina.integration.spring.ExecutorThreadModelFactoryBean">
  <property name="serviceName" value="MyService" />
  <property name="executor">
    <bean class="org.apache.mina.integration.spring.ThreadPoolExecutorFactoryBean">
      <property name="corePoolSize" value="10" />
      <property name="maxPoolSize" value="30" />
      <property name="keepAliveSeconds" value="30" />
    </bean>
  </property>
</bean>

<!-- The IoAcceptor which binds to port 8099 -->
<bean id="ioAcceptor"
class="org.apache.mina.integration.spring.IoAcceptorFactoryBean"> <property name="target"> <bean class="org.apache.mina.transport.socket.nio.SocketAcceptor" /> </property> <property name="bindings"> <list> <bean class="org.apache.mina.integration.spring.Binding"> <property name="address" value="8099" /> <property name="handler" ref="ioHandler" /> <property name="serviceConfig"> <bean class="org.apache.mina.transport.socket.nio.SocketAcceptorConfig"> <property name="reuseAddress" value="true" /> <property name="threadModel" ref="threadModel" /> </bean> </property> </bean> </list> </property> </bean>

2. Apache Mina의 IoServiceManager 및 기타 JMX를 통해 노출하고자하는 Bean을 등록한다. 그리고 Spring JMX MBeanExporter 설정을 통해 해당 Bean을 노출한다. 여기서는 RMI를 이용하였고, 이 경우 RMI Registry에 대한 설정도 추가해 준다.
<bean id="ioServiceManager"
    class="org.apache.mina.integration.jmx.IoServiceManager">
  <constructor-arg index="0" ref="ioAcceptor" />
</bean>

<bean id="mbeanServer"
    class="org.springframework.jmx.support.MBeanServerFactoryBean">
  <property name="locateExistingServerIfPossible" value="true" />
  <!--<property name="defaultDomain" value="server" />-->
</bean>

<bean id="serverConnector"
    class="org.springframework.jmx.support.ConnectorServerFactoryBean"
    depends-on="rmiRegistry">
  <property name="objectName" value="connector:name=rmi" />
  <property name="serviceUrl" 
      value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/server" />
</bean>

<bean id="rmiRegistry"
    class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
  <property name="port" value="1099" />
</bean>

<bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter">
  <property name="beans">
    <map>
      <entry key="server:name=ioService" value-ref="ioServiceManager" />
      <!--<entry key="server:name=myServiceBean" value-ref="myServiceBean" />-->
    </map>
  </property>
  <property name="registrationBehaviorName" value="REGISTRATION_REPLACE_EXISTING" />
  <property name="server">
    <ref local="mbeanServer" />
  </property>
</bean>

3. JMX를 통해 MBean에 접속하려는 서버에 방화벽이 설치되어 있는 경우, JMX/RMI Connector를 위한 Port를 명시적으로 지정할 필요가 있다. 그렇지 않으면 시스템에서 랜덤하게 지정된 Port를 사용하게 되는데 방화벽이 설치된 경우 임의 Port를 사용할 수 없으므로 접속이 불가능하게 된다. 다음과 같이 JMX/RMI Connector를 위한 포트를 지정해 준다. (참고: RMI Connector와 Repository 포트는 동일해도 됨).
<property name="serviceUrl" 
  value="service:jmx:rmi://localhost:1098/jndi/rmi://localhost:1099/server" />

4. JMX 모니터링 도구를 사용하여 JMX 서버에 접속하여 모니터링을 수행한다. JMX 접속 주소는 Spring 설정에서 노출한 serviceUrl이 되며, 다음은 JDK에 포함되어 있는 JColsole을 이용하여 모니터링을 수행한 화면이다.

사용자 삽입 이미지

사용자 삽입 이미지

사용자 삽입 이미지

사용자 삽입 이미지

사용자 삽입 이미지

사용자 삽입 이미지

사용자 삽입 이미지

Cron Job 설정 표현식 :: 2008/10/24 14:29

스프링에서 Quartz를 사용하여 스케쥴링을 할 때 CronTriggerBean을 사용하여 설정할 경우 cronExpression 설정에 대한 예이다.

<bean id="exampleCronTrigger"
	class="org.springframework.scheduling.quartz.CronTriggerBean">
	<property name="jobDetail">
		<ref bean="exampleJob" />
	</property>
	<property name="cronExpression">
		<value>0 59 23 * * ?</value>
	</property>
</bean>

1 Seconds (0-59)
2 Minutes (0-59)
3 Hours (0-23)
4 Day of Month (1-31)
5 Month (1-12 or JAN-DEC)
6 Day of Week (1-7 or SUN-SAT)
7 Year (1970-2099) <-- 생략 가능

스프링 설정에서 null 값 셋팅하는 방법 :: 2008/06/12 16:37

스프링 설정에서 null 값 셋팅하는 방법이다.
부분적인 테스트 수행 시 모든 bean을 참조할 필요가 없을 경우, 정의안된 argument 등에 대해서 null 값 셋팅 시에도 유용하게 사용할 수 있다.

<bean class="SampleBean">
<property name="name"><value></value></property>
</bean>
 
--> sampleBean.setName("") 와 동일.

<bean class="ExampleBean">
<property name="email"><null/></property>
</bean>

--> exampleBean.setEmail(null)과 동일
 

스프링 어플리케이션 컨텍스트 설정 :: 2008/05/15 23:03

스프링 프레임워크의 버전별 어플리케이션 컨텍스트 설정에 대한 간단한 샘플이다.

1. Spring 1.2에서 DTD 기반의 XML 설정을 지원한다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
 "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
 <!-- ========================= DATABASE ============================ -->
 <bean id="propertyConfigurer" 
  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="locations">
   <list>
    <value>jdbc.properties</value>
   </list>
  </property>
 </bean>
 <bean id="dataSource" 
  class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  <property name="driverClassName" value="${jdbc.driverClassName}" />
  <property name="url" value="${jdbc.url}" />
  <property name="username" value="${jdbc.username}" />
  <property name="password" value="${jdbc.password}" />
 </bean>
 <!-- ========================= TRANSACTION ======================== -->
 <bean id="transactionManager"
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource" />
 </bean>
 <!-- ========================= O/R BROKER ========================= -->
 <bean id="exampleBroker" 
  class="org.springmodules.orm.orbroker.BrokerFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="configLocation"
   value="classpath:net/java2go/dao/maps/ExampleBrokerSql.xml" />
 </bean>
 <!-- ========================= DAO ================================ -->
 <bean id="exampleDao" class="net.java2go.dao.IBatisExampleDAOImpl">
  <property name="broker" ref="examplenBroker" />
 </bean>
 
 <!-- ========================= SERVICE ============================ -->
 <bean id="exampleService" class="net.java2go.service.ExampleServiceImpl">
  <property name="exampleDao" ref="exampleDao" />
 </bean>
</beans>

2. Spring 2.0에서는 DTD 기반 설정뿐만 아니라 XML 스키마 기반의 XML 설정을 통해 보다 쉬운 컨텍스트 설정을 지원한다. 또한 클래스에서 어노테이션을 사용하여 어노테이션 기반의 컨텍스트 설정을 할 수 있다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
 <!-- ========================= DATABASE ============================ -->
 <bean id="propertyConfigurer"
  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="locations">
   <list>
    <value>jdbc.properties</value>
   </list>
  </property>
 </bean>
 <bean id="dataSource"
  class="com.mchange.v2.c3p0.ComboPooledDataSource"
  destroy-method="close">
  <property name="driverClass" value="${jdbc.driverClassName}" />
  <property name="jdbcUrl" value="${jdbc.url}" />
  <property name="user" value="${jdbc.username}" />
  <property name="password" value="${jdbc.password}" />
  <property name="initialPoolSize"
   value="${jdbc.initialPoolSize}" />
  <property name="maxPoolSize" value="${jdbc.maxPoolSize}" />
  <property name="minPoolSize" value="${jdbc.minPoolSize}" />
  <property name="acquireIncrement"
   value="${jdbc.acquireIncrement}" />
  <property name="acquireRetryAttempts"
   value="${jdbc.acquireRetryAttempts}" />
 </bean>
 <!-- ========================= TRANSACTION ======================== -->
 <!-- transaction -->
 <bean id="transactionManager"
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource" />
 </bean>
 <!--tx:annotation-driven transaction-manager="transactionManager" /-->
 <!-- ========================= ASPECT ============================= -->
 <tx:advice id="txAdvice"
  transaction-manager="transactionManager">
  <tx:attributes>
   <tx:method name="*Audit**" propagation="NOT_SUPPORTED" />
   <tx:method name="get*" propagation="SUPPORTS"
    read-only="true" rollback-for="Throwable" />
   <tx:method name="retrieve*" propagation="SUPPORTS"
    read-only="true" rollback-for="Throwable" />
   <tx:method name="verify*" propagation="SUPPORTS"
    read-only="true" rollback-for="Throwable" />
   <tx:method name="check*" propagation="SUPPORTS"
    read-only="true" rollback-for="Throwable" />
   <tx:method name="*" propagation="REQUIRED"
    read-only="false" rollback-for="Throwable" />
  </tx:attributes>
 </tx:advice>
 <bean id="loggingAspect"
  class="net.java2go.aspect.LoggingAspect">
  <property name="thresholdInMills" value="3000" />
 </bean>
 <aop:config>
  <aop:pointcut id="servicePointcut"
   expression="execution(* net.java2go.service..*Service.*(..))" />
  <aop:pointcut id="daoPointcut"
   expression="execution(* net.java2go.dao..*DAO.*(..))" />
  <aop:pointcut id="serviceDaoPointcut"
   expression="execution(* net.java2go.service..*Service.*(..))
    or execution(* net.java2go.dao..*DAO.*(..))" />
  <aop:advisor advice-ref="txAdvice"
   pointcut-ref="servicePointcut" />
  <aop:aspect ref="loggingAspect">
   <aop:around pointcut-ref="servicePointcut"
    method="logProfiling" />
   <aop:after-throwing pointcut-ref="serviceDaoPointcut"
    throwing="ex" method="logException" />
  </aop:aspect>
 </aop:config>
 <!-- ========================= IBATIS ============================= -->
 <bean id="sqlMapClient" 
  class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
  <property name="configLocation">
   <value>sqlmap-config.xml</value>
  </property>
  <property name="dataSource" ref="dataSource" />
 </bean>
 <!-- ========================= DAO ================================ -->
 <bean id="exampleDao" class="net.java2go.dao.IBatisExampleDAOImpl">
  <property name="sqlMapClient" ref="sqlMapClient" />
 </bean>
 
 <!-- ========================= SERVICE ============================ -->
 <bean id="exampleService" class="net.java2go.service.ExampleServiceImpl">
  <property name="exampleDao" ref="exampleDao" />
 </bean>
</beans>

3. Spring 2.5에서 더 많은 XML 네임스페이스와 어노테이션이 추가되었다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-2.5.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
  http://www.springframework.org/schema/tx
  http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
 <!-- ========================= DATABASE ============================ -->
 <context:property-placeholder location="jdbc.properties" />
</beans>

웹로직 8.1에서 스프링 어플리케이션 배포 :: 2007/08/13 16:39

웹로직8.1에서 스프링 프레임워크를 사용한 war 형태의 웹어플리케이션을 배포할 때
아래와 같은 오류가 발생한 다면..


weblogic.management.DeploymentException:
Exception:weblogic.management.ApplicationException: start() failed.
 Module: batman Error: weblogic.management.DeploymentException: Cannot set web app root system property when WAR file is not expanded - with nested exception:
[java.lang.IllegalStateException: Cannot set web app root system property when WAR file is not expanded]

 at weblogic.management.deploy.slave.SlaveDeployer$ActivateTask.doCommit(SlaveDeployer.java:2423)
 at weblogic.management.deploy.slave.SlaveDeployer$Task.commit(SlaveDeployer.java:2138)
 at weblogic.management.deploy.slave.SlaveDeployer$Task.checkAutoCommit(SlaveDeployer.java:2237)
 at weblogic.management.deploy.slave.SlaveDeployer$Task.prepare(SlaveDeployer.java:2132)
 at weblogic.management.deploy.slave.SlaveDeployer$ActivateTask.prepare(SlaveDeployer.java:2384)
 at weblogic.management.deploy.slave.SlaveDeployer.processPrepareTask(SlaveDeployer.java:866)
 at weblogic.management.deploy.slave.SlaveDeployer.prepareDelta(SlaveDeployer.java:594)
 at weblogic.management.deploy.slave.SlaveDeployer.prepareUpdate(SlaveDeployer.java:508)
 at weblogic.drs.internal.SlaveCallbackHandler$1.execute(SlaveCallbackHandler.java:25)
 at weblogic.kernel.ExecuteThread.execute(ExecuteThread.java:219)
 at weblogic.kernel.ExecuteThread.run(ExecuteThread.java:178)


아마도 log4j 설정과 관련이 있을 것입니다.
web.xml 에서 log4j listener 설정 부분을 제거하라.


 <!--
 <listener>
  <listener-class>
   org.springframework.web.util.Log4jConfigListener
  </listener-class>
 </listener>
 -->

 

Spring에서 문자 인코딩 처리 :: 2007/07/04 19:36

스프링을 사용한 웹어플리케이션에서 JSP와 DB 의 인코딩 설정이 모두 정상적인데도,
폼에서 값을 입력하면 글자가 모두 깨져서 나올 때가 있다.

이럴 때는 웹어플리케이션의 web.xml에 필터를 설치하면 해결할 수 있다.
비단 EUC-KR 뿐만 아니라 UTF-8 등의 인코딩도 간단히 설정할 수가 있다.

설정법은 web.xml 에 다음과 같이 필터를 추가한다.

 <!-- 인코딩 필더 설치 -->
 <filter>
  <filter-name>encodingFilter</filter-name>
  <filter-class>
   org.springframework.web.filter.CharacterEncodingFilter
  </filter-class>
  <init-param>
   <param-name>encoding</param-name>
   <param-value>UTF-8</param-value>
  </init-param>
 </filter>

 <filter-mapping>
  <filter-name>encodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>