/* * Copyright 2008-2009 MOPAS(Ministry of Public Administration and Security). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package egovframework.rte.fdl.security.securedobject.impl; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import egovframework.rte.fdl.security.config.SecuredObjectConfig; import javax.annotation.PostConstruct; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.util.StringUtils; /** * DB기반의 보호된 자원 관리를 구현한 DAO 클래스 * * <p><b>NOTE:</b> DB 기반의 Secured Object 정보를 제공하기 위한 DAO 로 * default 쿼리를 제공하며 사용자 DB 에 맞는 각 유형의 DB 쿼리는 재설정 가능하다. * namedParameterJdbcTemplate 를 사용하여 DB 조회를 처리한다.</p> * * @author ByungHun Woo * @since 2009.06.01 * @version 1.0 * @see <pre> * == 개정이력(Modification Information) == * * 수정일 수정자 수정내용 * ------- -------- --------------------------- * 2009.06.01 윤성종 최초 생성 * 2014.01.22 한성곤 Spring Security 3.2.X 업그레이드 적용, 설정 간소화 처리 관련 변경 * 2014.07.03 한성곤 RegexRequestMatcher의 equals 미적용에 따른 Map 검색 문제 해결 (RegexRequestMatcher -> SelfRegexRequestMatcher 변경) * * </pre> */ public class SecuredObjectDAO implements ApplicationContextAware { private static final Logger LOGGER = LoggerFactory.getLogger(SecuredObjectDAO.class); /** * url 형식인 보호자원 - Role 맵핑정보를 조회하는 default 쿼리이다. */ public static final String DEF_ROLES_AND_URL_QUERY = "SELECT a.resource_pattern url, b.authority authority " + "FROM SECURED_RESOURCES a, SECURED_RESOURCES_ROLE b " + "WHERE a.resource_id = b.resource_id " + "AND a.resource_type = 'url' ORDER BY a.sort_order "; /** * method 형식인 보호자원 - Role 맵핑정보를 조회하는 default 쿼리이다. */ public static final String DEF_ROLES_AND_METHOD_QUERY = "SELECT a.resource_pattern method, b.authority authority " + "FROM SECURED_RESOURCES a, SECURED_RESOURCES_ROLE b " + "WHERE a.resource_id = b.resource_id " + "AND a.resource_type = 'method' ORDER BY a.sort_order "; /** * pointcut 형식인 보호자원 - Role 맵핑정보를 조회하는 default 쿼리이다. */ public static final String DEF_ROLES_AND_POINTCUT_QUERY = "SELECT a.resource_pattern pointcut, b.authority authority " + "FROM SECURED_RESOURCES a, SECURED_RESOURCES_ROLE b " + "WHERE a.resource_id = b.resource_id " + "AND a.resource_type = 'pointcut' ORDER BY a.sort_order "; /** * 매 request 마다 best matching url 보호자원 - Role 맵핑정보를 얻기위한 default 쿼리이다. * (Oracle 10g specific) */ public static final String DEF_REGEX_MATCHED_REQUEST_MAPPING_QUERY_ORACLE10G = "SELECT a.resource_pattern uri, b.authority authority " + "FROM secured_resources a, secured_resources_role b " + "WHERE a.resource_id = b.resource_id " + "AND a.resource_id = " + " ( SELECT resource_id FROM " + " ( SELECT resource_id, ROW_NUMBER() OVER (ORDER BY sort_order) resource_order FROM secured_resources c " + " WHERE REGEXP_LIKE ( :url, c.resource_pattern ) " + " AND c.resource_type = 'url' " + " ORDER BY c.sort_order ) " + " WHERE resource_order = 1 ) "; /** * Role 의 계층(Hierarchy) 관계를 조회하는 default 쿼리이다. */ public static final String DEF_HIERARCHICAL_ROLES_QUERY = "SELECT a.child_role child, a.parent_role parent " + "FROM ROLES_HIERARCHY a LEFT JOIN ROLES_HIERARCHY b on (a.child_role = b.parent_role) "; private String sqlRolesAndUrl; private String sqlRolesAndMethod; private String sqlRolesAndPointcut; private String sqlRegexMatchedRequestMapping; private String sqlHierarchicalRoles; private NamedParameterJdbcTemplate namedParameterJdbcTemplate; private ApplicationContext context; public SecuredObjectDAO() { this.sqlRolesAndUrl = DEF_ROLES_AND_URL_QUERY; this.sqlRolesAndMethod = DEF_ROLES_AND_METHOD_QUERY; this.sqlRolesAndPointcut = DEF_ROLES_AND_POINTCUT_QUERY; this.sqlRegexMatchedRequestMapping = DEF_REGEX_MATCHED_REQUEST_MAPPING_QUERY_ORACLE10G; this.sqlHierarchicalRoles = DEF_HIERARCHICAL_ROLES_QUERY; } public void setDataSource(DataSource dataSource) { this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); } public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = context; } @PostConstruct public void securedObjectConfigInit() { if (context.getBeanNamesForType(SecuredObjectConfig.class).length > 0) { SecuredObjectConfig config = context.getBean(SecuredObjectConfig.class); if (StringUtils.hasText(config.getSqlRolesAndUrl())) { sqlRolesAndUrl = config.getSqlRolesAndUrl(); } if (StringUtils.hasText(config.getSqlRolesAndMethod())) { sqlRolesAndMethod = config.getSqlRolesAndMethod(); } if (StringUtils.hasText(config.getSqlRolesAndPointcut())) { sqlRolesAndPointcut = config.getSqlRolesAndPointcut(); } if (StringUtils.hasText(config.getSqlRegexMatchedRequestMapping())) { sqlRegexMatchedRequestMapping = config.getSqlRegexMatchedRequestMapping(); } if (StringUtils.hasText(config.getSqlHierarchicalRoles())) { sqlHierarchicalRoles = config.getSqlHierarchicalRoles(); } } } /** * 롤에 대한 URL 정보를 가져오는 SQL을 얻어온다. * @return */ public String getSqlRolesAndUrl() { return sqlRolesAndUrl; } /** * 롤에대한 URL 정보를 가져오는 SQL을 설정한다. * @param sqlRolesAndUrl */ public void setSqlRolesAndUrl(String sqlRolesAndUrl) { this.sqlRolesAndUrl = sqlRolesAndUrl; } /** * 롤에 대한 메소드 정보를 가져오는 SQL을 얻어온다. * @return */ public String getSqlRolesAndMethod() { return sqlRolesAndMethod; } /** * 롤에 대한 메소드 정보를 가져오는 SQL을 설정한다. * @param sqlRolesAndMethod */ public void setSqlRolesAndMethod(String sqlRolesAndMethod) { this.sqlRolesAndMethod = sqlRolesAndMethod; } /** * 롤에 대한 포인트컷 정보를 가져오는 SQL을 얻어온다. * @return */ public String getSqlRolesAndPointcut() { return sqlRolesAndPointcut; } /** * 롤에 대한 포인트컷 정보를 가져오는 SQL을 설정한다. * @param sqlRolesAndPointcut */ public void setSqlRolesAndPointcut(String sqlRolesAndPointcut) { this.sqlRolesAndPointcut = sqlRolesAndPointcut; } /** * 매핑 정보를 가져오는 SQL을 얻어온다. * @return */ public String getSqlRegexMatchedRequestMapping() { return sqlRegexMatchedRequestMapping; } /** * 매핑 정보를 가져오는 SQL을 설정한다. * @param sqlRegexMatchedRequestMapping */ public void setSqlRegexMatchedRequestMapping(String sqlRegexMatchedRequestMapping) { this.sqlRegexMatchedRequestMapping = sqlRegexMatchedRequestMapping; } /** * 롤 계층구조 정보를 가져오는 SQL을 얻어온다. * @return */ public String getSqlHierarchicalRoles() { return sqlHierarchicalRoles; } /** * 롤 계층구조 정보를 가져오는 SQL을 설정한다. * @param sqlHierarchicalRoles */ public void setSqlHierarchicalRoles(String sqlHierarchicalRoles) { this.sqlHierarchicalRoles = sqlHierarchicalRoles; } /** * 리소스 유형에 대한 할당된 롤 정보를 가져온다. * * @param resourceType * @return * @throws Exception */ public LinkedHashMap<Object, List<ConfigAttribute>> getRolesAndResources(String resourceType, String requestMatcherType) throws Exception { LinkedHashMap<Object, List<ConfigAttribute>> resourcesMap = new LinkedHashMap<Object, List<ConfigAttribute>>(); String sqlRolesAndResources; boolean isResourcesUrl = true; if ("method".equals(resourceType)) { sqlRolesAndResources = getSqlRolesAndMethod(); isResourcesUrl = false; } else if ("pointcut".equals(resourceType)) { sqlRolesAndResources = getSqlRolesAndPointcut(); isResourcesUrl = false; } else { sqlRolesAndResources = getSqlRolesAndUrl(); } List<Map<String, Object>> resultList = this.namedParameterJdbcTemplate.queryForList(sqlRolesAndResources, new HashMap<String, String>()); Iterator<Map<String, Object>> itr = resultList.iterator(); Map<String, Object> tempMap; String preResource = null; String presentResourceStr; Object presentResource; while (itr.hasNext()) { tempMap = itr.next(); presentResourceStr = (String) tempMap.get(resourceType); // url 인 경우 RequestKey 형식의 key를 Map에 담아야 함 if (isResourcesUrl) { if (requestMatcherType.equalsIgnoreCase("regex")) { presentResource = new SelfRegexRequestMatcher(presentResourceStr, null); } else if (requestMatcherType.equalsIgnoreCase("ciRegex")) { presentResource = new SelfRegexRequestMatcher(presentResourceStr, null, true); } else { presentResource = new AntPathRequestMatcher(presentResourceStr); } } else { presentResource = presentResourceStr; } List<ConfigAttribute> configList = new LinkedList<ConfigAttribute>(); // 이미 requestMap 에 해당 Resource 에 대한 Role 이 하나 이상 맵핑되어 있었던 경우, // sort_order 는 resource(Resource) 에 대해 매겨지므로 같은 Resource 에 대한 Role 맵핑은 연속으로 조회됨. // 해당 맵핑 Role List (SecurityConfig) 의 데이터를 재활용하여 새롭게 데이터 구축 if (preResource != null && presentResourceStr.equals(preResource)) { List<ConfigAttribute> preAuthList = resourcesMap.get(presentResource); Iterator<ConfigAttribute> preAuthItr = preAuthList.iterator(); while (preAuthItr.hasNext()) { SecurityConfig tempConfig = (SecurityConfig) preAuthItr.next(); configList.add(tempConfig); } } configList.add(new SecurityConfig((String) tempMap.get("authority"))); // 만약 동일한 Resource 에 대해 한개 이상의 Role 맵핑 추가인 경우 // 이전 resourceKey 에 현재 새로 계산된 Role 맵핑 리스트로 덮어쓰게 됨. resourcesMap.put(presentResource, configList); // 이전 resource 비교위해 저장 preResource = presentResourceStr; } return resourcesMap; } public LinkedHashMap<Object, List<ConfigAttribute>> getRolesAndUrl(String requestMatcherType) throws Exception { return getRolesAndResources("url", requestMatcherType); } public LinkedHashMap<Object, List<ConfigAttribute>> getRolesAndMethod() throws Exception { return getRolesAndResources("method", null); } public LinkedHashMap<Object, List<ConfigAttribute>> getRolesAndPointcut() throws Exception { return getRolesAndResources("pointcut", null); } public List<ConfigAttribute> getRegexMatchedRequestMapping(String url) throws Exception { // best regex matching - best 매칭된 Uri 에 따른 Role List 조회, // DB 차원의 정규식 지원이 있는 경우 사용 (ex. hsqldb custom function, Oracle 10g regexp_like 등) Map<String, String> paramMap = new HashMap<String, String>(); paramMap.put("url", url); List<Map<String, Object>> resultList = this.namedParameterJdbcTemplate.queryForList(getSqlRegexMatchedRequestMapping(), paramMap); Iterator<Map<String, Object>> itr = resultList.iterator(); Map<String, Object> tempMap; List<ConfigAttribute> configList = new LinkedList<ConfigAttribute>(); // 같은 Uri 에 대한 Role 맵핑이므로 무조건 configList 에 add 함 while (itr.hasNext()) { tempMap = itr.next(); configList.add(new SecurityConfig((String) tempMap.get("authority"))); } if (configList.size() > 0) { LOGGER.debug("Request Uri : {}, matched Uri : {}, mapping Roles : {}", url, resultList.get(0).get("uri"), configList); } return configList; } public String getHierarchicalRoles() throws Exception { List<Map<String, Object>> resultList = this.namedParameterJdbcTemplate.queryForList(getSqlHierarchicalRoles(), new HashMap<String, String>()); Iterator<Map<String, Object>> itr = resultList.iterator(); StringBuffer concatedRoles = new StringBuffer(); Map<String, Object> tempMap; while (itr.hasNext()) { tempMap = itr.next(); concatedRoles.append(tempMap.get("child")); concatedRoles.append(" > "); concatedRoles.append(tempMap.get("parent")); concatedRoles.append("\n"); } return concatedRoles.toString(); } }