목차 >> Glue Security API 적용 
+- library 추가 
+- web.xml 수정 
+- security-context.xml 추가 
+- applicationContext.xml 수정 
+- security-service.xml 추가 
+- security-query.glue_sql 추가 
+- security-servlet.xml 추가

3장 Glue Security API 적용

Glue Security는 Manager모듈(화면)과 API모듈로 구성되어 있으며, 여기서는 API 모듈을 Glue Framework 기반의 Web Application에 적용하는 방법을 다룹니다.
Glue Security API 에서 제공하는 기능들은 Spring Security 기반에서 개발 되었습니다.
Glue(2014.10.24일 기준)에서 사용하는 Spring 버전은 다음과 같고, 버전 간에 호환 문제가 발생하지 않도록 의존물들을 확인해서 맞는 버전을 사용하시기 바랍니다.

  • Spring : 3.2.8.RELEASE
  • Spring Security : 3.2.4.RELEASE

GlueFramework 기반의 Web Application용 Glue Project는 다음과 같은 구조일 것이며, 제공되는 API모듈은 다음과 같이 구성되어 있습니다.

Glue Project 구조와 API 모듈

API 모듈은 다음 절차에 따라 적용합니다.

  1. library 추가
  2. web.xml 수정
  3. security-context.xml 추가
  4. applicationContext.xml 수정
  5. security-service.xml 추가
  6. security-query.glue_sql 추가
  7. security-servlet.xml 추가

library 추가

API 모듈은 GLUE_CORE에 포함시키며, API모듈의 의존 라이브러리는 GLUE_STD에 포함시킵니다.
GlueSDK에 추가할 Library는 다음과 같습니다.

library 설명 위치
glue-security-access-{version}.jar API 모듈 GLUE_CORE
jstl-1.2.jar JavaServer Pages Standard Tag Library GLUE_STD
spring-security-config-3.2.4.RELEASE.jar Spring Security GLUE_STD
spring-security-core-3.2.4.RELEASE.jar Spring Security GLUE_STD
spring-security-web-3.2.4.RELEASE.jar Spring Security GLUE_STD

그외 GlueSDK에 포함되어 있는 jackson-core-asl-1.9.12.jar와 jackson-mapper-asl-1.9.12.jar 도 사용되므로, Web Application에서 빠지지 않도록 합니다.

maven 을 사용하는 경우 pom.xml에 의존물들을 참고해서 dependency를 추가합니다.

web.xml 수정

web application의 web.xml 에 다음을 추가합니다.

<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>

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/security/security-context.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
    <servlet-name>security</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>security</servlet-name>
    <url-pattern>/SecurityReload</url-pattern>
</servlet-mapping>

web.xml에 <filter>가 있다면, filter는 그 순서에 유의해서 추가합니다.
이미 ContextLoaderListener를 사용하고 있다면, contextConfigLocation 의 <param-value>에 다음과 같이 security-context.xml 만 추가합니다.

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value> 
        /WEB-INF/some-context.xml
        /WEB-INF/security/security-context.xml
    </param-value>
</context-param>

web.xml 의 dispatcher servlet이 이미 있을 것이고, sercurity servlet을 추가 등록하였으니, url-pattern이 중복되지 않도록 유의합니다.

security-context.xml 추가

web.xml의 <context-param>에 지정된 파일이름을 근거로 security-context.xml 을 추가합니다. security-context.xml은 API모듈에 포함되어 있으며, 그 내용은 다음과 같습니다.

<?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:security="http://www.springframework.org/schema/security"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans.xsd
               http://www.springframework.org/schema/security
               http://www.springframework.org/schema/security/spring-security.xsd">
    <http auto-config="true" access-denied-page="/denied.jsp">
        <custom-filter ref="filterInvocationInterceptor" after="FILTER_SECURITY_INTERCEPTOR" />
        <custom-filter ref="gluefilter" before="FILTER_SECURITY_INTERCEPTOR" />
        <form-login login-page="/login.jsp" default-target-url="/menu.jsp" />
    </http>
    <beans:bean id="filterInvocationInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
        <beans:property name="observeOncePerRequest" value="false" />
        <beans:property name="securityMetadataSource" ref="glueInvocationDefinitionSource" />
        <beans:property name="authenticationManager" ref="authenticationManager" />
        <beans:property name="accessDecisionManager" ref="accessDecisionManager" />
    </beans:bean>

    <beans:bean id="glueInvocationDefinitionSource" class="com.poscoict.glueframework.security.bean.GlueInvocationDefinitionSource"/>
    <beans:bean id="gluefilter" class="com.poscoict.glueframework.security.filter.GlueServiceFilter"/>
    <beans:bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
        <beans:property name="allowIfAllAbstainDecisions" value="true" />
        <beans:property name="decisionVoters">
            <beans:list>
                <beans:bean class="org.springframework.security.access.vote.RoleVoter" />
            </beans:list>
        </beans:property>
    </beans:bean>
    <authentication-manager alias="authenticationManager">
        <authentication-provider user-service-ref="glueUserService" >
            <password-encoder hash="sha">
                <salt-source user-property="username"/>
            </password-encoder>
        </authentication-provider>
    </authentication-manager>
    <beans:bean id="glueUserService" class="com.poscoict.glueframework.security.bean.GlueUserDetailsService"/>
</beans:beans>

<http>의 access-denied-page 속성과 <form-login>의 login-page 와 default-target-url 속성은 그 값을 변경해서 사용할 수 있습니다.
위와 같은 예제에서는 denied.jsp, login.jsp, menu.jsp를 필요로 합니다.

다음은 login.jsp 예제입니다.

<%@ page contentType="text/html;charset=utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<title>Login Page</title>
</head>
<body>
    <font color="red">${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}</font>
    <br>
    <form action="j_spring_security_check" method="POST">
        아이디 : <input type="text" name="j_username" size="20" value="" /><br>
        비밀번호 : <input type="password" name="j_password" size="20" /><br>
        <input type="submit" value="Login" />
    </form>
</body>
</html>

다음은 denied.jsp 예제입니다

<%@ page contentType="text/html;charset=utf-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Denied Page</title>
    </head>
    <body>
접근 할 수 없는 Page 군요!!!<br>
<a href="<%=request.getContextPath()%>">확인</a><br/>
    </body>
</html>

applicationContext.xml 수정

security-context.xml의 gluefilter 와 glueUserService 빈은 viewAuthorityManager와 serviceAuthorityManager를 필요로 하므로 applicationContext.xml에 viewAuthorityManager와 serviceAuthorityManager를 추가합니다.

<bean id="securityDs" class=". . . ">
    . . . 중략 . . .
</bean>
<bean id="securityTx" 
      class="com.poscoict.glueframework.transaction.GlueDataSourceTransactionManager">
    <property name="dataSource" ref="securityDs"/>
</bean>
<bean id="securityDao" class="com.poscoict.glueframework.dao.jdbc.GlueJdbcDao">
    <property name="dataSource" ref="securityDs"/>
    <property name="queryManager" ref="queryManager"/>
</bean>

<bean id="viewAuthorityManager" 
      class="com.poscoict.glueframework.security.authority.GlueViewAuthorityDbManagerImpl">
    <property name="jdbcDao" ref="securityDao"/>
</bean>
<bean id="serviceAuthorityManager" 
      class="com.poscoict.glueframework.security.authority.GlueServiceAuthorityDbManagerImpl">
    <property name="jdbcDao" ref="securityDao"/>
</bean>

securityDs 는 2가지 유형중 한가지를 선택해서 사용하며, 유형에 따라 property 를 설정하도록 합니다.

security-service.xml 추가

applicationContext.xml에서 serviceLoader 를 찾아 extraServiceFiles property를 이용해서 security-service.xml를 추가합니다.

    <bean id="serviceLoader" class="com.poscoict.glueframework.biz.control.GlueServiceLoader">
        <property name="extraServiceFiles">
            <list>
                <value>ref/security-service.xml</value>
            </list>
        </property>
    </bean>
  • glue-core의 버전이 4.2.4 이하인 경우에는 API 모듈에 포함된 security-service.xml을 참고해서 service 폴더에 생성합니다. 그 내용은 다음과 같습니다.
    <?xml version="1.0" encoding="UTF-8"?>
    <service name="security-service" initial="Router" xmlns="http://www.poscoict.com/glueframework/service">
        <activity name="Router" class="com.poscoict.glueframework.biz.activity.GlueDefaultRouter">
            <transition name="getUserInfo" value="UserRouter"/>
            <transition name="success" value="getUrlPattern"/>
        </activity>
        <activity name="getUrlPattern" class="com.poscoict.glueframework.biz.activity.GlueJdbcSearch">
            <transition name="success" value="end"/>
            <property name="sql-key" value="security.urlRepository.select"/>
            <property name="dao" value="securityDao"/>
            <property name="param-count" value="0"/>
            <property name="result-key" value="ResourcePattern"/>
        </activity>
        <activity name="getUsers" class="com.poscoict.glueframework.biz.activity.GlueJdbcSearch">
            <transition name="success" value="getRoles"/>
            <property name="sql-key" value="security.userInfo.select"/>
            <property name="result-key" value="users"/>
            <property name="dao" value="securityDao"/>
            <property name="param-count" value="1"/>
            <property name="param0" value="user_id"/>
        </activity>
        <activity name="getRoles" class="com.poscoict.glueframework.biz.activity.GlueJdbcSearch">
            <transition name="success" value="end"/>
            <property name="param0" value="user_id"/>
            <property name="sql-key" value="security.role.select"/>
            <property name="dao" value="securityDao"/>
            <property name="param-count" value="1"/>
            <property name="result-key" value="roles"/>
        </activity>
        <activity name="UserRouter" class="com.poscoict.glueframework.biz.activity.GlueDefaultRouter">
            <transition name="crc_id" value="getCrcUsers"/>
            <transition name="success" value="getUsers"/>
        </activity>
        <activity name="getCrcUsers" class="com.poscoict.glueframework.biz.activity.GlueJdbcSearch">
            <transition name="success" value="getRoles"/>
            <property name="sql-key" value="security.userInfo.select.crc"/>
            <property name="dao" value="securityDao"/>
            <property name="param0" value="crc_id"/>
            <property name="param1" value="user_id"/>
            <property name="param-count" value="2"/>
            <property name="result-key" value="users"/>
        </activity>
    </service>
    

security-query.glue_sql 추가

applicationContext.xml에서 queryLoader 를 찾아 extraQueryFiles property를 이용해서 security-query.glue_sql를 추가합니다.

    <bean id="queryLoader" class="com.poscoict.glueframework.dao.manager.GlueQueryLoader">
        <property name="extraQueryFiles">
            <list>
                <value>ref/security-query.glue_sql</value>
            </list>
        </property>
    </bean>
  • glue-core의 버전이 4.2.4 이하인 경우에는 API 모듈에 포함된 security-query.glue_sql을 참고해서 query 폴더에 생성합니다. 그 내용은 다음과 같습니다.
    <?xml version="1.0" encoding="UTF-8"?>
    <queryMap desc="sample" xmlns="http://www.poscoict.com/glueframework/query">
        <query id="security.urlRepository.select" desc="" isNamed="false"
               resultType="com.poscoict.glueframework.security.vo.GlueResource">
            <![CDATA[
    SELECT R.RESOURCE_PATTERN, RR.ROLE_ID 
    FROM RESOURCES R, RESOURCES_ROLES RR 
    WHERE R.RESOURCE_ID=RR.RESOURCE_ID AND R.RESOURCE_TYPE='URL'
            ]]>
        </query>
        <query id="security.userInfo.select" desc="" isNamed="false"
               resultType="com.poscoict.glueframework.security.vo.GlueUser">
            <![CDATA[
    SELECT * FROM USERS WHERE USER_ID=?
            ]]>
        </query>
        <query id="security.role.select" desc="" isNamed="false"
               resultType="com.poscoict.glueframework.security.vo.GlueRole">
            <![CDATA[
    SELECT ROLE_ID 
    FROM GROUPS_ROLES 
    WHERE GROUP_ID IN (SELECT GROUP_ID FROM USERS WHERE USER_ID=?)
            ]]>
        </query>
        <query id="security.viewRoles.select" desc="" isNamed="false"
               resultType="com.poscoict.glueframework.security.vo.GlueViewRoles">
            <![CDATA[
    SELECT V.*, VR.ROLE_ID 
    FROM VIEW_AUTHORITY V, VIEW_ROLES VR 
    WHERE V.VIEW_ID=VR.VIEW_ID
            ]]>
        </query>
        <query id="security.userInfo.select.crc" desc="" isNamed="false"
               resultType="com.poscoict.glueframework.security.vo.GlueUser">
            <![CDATA[
    SELECT * FROM USERS WHERE CRC_ID=? AND USER_ID=?
            ]]>
        </query>
        <query id="security.serviceRoles.select" desc="" isNamed="false"
               resultType="com.poscoict.glueframework.security.vo.GlueResource">
            <![CDATA[
    SELECT R.RESOURCE_PATTERN, RR.ROLE_ID 
    FROM RESOURCES R, RESOURCES_ROLES RR 
    WHERE R.RESOURCE_ID=RR.RESOURCE_ID AND R.RESOURCE_TYPE='SERVICE'
            ]]>
        </query>
    </queryMap>
    

security-servlet.xml 추가

web.xml 에 다음과 같이 security servlet이 추가되어 있으므로 WEB-INF 밑에 security-servlet.xml 을 추가합니다.

<servlet>
    <servlet-name>security</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>security</servlet-name>
    <url-pattern>/SecurityReload</url-pattern>
</servlet-mapping>

추가할 security-servlet.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:dwr="http://www.directwebremoting.org/schema/spring-dwr"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
        http://www.directwebremoting.org/schema/spring-dwr
        http://www.directwebremoting.org/schema/spring-dwr-3.0.xsd">
    <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <value>
                /SecurityReload=securityController
            </value>
        </property>
    </bean>
    <bean name="securityController" class="com.poscoict.glueframework.security.controller.GlueSecurityResourceReload"/>
    <bean id="viewResolver" name="jsonView" class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/>
</beans>