commit cbbd2497f41423d62d46dfe1157eb2f37ec8ce83 Author: xit Date: Tue May 2 13:12:37 2023 +0900 최초 커밋 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..7f8bde8 --- /dev/null +++ b/pom.xml @@ -0,0 +1,204 @@ + + 4.0.0 + + cokr.xit + xit-security + 23.04.01 + jar + + xit-security + http://maven.apache.org + + + UTF-8 + + 1.8 + ${java.version} + ${java.version} + + 5.3.20 + 4.1.0 + + + + + mvn2s + https://repo1.maven.org/maven2/ + + true + + + true + + + + egovframe + http://maven.egovframe.kr:8080/maven/ + + true + + + false + + + + egovframe2 + https://www.egovframe.go.kr/maven/ + + true + + + false + + + + + + + org.egovframe.rte + org.egovframe.rte.fdl.security + ${org.egovframe.rte.version} + + + + cokr.xit + xit-user + 23.04.01 + + + + org.mariadb.jdbc + mariadb-java-client + 2.7.2 + test + + + + + + install + ${basedir}/target + ${project.artifactId}-${project.version} + + + ${basedir}/src/main/resources + + + + ${basedir}/src/test/resources + ${basedir}/src/main/resources + + + + + + org.apache.tomcat.maven + tomcat7-maven-plugin + 2.2 + + 80 + / + + -Xms256m -Xmx768m -XX:MaxPermSize=256m + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + UTF-8 + + + + org.codehaus.mojo + hibernate3-maven-plugin + 2.1 + + + + hbm2ddl + annotationconfiguration + + + + + + org.hsqldb + hsqldb + 2.3.2 + + + + + + org.codehaus.mojo + emma-maven-plugin + 1.0-alpha-3 + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.1 + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + **/*.class + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0 + + true + xml + + **/Abstract*.java + **/*Suite.java + + + **/*Test.java + + + + + org.codehaus.mojo + emma-maven-plugin + true + + + org.apache.maven.plugins + maven-source-plugin + 2.2 + + + attach-sources + + jar + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + + + \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/Authority.java b/src/main/java/cokr/xit/base/security/Authority.java new file mode 100644 index 0000000..09efd99 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/Authority.java @@ -0,0 +1,234 @@ +package cokr.xit.base.security; + +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.springframework.security.core.GrantedAuthority; + +import cokr.xit.foundation.Assert; +import cokr.xit.foundation.data.DataObject; +import cokr.xit.foundation.data.StringMap; + +/**권한 정보, 사용자의 역할(Role) + * @author mjkhan + */ +public class Authority implements GrantedAuthority { + private static final long serialVersionUID = 1L; + + /**권한 유형 + * @author mjkhan + */ + public static enum Type { + /** 수퍼 권한: 시스템 관리자 권한 */ + SUPER, + /** 내재적 권한: 시스템 사용자 권한, 로그인하지 않은 사용자 권한 */ + IMPLICIT, + /** 시스템 정의 권한 */ + SYS_DEFINED; + + /**현재 권한 유형이 내장된 유형(SUPER, IMPLICIT)인지 반환한다. + * @return 권한 유형의 내장 여부 + * + */ + public boolean isBuiltIn() { + return !SYS_DEFINED.equals(this); + } + } + + private int type; + private String + id, + name, + description, + infoScope, + userInfoScope; + private List actions; + private Date createdAt; + + /**{@link Type 권한 유형} 코드를 반환한다. + * @return 권한 유형 코드 + */ + public int getType() { + return type; + } + + /**{@link Type 권한 유형}을 반환한다. + * @return 권한 유형 + */ + public Type type() { + return Type.values()[type]; + } + + @Override + public String getAuthority() { + return getId(); + } + + /**권한 id를 반환한다. + * @return 권한 id + */ + public String getId() { + return id; + } + + /**권한 id를 설정한다. + * @param id 권한 id + */ + public Authority setId(String id) { + this.id = id; + return this; + } + + /**권한 이름을 반환한다. + * @return 권한 이름 + */ + public String getName() { + return name; + } + + /**권한 이름을 설정한다. + * @param name 권한 이름 + */ + public Authority setName(String name) { + this.name = name; + return this; + } + + /**설명을 반환한다. + * @return 설명 + */ + public String getDescription() { + return description; + } + + /**설명을 설정한다. + * @param description 설명 + */ + public void setDescription(String description) { + this.description = description; + } + + /**접근할 수 있는 업무정보 범위를 반환한다. + * @return 접근할 수 있는 업무정보 범위 + * + */ + public String getInfoScope() { + return infoScope; + } + + /**접근할 수 있는 업무정보 범위를 설정한다. + * @param infoScope 접근할 수 있는 업무정보 범위 + * + */ + public Authority setInfoScope(String infoScope) { + this.infoScope = infoScope; + return this; + } + + /**접근할 수 있는 사용자정보 범위를 반환한다. + * @return 접근할 수 있는 사용자정보 범위 + * + */ + public String getUserInfoScope() { + return userInfoScope; + } + + /**접근할 수 있는 사용자정보 범위를 설정한다. + * @param userInfoScope 접근할 수 있는 사용자정보 범위 + * + */ + public Authority setUserInfoScope(String userInfoScope) { + this.userInfoScope = userInfoScope; + return this; + } + + /**등록 일시를 반환한다. + * @return 등록 일시 + */ + public Date getCreatedAt() { + return createdAt; + } + + /**등록 일시를 설정한다. + * @param createdAt 등록 일시 + */ + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + /**실행기능(URL)을 반환한다. + * @return 실행기능(URL) + */ + public List getActions() { + return actions != null ? actions : Collections.emptyList(); + } + + /**실행기능(URL)을 설정한다. + * @param actions 실행기능(URL) + */ + public void setActions(List actions) { + this.actions = actions; + } + + private StringMap infoScopes; + + /**정보 접근 범위를 반환한다. + * @return 정보 접근 범위 + */ + public Map getInfoScopes() { + return Assert.ifEmpty(infoScopes, Collections::emptyMap); + } + + /**정보 접근 범위를 설정한다. + * @param infoScopes 정보 접근 범위 + */ + public void setInfoScopes(List infoScopes) { + if (this.infoScopes != null) + this.infoScopes.clear(); + if (Assert.isEmpty(infoScopes)) + return; + + if (this.infoScopes == null) + this.infoScopes = new StringMap<>(); + + infoScopes.forEach(row -> this.infoScopes.put(row.string("INF_TYPE"), row.string("INF_SCP"))); + } + + /**지정하는 정보유형의 접근 범위를 반환한다. + * @param infoType 정보 유형 + * @return 접근 범위 + */ + public String getInfoScope(String infoType) { + return Assert.ifEmpty(infoScopes != null ? infoScopes.get(infoType) : null, () -> "none"); + } + + /**action을 실행할 권한이 있는지 반환한다. + * @param action 실행할 기능(URL) + * @return + *
  • 기능을 실행할 권한이 있으면 true
  • + *
  • 기능을 실행할 권한이 없으면 false
  • + *
+ */ + public boolean isGranted(String action) { + if (Type.SUPER.equals(type())) return true; + + return getActions().contains(action); + } + + @Override + public String toString() { + return String.format("%s{id:'%s', name:'%s'}", getClass().getSimpleName(), getId(), getName()); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/SecuredUserInfo.java b/src/main/java/cokr/xit/base/security/SecuredUserInfo.java new file mode 100644 index 0000000..b2d4b3b --- /dev/null +++ b/src/main/java/cokr/xit/base/security/SecuredUserInfo.java @@ -0,0 +1,163 @@ +package cokr.xit.base.security; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import cokr.xit.foundation.UserInfo; + +/**현재 접속한 사용자의 정보를 갖는 객체.
+ * 권한 정보를 추가하여, 사용자가 특정 기능을 실행할 권한이 있는지 제공한다. + */ +public class SecuredUserInfo extends UserInfo implements UserDetails { + private static final long serialVersionUID = 1L; + + /**현재 접속한 사용자의 정보를 찾는 객체 + * @author mjkhan + */ + @Component(UserInfo.SEC_NAME) + public static class Provider extends UserInfo.Provider { + @Override + public UserInfo currentUser() { + return userInfo == null ? get(SecurityContextHolder.getContext().getAuthentication()) : (UserInfo)userInfo; + } + + private UserInfo get(Authentication authentication) { + Object obj = authentication != null ? authentication.getPrincipal() : null; + return obj instanceof SecuredUserInfo ? (UserInfo)obj : SecuredUserInfo.unknown; + } + + @Override + public void setUserInfo(UserInfo userInfo) { + if (!(userInfo instanceof SecuredUserInfo)) + throw new IllegalArgumentException("userInfo is not a SecuredUserInfo"); + super.setUserInfo(userInfo); + } + } + + /**인증되지 않은 사용자 정보 + */ + public static final SecuredUserInfo unknown = new SecuredUserInfo().seal(); + + private List authorities; + private boolean sealed; + + @Override + public List getAuthorities() { + return authorities != null ? authorities : Collections.emptyList(); + } + + /**사용자에게 허용된 권한을 설정한다. + * @param authorities 허용된 권한 목록 + */ + public void setAuthorities(List authorities) { + notSealed().authorities = authorities; + } + + /**사용자에게 action을 실행할 권한이 있는지 반환한다. + * @param action 실행할 기능(URL) + * @return + *
  • 기능을 실행할 권한이 있으면 true
  • + *
  • 기능을 실행할 권한이 없으면 false
  • + *
+ */ + public boolean isGranted(String action) { + if (authorities != null && !authorities.isEmpty()) { + for (Authority authority: authorities) { + if (authority.isGranted(action)) + return true; + } + } + return false; + } + + /**사용자가 지정한 아이디의 권한이 있는지 반환한다. + * @param authIDs 권한 아이디 + * @return + *
  • 지정한 아이디의 권한이 있으면 true
  • + *
  • 지정한 아이디의 권한이 없으면 false
  • + *
+ */ + public boolean hasAuthorities(String... authIDs) { + if (authIDs == null || authIDs.length < 1) return false; + + for (Authority authority: getAuthorities()) { + String authorityID = authority.getId(); + for (String authID: authIDs) { + if (authorityID.equals(authID)) + return true; + } + } + + return false; + } + + @Override + public List getAccessibleActions() { + List authorities = getAuthorities(); + boolean admin = authorities.stream() + .filter(auth -> Authority.Type.SUPER.equals(auth.type())) + .count() > 0; + if (admin) { + return ALL_ACTIONS; + } else { + return authorities.stream() + .flatMap(auth -> auth.getActions().stream()) + .distinct() + .collect(Collectors.toList()); + } + } + + /**지정하는 아이디의 권한의 정보접근 범위를 반환한다. + * @param authID 권한 아이디 + * @return 지정하는 아이디의 권한의 정보접근 범위 + */ + public String getInfoScope(String authID) { + for (Authority authority: getAuthorities()) { + if (authority.getId().equals(authID)) + return authority.getInfoScope(); + } + return ""; + } + + @Override + public boolean isAccountNonExpired() { + return isEnabled(); + } + + @Override + public boolean isAccountNonLocked() { + return isEnabled(); + } + + @Override + public boolean isCredentialsNonExpired() { + return isEnabled(); + } + + @Override + public boolean isEnabled() { + return true; + } + + private SecuredUserInfo seal() { + sealed = true; + return this; + } + + private SecuredUserInfo notSealed() { + if (sealed) + throw new IllegalStateException(this + " is sealed"); + return this; + } + + @Override + public String toString() { + return String.format("%s('%s', '%s')", getClass().getSimpleName(), getId(), getName()); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/AccessContext.java b/src/main/java/cokr/xit/base/security/access/AccessContext.java new file mode 100644 index 0000000..ef92a9b --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/AccessContext.java @@ -0,0 +1,95 @@ +package cokr.xit.base.security.access; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import javax.annotation.Resource; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import cokr.xit.base.security.Authority; +import cokr.xit.base.security.access.dao.AuthorityMapper; +import cokr.xit.foundation.AbstractComponent; + +/**애플리케이션이 정의하거나 등록한 권한과 각 권한이 실행할 수 있는 기능(URL)들을 로드하여 + * 사용자의 권한 체크에 정보를 제공한다.
+ * AccessContext는 애플리케이션이 로드될 때 이 정보들을 가져온다. + * @author mjkhan + */ +@Component("accessContext") +public class AccessContext extends AbstractComponent implements ApplicationContextAware { + /** 권한 인덱스 */ + protected Map authorityIndex; + /** 내재적 권한 목록 */ + protected List implicits; + /** 권한 정보 DAO */ + @Resource(name="authorityMapper") + protected AuthorityMapper authorityMapper; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + loadAuthorities(); + } + + /**애플리케이션의 모든 권한과 각 권한이 실행할 수 있는 기능(URL)을 로드한다. + */ + public void loadAuthorities() { + List authorities = authorityMapper.getAuthorities(); + authorityIndex = authorities.stream() + .collect(Collectors.toMap(Authority::getId, auth -> auth)); + if (authorityIndex.isEmpty()) return; + + implicits = authorities.stream() + .filter(authority -> Authority.Type.IMPLICIT.equals(authority.type())) + .collect(Collectors.toList()); + + authorityMapper.getActionList().stream() + .collect(Collectors.groupingBy(row -> row.string("AUTH_ID"))) + .forEach((authID, actions) -> { + List actionList = actions.stream() + .map(action -> action.string("ACTION")) + .collect(Collectors.toList()); + Authority authority = authorityIndex.get(authID); + if (authority != null) + authority.setActions(actionList); + }); + } + + /**지정하는 권한들을 반환한다. + * @param authIDs 권한 아이디 + * @return 권한 목록 + */ + public List getAuthorities(List authIDs) { + if (isEmpty(authIDs) || isEmpty(authorityIndex)) + return Collections.emptyList(); + + return authorityIndex.entrySet().stream() + .filter(entry -> authIDs.contains(entry.getKey())) + .map(Entry::getValue) + .collect(Collectors.toList()); + } + + /**내재적 권한 목록을 반환한다. + * @return 내재적 권한 목록 + */ + public List getImplicits() { + return ifEmpty(implicits, Collections::emptyList); + } + + /**지정하는 권한이 실행할 수 있는 기능(URL)을 반환한다. + * @param authIDs 권한 아이디 + * @return 실행 기능 목록 + */ + public List getActions(List authIDs) { + return getAuthorities(authIDs).stream() + .flatMap(authority -> authority.getActions().stream()) + .distinct() + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/ActionGroup.java b/src/main/java/cokr/xit/base/security/access/ActionGroup.java new file mode 100644 index 0000000..b8ae813 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/ActionGroup.java @@ -0,0 +1,75 @@ +package cokr.xit.base.security.access; + +import java.util.Date; + +/**기능 그룹 + * @author mjkhan + */ +public class ActionGroup { + private String + id, + name, + description; + private Date createdAt; + + /**id를 반환한다. + * @return id + */ + public String getId() { + return id; + } + + /**id를 설정한다. + * @param id id + */ + public void setId(String id) { + this.id = id; + } + + /**이름을 반환한다. + * @return 이름 + */ + public String getName() { + return name; + } + + /**이름을 설정한다. + * @param name 이름 + */ + public void setName(String name) { + this.name = name; + } + + /**설명을 반환한다. + * @return 설명 + */ + public String getDescription() { + return description; + } + + /**설명을 설정한다. + * @param description 설명 + */ + public void setDescription(String description) { + this.description = description; + } + + /**등록 일자를 반환한다. + * @return 등록 일자 + */ + public Date getCreatedAt() { + return createdAt; + } + + /**등록 일자를 설정한다. + * @param createdAt 등록 일자 + */ + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + @Override + public String toString() { + return String.format("%s('%s', '%s')", getClass().getSimpleName(), getId(), getName()); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/ApplicationAccess.java b/src/main/java/cokr/xit/base/security/access/ApplicationAccess.java new file mode 100644 index 0000000..4dc8aee --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/ApplicationAccess.java @@ -0,0 +1,64 @@ +package cokr.xit.base.security.access; + +import java.util.Collection; + +import javax.annotation.Resource; + +import org.springframework.security.access.AccessDecisionVoter; +import org.springframework.security.access.ConfigAttribute; +import org.springframework.security.core.Authentication; + +import cokr.xit.base.security.Authority; +import cokr.xit.base.security.SecuredUserInfo; +import cokr.xit.foundation.AbstractComponent; +import cokr.xit.foundation.Access; + +/**애플리케이션 접근 제어자.
+ * 사용자가 애플리케이션의 기능을 실행할 수 있는지 판단한다. + * @author mjkhan + */ +public class ApplicationAccess extends AbstractComponent implements AccessDecisionVoter { + private boolean controlAccess; + /** 권한별 가용 기능(URL) */ + @Resource(name="accessContext") + protected AccessContext accessContext; + + /**접근 제어를 사용할지 설정한다. + * @param controlAccess 접근 제어 사용 여부 + *
  • 접근 제어를 사용하면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public void setControlAccess(boolean controlAccess) { + this.controlAccess = controlAccess; + } + + @Override + public boolean supports(ConfigAttribute attribute) { + return true; + } + + @Override + public boolean supports(Class clazz) { + return true; + } + + @Override + public int vote(Authentication authentication, Object obj, Collection attributes) { + if (!controlAccess) return ACCESS_GRANTED; + + String action = Access.current().getAction(); + if (isEmpty(action)) + return ACCESS_GRANTED; + + SecuredUserInfo userInfo = currentUser(); + if (userInfo.isGranted(action)) + return ACCESS_GRANTED; + + for (Authority authority: accessContext.getImplicits()) + if (authority.isGranted(action)) + return ACCESS_GRANTED; + + return ACCESS_DENIED; + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/dao/ActionGroupMapper.java b/src/main/java/cokr/xit/base/security/access/dao/ActionGroupMapper.java new file mode 100644 index 0000000..25628c0 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/dao/ActionGroupMapper.java @@ -0,0 +1,150 @@ +package cokr.xit.base.security.access.dao; + +import java.util.List; +import java.util.Map; + +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +import cokr.xit.base.security.access.ActionGroup; +import cokr.xit.base.security.access.service.ActionQuery; +import cokr.xit.foundation.component.AbstractMapper; +import cokr.xit.foundation.data.DataObject; + +/**기능 그룹 관리 DAO + * @author mjkhan + */ +@Mapper("actionGroupMapper") +public interface ActionGroupMapper extends AbstractMapper { + /**주어진 조건에 따라 기능 그룹 목록을 조회한다. + * @param by 조건 필드 이름 + *
  • actionGroupName - 기능 그룹 이름
  • + *
  • 그 외는 기능 그룹 아이디
  • + *
+ * @param term 조건 필드값 + * @param pageNum 시작 페이지 번호 + * @param fetchSize 한 번에 가져올 기능 그룹수 + * @return 기능 그룹 목록 + */ + List getGroupList(ActionQuery req); + + /**지정하는 기능 그룹을 가져온다. + * @param groupIDs 기능 그룹 아이디 + * @return 기능 그룹 + */ + List getGroups(ActionQuery req); + + /**지정하는 기능 그룹을 가져온다. + * @param groupIDs 기능 그룹 아이디 + * @return 기능 그룹 + */ + default List getGroups(String... groupIDs) { + ActionQuery req = new ActionQuery().setGroupIDs(groupIDs); + return getGroups(req); + } + + /**지정하는 기능 그룹을 가져온다. + * @param groupID 기능 그룹 아이디 + * @return 기능 그룹 + */ + default ActionGroup getGroup(String groupID) { + List groups = getGroups(groupID); + return !groups.isEmpty() ? groups.get(0) : null; + } + + /**기능 그룹을 등록한다. + * @param group 기능 그룹 + * @return 등록 여부 + *
  • 등록됐으면 true
  • + *
  • 그렇지 않으며 false
  • + *
+ */ + int insertGroup(ActionGroup group); + + /**기능 그룹을 수정한다. + * @param group 기능 그룹 + * @return 수정 여부 + *
  • 수정됐으면 true
  • + *
  • 그렇지 않으며 false
  • + *
+ */ + int updateGroup(ActionGroup group); + + /**지정하는 기능 그룹을 제거한다. + * @param groupIDs 기능 그룹 아이디 + * @return 저장된 정보수 + */ + int removeGroups(Map params); + + /**지정하는 기능 그룹을 제거한다. + * @param groupIDs 기능 그룹 아이디 + * @return 저장된 정보수 + */ + default int removeGroups(String... groupIDs) { + if (isEmpty(groupIDs)) return 0; + + removeActions(groupIDs, null); + + DataObject params = params() + .set("groupIDs", ifEmpty(groupIDs, () -> null)) + .set("currentUser", currentUser()); + return removeGroups(params); + } + + /**지정하는 그룹의 기능 목록을 조회한다. + * @param groupIDs 기능 그룹 아이디 + * @return 기능 목록 + */ + List getActionList(ActionQuery req); + + /**지정하는 그룹의 기능 목록을 조회한다. + * @param groupIDs 기능 그룹 아이디 + * @return 기능 목록 + */ + default List getActionList(String... groupIDs) { + return getActionList(new ActionQuery().setGroupIDs(groupIDs)); + } + + /**지정하는 기능 그룹에 기능들을 추가한다. + * @param groupID 그룹 아이디 + * @param actions 기능(URL) + * @return 저장된 정보수 + */ + int addActions(Map params); + + /**지정하는 기능 그룹에 기능들을 추가한다. + * @param groupID 그룹 아이디 + * @param actions 기능(URL) + * @return 저장된 정보수 + */ + default int addActions(String groupID, String... actions) { + if (isEmpty(groupID) || isEmpty(actions)) return 0; + + DataObject params = params() + .set("groupID", groupID) + .set("actions", ifEmpty(actions, () -> null)) + .set("currentUser", currentUser()); + return addActions(params); + } + + /**지정하는 기능 그룹의 기능을 제거한다. + * @param groupID 기능 그룹 아이디 + * @param actions 기능 + * @return 저장된 정보수 + */ + int removeActions(Map params); + + /**지정하는 기능 그룹의 기능을 제거한다. + * @param groupIDs 기능 그룹 아이디 + * @param actions 기능 + * @return 저장된 정보수 + */ + default int removeActions(String[] groupIDs, String[] actions) { + if (isEmpty(groupIDs) && isEmpty(actions)) return 0; + + DataObject params = params() + .set("groupIDs", ifEmpty(groupIDs, () -> null)) + .set("actions", ifEmpty(actions, () -> null)) + .set("currentUser", currentUser()); + return removeActions(params); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/dao/AuthorityMapper.java b/src/main/java/cokr/xit/base/security/access/dao/AuthorityMapper.java new file mode 100644 index 0000000..2f2685d --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/dao/AuthorityMapper.java @@ -0,0 +1,240 @@ +package cokr.xit.base.security.access.dao; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; + +import cokr.xit.base.security.Authority; +import cokr.xit.base.security.access.service.AuthorityQuery; +import cokr.xit.foundation.component.AbstractMapper; +import cokr.xit.foundation.data.DataObject; + +/**권한 정보 관리 DAO + * @author mjkhan + */ +@Mapper("authorityMapper") +public interface AuthorityMapper extends AbstractMapper { + /**주어진 조건의 권한 목록을 조회한다. + * @return 권한 목록 + */ + List getAuthorityList(AuthorityQuery req); + + /**주어진 조건의 권한들을 가져온다. + * @return 권한 목록 + */ + List getAuthorities(AuthorityQuery req); + + /**지정하는 아이디의 권한들을 가져온다. + * @param authIDs 권한 아이디 + * @return 권한 목록 + */ + default List getAuthorities(String... authIDs) { + return getAuthorities(new AuthorityQuery().setAuthIDs(authIDs)); + } + + /**지정하는 아이디의 권한을 가져온다. + * @param authID 권한 아이디 + * @return 권한 + */ + default Authority getAuthority(String authID) { + List authorities = getAuthorities(authID); + return !authorities.isEmpty() ? authorities.get(0) : null; + } + + /**권한을 등록한다. + * @param authority 권한 + * @return 저장된 정보수 + */ + int insertAuthority(Authority authority); + + /**권한을 수정한다. + * @param authority 권한 + * @return 저장된 정보수 + */ + int updateAuthority(Authority authority); + + /**지정하는 조건의 권한을 삭제한다. + * @param params 삭제 조건 + *
 {
+	 *     "authIDs": 권한 아이디
+	 * }
+ * @return + */ + int removeAuthorities(Map params); + + /**지정하는 권한들을 삭제한다. + * @param authIDs 권한 아이디 + * @return 저장된 정보수 + */ + default int removeAuthorities(String... authIDs) { + List authorities = getAuthorities(authIDs); + if (authorities.stream().filter(authority -> authority.type().isBuiltIn()).count() > 0) + throw new RuntimeException("내장된 권한은 삭제할 수 없습니다."); + + removeActionGroups(authIDs, null); + removeUsers(authIDs, null); + return removeAuthorities(params().set("authIDs", authIDs)); + } + + /**지정하는 권한의 기능 그룹 목록을 조회한다. + * @param pageNum 시작 페이지 번호 + * @param fetchSize 한 번에 가져올 기능 그룹수 + * @param authIDs 권한 아이디 + * @return 권한별 기능 그룹 목록 + */ + List getActionGroupList(AuthorityQuery req); + + /**지정하는 권한에 기능 그룹들을 추가한다. + * @param params 권한, 기능그룹 아이디 + *
 {
+	 *     "authID": 권한 아이디,
+	 *     "groupIDs": 기능그룹 아이디(배열)
+	 * }
+ * @return 저장된 정보수 + */ + int addActionGroups(Map params); + + /**지정하는 권한에 기능 그룹들을 추가한다. + * @param authID 권한 아이디 + * @param groupIDs 기능 그룹 아이디 + * @return 저장된 정보수 + */ + default int addActionGroups(String authID, String... groupIDs) { + return addActionGroups( + params() + .set("authID", authID) + .set("groupIDs", groupIDs) + ); + } + + /**지정한 권한의 기능 그룹들을 삭제한다. + * @param params 삭제 조건 + *
 {
+	 *     "authIDs": 권한 아이디(배열),
+	 *     "groupIDs": 기능그룹 아이디(배열)
+	 * }
+ * @return 저장된 정보수 + */ + int removeActionGroups(Map params); + + /**지정한 권한의 기능 그룹들을 삭제한다. + * @param authIDs 권한 아이디 + * @param groupIDs 기능 그룹 아이디 + * @return 저장된 정보수 + */ + default int removeActionGroups(String[] authIDs, String[] groupIDs) { + DataObject params = params() + .set("authIDs", ifEmpty(authIDs, () -> null)) + .set("groupIDs", ifEmpty(groupIDs, () -> null)); + return removeActionGroups(params); + } + + /**지정하는 권한에서 기능 그룹들을 삭제한다.
+ * 기능 그룹을 지정하지 않으면 해당 권한의 모든 기능 그룹을 삭제한다. + * @param authID 권한 아이디 + * @param groupIDs 기능 그룹 아이디 + * @return 저장된 정보수 + */ + default int removeActionGroups(String authID, String... groupIDs) { + return removeActionGroups(new String[] {authID}, groupIDs); + } + + /**지정하는 기능 그룹들을 삭제한다. + * @param groupIDs 기능 그룹 아이디 + * @return 저장된 정보수 + */ + default int removeActionGroups(String... groupIDs) { + return removeActionGroups((String[])null, groupIDs); + } + + /**권한별 기능 그룹, 기능 목록을 가져온다. + * @return 권한별 기능 그룹, 기능 목록 + */ + List getActionList(); + + /**지정하는 권한의 사용자를 조회한다. + * @param pageNum 시작 페이지 번호 + * @param fetchSize 한 번에 가져올 사용자 정보수 + * @param authIDs 권한 아이디 + * @return 사용자 목록 + */ + List getUserList(AuthorityQuery req); + + /**지정하는 사용자의 권한 목록을 조회한다. + * @param params 사용자 조회 조건 + *
 {
+	 *     "userIDs": 사용자 아이디(배열)
+	 * }
+ * @return 권한 목록 + */ + List getUserAuths(Map params); + + /**지정하는 사용자의 권한 목록을 조회한다. + * @param userID 사용자 아이디 + * @return 권한 목록 + */ + default List getUserAuths(String userID) { + List list = getUserAuths(params().set("userIDs", Arrays.asList(userID))); + return list.stream() + .map(row -> row.string("AUTH_ID")) + .collect(Collectors.toList()); + } + + /**지정하는 권한에 사용자를 추가한다. + * @param authID 권한 아이디 + * @param userIDs 사용자 아이디 + * @return 저장된 정보수 + */ + int addUsers(Map params); + + /**지정하는 권한에 사용자를 추가한다. + * @param authID 권한 아이디 + * @param userIDs 사용자 아이디 + * @return 저장된 정보수 + */ + default int addUsers(String authID, String... userIDs) { + return addUsers( + params() + .set("authID", authID) + .set("userIDs", userIDs) + ); + } + + /**지정하는 권한에서 사용자를 제거한다.
+ * 사용자를 지정하지 않으면 해당 권한의 모든 사용자를 제거한다. + * @param params 사용자 제거 조건 + *
 {
+	 *     "authIDs": 권한 아이디(배열),
+	 *     "groupIDs": 기능그룹 아이디(배열)
+	 * }
+ * @return 저장된 정보수 + */ + int removeUsers(Map params); + + /**지정하는 권한에서 사용자를 제거한다.
+ * 사용자를 지정하지 않으면 해당 권한의 모든 사용자를 제거한다. + * @param authIDs 권한 아이디 + * @param userIDs 사용자 아이디 + * @return 저장된 정보수 + */ + default int removeUsers(String[] authIDs, String[] userIDs) { + return removeUsers( + params() + .set("authIDs", ifEmpty(authIDs, () -> null)) + .set("userIDs", ifEmpty(userIDs, () -> null)) + ); + } + + /**지정하는 권한에서 사용자를 제거한다.
+ * 사용자를 지정하지 않으면 해당 권한의 모든 사용자를 제거한다. + * @param authID 권한 아이디 + * @param userIDs 사용자 아이디 + * @return 저장된 정보수 + */ + default int removeUsers(String authID, String... userIDs) { + return removeUsers(new String[] {authID}, userIDs); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/dao/package-info.java b/src/main/java/cokr/xit/base/security/access/dao/package-info.java new file mode 100644 index 0000000..246e0fb --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/dao/package-info.java @@ -0,0 +1,3 @@ +/**사용자 접근 정보 테이블 접근 객체. + */ +package cokr.xit.base.security.access.dao; \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/package-info.java b/src/main/java/cokr/xit/base/security/access/package-info.java new file mode 100644 index 0000000..0f9cc01 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/package-info.java @@ -0,0 +1,23 @@ +/**사용자 접근 관리. + *
  • 사용자 접근 관리의 체계는 다음과 같다.
    + * 접근 관리 엔티티
  • + *
  • 그룹별 기능이란 애플리케이션이 제공하는 기능(URL)을 말한다. 다음은 그 예들이다. + *
    • /notice/list.do?...
    • + *
    • /notice/info.do?...
    • + *
    • /notice/process.do
    • + *
    • /notice/cancel.do
    • + *
  • + *
  • 기능 그룹이란 기능들을 특성에 따라 그룹으로 묶은 것을 말한다. 다음은 그 예들이다. + *
    • "notice-read" 그룹 - {"/notice/list.do?...", "/notice/info.do?..."}
    • + *
    • "notice-process" 그룹 - {"/notice/process.do", "/notice/cancel.do"}
    • + *
  • + *
  • 권한(Authority)이란 사용자에게 부여되는 역할을 말한다.
    + * 예) "ROLE_NOTICE" - 고지 담당자
  • + *
  • 권한은 여러 개의 기능 그룹을 부여받으며, 이를 권한별 기능 그룹이라 한다.
    + * 예) "ROLE_NOTICE" - {"notice-read", "notice-process"}
  • + *
  • 사용자는 여러 권한을 할당받으며 이는 권한-사용자로 관리한다.
  • + *
  • 다음은 이 체계를 위한 정보를 저장하는 데이터베이스 테이블들이다.
    + * 접근 관리 테이블
  • + *
+ */ +package cokr.xit.base.security.access; \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/service/ActionGroupService.java b/src/main/java/cokr/xit/base/security/access/service/ActionGroupService.java new file mode 100644 index 0000000..31d84f1 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/service/ActionGroupService.java @@ -0,0 +1,88 @@ +package cokr.xit.base.security.access.service; + +import java.util.List; + +import cokr.xit.base.security.access.ActionGroup; +import cokr.xit.foundation.data.DataObject; + +/**기능 그룹 관리 서비스를 제공한다. + *
  • {@link #getGroupList(String, String, int, int) 기능 그룹 목록 조회}
  • + *
  • {@link #getGroups(String...) 기능 그룹 가져오기}
  • + *
  • {@link #create(ActionGroup) 기능 그룹 등록}
  • + *
  • {@link #update(ActionGroup) 기능 그룹 수정}
  • + *
  • {@link #remove(String...) 기능 그룹 제거}
  • + *
  • {@link #getActionList(String...) 기능 목록 조회}
  • + *
  • {@link #addActions(String, String...) 그룹 기능 추가}
  • + *
  • {@link #removeActions(String, String...) 그룹 기능 제거}
  • + *
+ * @author mjkhan + */ +public interface ActionGroupService { + /**주어진 조건에 따라 기능 그룹 목록을 조회한다. + * @param by 조건 필드 이름 + *
  • actionGroupName - 기능 그룹 이름
  • + *
  • 그 외는 기능 그룹 아이디
  • + *
+ * @param term 조건 필드값 + * @param pageNum 시작 페이지 번호 + * @param fetchSize 한 번에 가져올 기능 그룹수 + * @return 기능 그룹 목록 + */ + List getGroupList(ActionQuery req); + + /**지정하는 기능 그룹을 가져온다. + * @param groupIDs 기능 그룹 아이디 + * @return 기능 그룹 + */ + List getGroups(String... groupIDs); + + /**지정하는 기능 그룹을 가져온다. + * @param groupID 기능 그룹 아이디 + * @return 기능 그룹 + */ + ActionGroup getGroup(String groupID); + + /**기능 그룹을 등록한다. + * @param group 기능 그룹 + * @return 등록 여부 + *
  • 등록됐으면 true
  • + *
  • 그렇지 않으며 false
  • + *
+ */ + boolean create(ActionGroup group); + + /**기능 그룹을 수정한다. + * @param group 기능 그룹 + * @return 수정 여부 + *
  • 수정됐으면 true
  • + *
  • 그렇지 않으며 false
  • + *
+ */ + boolean update(ActionGroup group); + + /**지정하는 기능 그룹을 제거한다. + * @param groupIDs 기능 그룹 아이디 + * @return 저장된 정보수 + */ + int remove(String... groupIDs); + + /**지정하는 그룹의 기능 목록을 조회한다. + * @param groupIDs 기능 그룹 아이디 + * @return 기능 목록 + */ + List getActionList(ActionQuery req); + + /**지정하는 기능 그룹에 기능들을 추가한다. + * @param groupID 그룹 아이디 + * @param actions 기능(URL) + * @return 저장된 정보수 + */ + int addActions(String groupID, String... actions); + + /**지정하는 기능 그룹의 기능을 제거한다. + * @param groupID 기능 그룹 아이디 + * @param actions 기능 + * @return 저장된 정보수 + */ + int removeActions(String groupID, String... actions); +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/service/ActionQuery.java b/src/main/java/cokr/xit/base/security/access/service/ActionQuery.java new file mode 100644 index 0000000..b9c6daf --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/service/ActionQuery.java @@ -0,0 +1,25 @@ +package cokr.xit.base.security.access.service; + +import cokr.xit.foundation.component.QueryRequest; + +public class ActionQuery extends QueryRequest { + private static final long serialVersionUID = 1L; + + private String[] groupIDs; + + /**groupIDs을(를) 반환한다. + * @return groupIDs + */ + public String[] getGroupIDs() { + return groupIDs; + } + + /**groupIDs을(를) 설정한다. + * @param groupIDs groupIDs + * @return ActionQuery + */ + public T setGroupIDs(String... groupIDs) { + this.groupIDs = groupIDs; + return self(); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/service/AuthorityQuery.java b/src/main/java/cokr/xit/base/security/access/service/AuthorityQuery.java new file mode 100644 index 0000000..4e15561 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/service/AuthorityQuery.java @@ -0,0 +1,51 @@ +package cokr.xit.base.security.access.service; + +import cokr.xit.foundation.component.QueryRequest; + +/**권한 조회 조건. + *
  • by: 조회 조건 필드 이름 + *
    • authID - 권한 아이디
    • + *
    • 그 외는 권한 이름
    • + *
    + *
  • + *
+ * @author mjkhan + */ +public class AuthorityQuery extends QueryRequest { + private static final long serialVersionUID = 1L; + + private String[] authIDs; + private String[] userIDs; + + /**권한 아아디를 반환한다. + * @return 권한 아아디 + */ + public String[] getAuthIDs() { + return ifEmpty(authIDs, () -> null); + } + + /**권한 아아디를 설정한다. + * @param authIDs 권한 아아디 + * @return AuthorityQuery + */ + public T setAuthIDs(String... authIDs) { + this.authIDs = authIDs; + return self(); + } + + /**사용자 아이디를 반환한다. + * @return userIDs 사용자 아이디 + */ + public String[] getUserIDs() { + return ifEmpty(userIDs, () -> null); + } + + /**사용자 아이디를 설정한다. + * @param userIDs 사용자 아이디 + * @return AuthorityQuery + */ + public T setUserIDs(String[] userIDs) { + this.userIDs = userIDs; + return self(); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/service/AuthorityService.java b/src/main/java/cokr/xit/base/security/access/service/AuthorityService.java new file mode 100644 index 0000000..d220da0 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/service/AuthorityService.java @@ -0,0 +1,117 @@ +package cokr.xit.base.security.access.service; + +import java.util.List; + +import cokr.xit.base.security.Authority; +import cokr.xit.foundation.data.DataObject; + +/**권한 정보 서비스를 제공한다. + *
  • {@link #getAuthorityList(String, String, int, int) 권한 목록 조회}
  • + *
  • {@link #getAuthorities(String...) 권한 가져오기}
  • + *
  • {@link #create(Authority) 권한 등록}
  • + *
  • {@link #update(Authority) 권한 수정}
  • + *
  • {@link #removeAuthorities(String...) 권한 제거}
  • + *
  • {@link #getActionGroupList(int, int, String...) 권한별 기능 그룹 조회}
  • + *
  • {@link #addActionGroups(String, String...) 권한 기능 그룹 추가}
  • + *
  • {@link #removeActionGroups(String, String...) 권한 기능 그룹 제거}
  • + *
  • {@link #getUserList(int, int, String...) 권한별 사용자 조회}
  • + *
  • {@link #addUsers(String, String...) 권한 사용자 추가}
  • + *
  • {@link #removeUsers(String, String...) 권한 사용자 제거}
  • + *
+ * @author mjkhan + */ +public interface AuthorityService { + /**주어진 조건의 권한 목록을 조회한다. + * @param by 조건 필드 이름 + *
  • authID - 권한 아이디
  • + *
  • 그 외는 권한 이름
  • + *
+ * @param term 조건 필드값 + * @param pageNum 시작 페이지 번호 + * @param fetchSize 한 번에 가져올 권한 정보수 + * @return 권한 목록 + */ + List getAuthorityList(AuthorityQuery req); + + /**지정하는 권한들을 가져온다. + * @param authIDs 권한 아이디 + * @return 권한 목록 + */ + List getAuthorities(String... authIDs); + + /**지정하는 권한을 가져온다. + * @param authID 권한 아이디 + * @return 권한 + */ + Authority getAuthority(String authID); + + /**권한을 등록한다. + * @param authority 권한 + * @return 등록 여부 + *
  • 등록됐으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + boolean create(Authority authority); + + /**권한을 수정한다. + * @param authority 권한 + * @return 수정 여부 + *
  • 수정됐으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + boolean update(Authority authority); + + /**지정하는 권한들을 삭제한다. + * @param authIDs 권한 아이디 + * @return 저장된 정보수 + */ + int removeAuthorities(String... authIDs); + + /**지정하는 권한의 기능 그룹 목록을 조회한다. + * @param pageNum 시작 페이지 번호 + * @param fetchSize 한 번에 가져올 기능 그룹수 + * @param authIDs 권한 아이디 + * @return 권한별 기능 그룹 목록 + */ + List getActionGroupList(AuthorityQuery req); + + /**지정하는 권한에 기능 그룹들을 추가한다. + * @param authID 권한 아이디 + * @param groupIDs 기능 그룹 아이디 + * @return 저장된 정보수 + */ + int addActionGroups(String authID, String... groupIDs); + + /**지정하는 권한에서 기능 그룹들을 삭제한다.
+ * 기능 그룹을 지정하지 않으면 해당 권한의 모든 기능 그룹을 삭제한다. + * @param authID 권한 아이디 + * @param groupIDs 기능 그룹 아이디 + * @return 저장된 정보수 + */ + int removeActionGroups(String authID, String... groupIDs); + + /**지정하는 권한의 사용자를 조회한다. + * @param pageNum 시작 페이지 번호 + * @param fetchSize 한 번에 가져올 사용자 정보수 + * @param authIDs 권한 아이디 + * @return 사용자 목록 + */ + List getUserList(AuthorityQuery req); + + /**지정하는 권한에 사용자를 추가한다. + * @param authID 권한 아이디 + * @param userIDs 사용자 아이디 + * @return 저장된 정보수 + */ + int addUsers(String authID, String... userIDs); + + /**지정하는 권한에서 사용자를 제거한다.
+ * 사용자를 지정하지 않으면 해당 권한의 모든 사용자를 제거한다. + * @param authID 권한 아이디 + * @param userIDs 사용자 아이디 + * @return 저장된 정보수 + */ + int removeUsers(String authID, String... userIDs); +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/service/bean/ActionGroupBean.java b/src/main/java/cokr/xit/base/security/access/service/bean/ActionGroupBean.java new file mode 100644 index 0000000..82b7f11 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/service/bean/ActionGroupBean.java @@ -0,0 +1,122 @@ +package cokr.xit.base.security.access.service.bean; + +import java.util.List; + +import javax.annotation.Resource; + +import org.springframework.stereotype.Component; + +import cokr.xit.base.security.access.ActionGroup; +import cokr.xit.base.security.access.dao.ActionGroupMapper; +import cokr.xit.base.security.access.service.ActionQuery; +import cokr.xit.foundation.AbstractComponent; +import cokr.xit.foundation.data.DataObject; + +/**기능 그룹 관리 DAO + * @author mjkhan + */ +@Component("actionGroupBean") +public class ActionGroupBean extends AbstractComponent { + @Resource(name = "actionGroupMapper") + private ActionGroupMapper actionGroupMapper; + + /**주어진 조건에 따라 기능 그룹 목록을 조회한다. + * @param by 조건 필드 이름 + *
  • actionGroupName - 기능 그룹 이름
  • + *
  • 그 외는 기능 그룹 아이디
  • + *
+ * @param term 조건 필드값 + * @param pageNum 시작 페이지 번호 + * @param fetchSize 한 번에 가져올 기능 그룹수 + * @return 기능 그룹 목록 + */ + public List getGroupList(ActionQuery req) { + switch (ifEmpty(req.getBy(), () -> "")) { + case "groupID": req.setBy("GRP_ID"); break; + default : req.setBy("GRP_NM"); break; + } + req.setOrderBy(req.getBy()); + return actionGroupMapper.getGroupList(req); + } + + /**지정하는 기능 그룹을 가져온다. + * @param groupIDs 기능 그룹 아이디 + * @return 기능 그룹 + */ + public List getGroups(String... groupIDs) { + return actionGroupMapper.getGroups(groupIDs); + } + + /**지정하는 기능 그룹을 가져온다. + * @param groupID 기능 그룹 아이디 + * @return 기능 그룹 + */ + public ActionGroup getGroup(String groupID) { + return actionGroupMapper.getGroup(groupID); + } + + /**기능 그룹을 등록한다. + * @param group 기능 그룹 + * @return 등록 여부 + *
  • 등록됐으면 true
  • + *
  • 그렇지 않으며 false
  • + *
+ */ + public boolean create(ActionGroup group) { + if (group == null) return false; + + ActionGroup existing = getGroup(group.getId()); + if (existing != null) + throw applicationException(null) + .setMessage(message("duplicate.object", "기능그룹")); + + return actionGroupMapper.insertGroup(group) > 0; + } + + /**기능 그룹을 수정한다. + * @param group 기능 그룹 + * @return 수정 여부 + *
  • 수정됐으면 true
  • + *
  • 그렇지 않으며 false
  • + *
+ */ + public boolean update(ActionGroup group) { + if (group == null) return false; + + return actionGroupMapper.updateGroup(group) > 0; + } + + /**지정하는 기능 그룹을 제거한다. + * @param groupIDs 기능 그룹 아이디 + * @return 저장된 정보수 + */ + public int remove(String... groupIDs) { + return actionGroupMapper.removeGroups(groupIDs); + } + + /**지정하는 그룹의 기능 목록을 조회한다. + * @param groupIDs 기능 그룹 아이디 + * @return 기능 목록 + */ + public List getActionList(ActionQuery req) { + return actionGroupMapper.getActionList(req); + } + + /**지정하는 기능 그룹에 기능들을 추가한다. + * @param groupID 그룹 아이디 + * @param actions 기능(URL) + * @return 저장된 정보수 + */ + public int addActions(String groupID, String... actions) { + return actionGroupMapper.addActions(groupID, actions); + } + + /**지정하는 기능 그룹의 기능을 제거한다. + * @param groupID 기능 그룹 아이디 + * @param actions 기능 + * @return 저장된 정보수 + */ + public int removeActions(String groupID, String... actions) { + return actionGroupMapper.removeActions(new String[] {groupID}, actions); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/service/bean/ActionGroupServiceBean.java b/src/main/java/cokr/xit/base/security/access/service/bean/ActionGroupServiceBean.java new file mode 100644 index 0000000..df95ba4 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/service/bean/ActionGroupServiceBean.java @@ -0,0 +1,73 @@ +package cokr.xit.base.security.access.service.bean; + +import java.util.List; + +import javax.annotation.Resource; + +import org.springframework.stereotype.Service; + +import cokr.xit.base.security.access.ActionGroup; +import cokr.xit.base.security.access.dao.AuthorityMapper; +import cokr.xit.base.security.access.service.ActionGroupService; +import cokr.xit.base.security.access.service.ActionQuery; +import cokr.xit.foundation.component.AbstractServiceBean; +import cokr.xit.foundation.data.DataObject; + +/**기능 그룹 서비스 인터페이스 구현체 + * @author mjkhan + */ +@Service("actionGroupService") +public class ActionGroupServiceBean extends AbstractServiceBean implements ActionGroupService { + @Resource(name="actionGroupBean") + private ActionGroupBean actionGroupBean; + @Resource(name="authorityMapper") + private AuthorityMapper authorityMapper; + + @Override + public List getGroupList(ActionQuery req) { + return actionGroupBean.getGroupList(req); + } + + @Override + public List getGroups(String... groupIDs) { + return actionGroupBean.getGroups(groupIDs); + } + + @Override + public ActionGroup getGroup(String groupID) { + return actionGroupBean.getGroup(groupID); + } + + @Override + public boolean create(ActionGroup group) { + if (group == null) return false; + + return actionGroupBean.create(group); + } + + @Override + public boolean update(ActionGroup group) { + return actionGroupBean.update(group); + } + + @Override + public int remove(String... groupIDs) { + authorityMapper.removeActionGroups(groupIDs); + return actionGroupBean.remove(groupIDs); + } + + @Override + public List getActionList(ActionQuery req) { + return actionGroupBean.getActionList(req); + } + + @Override + public int addActions(String groupID, String... actions) { + return actionGroupBean.addActions(groupID, actions); + } + + @Override + public int removeActions(String groupID, String... actions) { + return actionGroupBean.removeActions(groupID, actions); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/service/bean/AuthorityBean.java b/src/main/java/cokr/xit/base/security/access/service/bean/AuthorityBean.java new file mode 100644 index 0000000..9fef059 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/service/bean/AuthorityBean.java @@ -0,0 +1,172 @@ +package cokr.xit.base.security.access.service.bean; + +import java.util.List; + +import javax.annotation.Resource; + +import org.springframework.stereotype.Component; + +import cokr.xit.base.security.Authority; +import cokr.xit.base.security.access.dao.AuthorityMapper; +import cokr.xit.base.security.access.service.AuthorityQuery; +import cokr.xit.foundation.AbstractComponent; +import cokr.xit.foundation.data.DataObject; + +/**권한 정보 관리 Bean + * @author mjkhan + */ +@Component("authorityBean") +public class AuthorityBean extends AbstractComponent { + @Resource(name = "authorityMapper") + private AuthorityMapper authorityMapper; + + /**주어진 조건의 권한 목록을 조회한다. + * @param req 조회 조건 + * @return 권한 목록 + */ + public List getAuthorityList(AuthorityQuery req) { + switch (ifEmpty(req.getBy(), () -> "")) { + case "authID": req.setBy("AUTH_ID"); break; + default: req.setBy("AUTH_NM"); break; + } + req.setOrderBy("AUTH_TYPE, " + req.getBy()); + return authorityMapper.getAuthorityList(req); + } + + /**지정하는 권한들을 가져온다. + * @param authIDs 권한 아이디 + * @return 권한 목록 + */ + public List getAuthorities(String... authIDs) { + return authorityMapper.getAuthorities(authIDs); + } + + /**지정하는 권한을 가져온다. + * @param authID 권한 아이디 + * @return 권한 + */ + public Authority getAuthority(String authID) { + return authorityMapper.getAuthority(authID); + } + + /**권한을 등록한다. + * @param authority 권한 + * @return 등록 여부 + *
  • 등록됐으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean create(Authority authority) { + if (authority == null) return false; + + if (!getAuthorities(authority.getId()).isEmpty()) + throw new RuntimeException(message("duplicate.object", "권한")); + + return authorityMapper.insertAuthority(authority) > 0; + } + + /**권한을 수정한다. + * @param authority 권한 + * @return 수정 여부 + *
  • 수정됐으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean update(Authority authority) { + if (authority == null) + return false; + Authority existing = getAuthority(authority.getId()); + if (existing == null) + throw applicationException(null).setMessage("존재하지 않는 권한입니다."); + if (existing.type().isBuiltIn()) + throw applicationException(null).setMessage("내장된 권한은 수정할 수 없습니다."); + + return authorityMapper.updateAuthority(authority) > 0; + } + + /**지정하는 권한들을 삭제한다. + * @param authIDs 권한 아이디 + * @return 저장된 정보수 + */ + public int removeAuthorities(String... authIDs) { + return authorityMapper.removeAuthorities(authIDs); + } + + /**지정하는 권한의 기능 그룹 목록을 조회한다. + * @param req 조회 조건(authIDs - 권한 아이디) + * @return 권한별 기능 그룹 목록 + */ + public List getActionGroupList(AuthorityQuery req) { + req.setOrderBy("AUTH_ID, GRP_ID"); + return authorityMapper.getActionGroupList(req); + } + + /**지정하는 권한에 기능 그룹들을 추가한다. + * @param authID 권한 아이디 + * @param groupIDs 기능 그룹 아이디 + * @return 저장된 정보수 + */ + public int addActionGroups(String authID, String... groupIDs) { + return authorityMapper.addActionGroups(authID, groupIDs); + } + + /**지정하는 권한에서 기능 그룹들을 삭제한다.
+ * 기능 그룹을 지정하지 않으면 해당 권한의 모든 기능 그룹을 삭제한다. + * @param authID 권한 아이디 + * @param groupIDs 기능 그룹 아이디 + * @return 저장된 정보수 + */ + public int removeActionGroups(String authID, String... groupIDs) { + return authorityMapper.removeActionGroups(authID, groupIDs); + } + + /**지정하는 기능 그룹들을 삭제한다. + * @param groupIDs 기능 그룹 아이디 + * @return 저장된 정보수 + */ + public int removeActionGroups(String... groupIDs) { + return authorityMapper.removeActionGroups(groupIDs); + } + + /**권한별 기능 그룹, 기능 목록을 가져온다. + * @return 권한별 기능 그룹, 기능 목록 + */ + public List getActionList() { + return authorityMapper.getActionList(); + } + + /**지정하는 권한의 사용자를 조회한다. + * @param req 조회 조건 + * @return 사용자 목록 + */ + public List getUserList(AuthorityQuery req) { + return authorityMapper.getUserList(req.setOrderBy("AUTH_ID, USER_ID")); + } + + /**지정하는 사용자의 권한 목록을 조회한다. + * @param userID 사용자 아이디 + * @return 권한 목록 + */ + public List getUserAuths(String userID) { + return authorityMapper.getUserAuths(userID); + } + + /**지정하는 권한에 사용자를 추가한다. + * @param authID 권한 아이디 + * @param userIDs 사용자 아이디 + * @return 저장된 정보수 + */ + public int addUsers(String authID, String... userIDs) { + return authorityMapper.addUsers(authID, userIDs); + } + + /**지정하는 권한에서 사용자를 제거한다.
+ * 사용자를 지정하지 않으면 해당 권한의 모든 사용자를 제거한다. + * @param authID 권한 아이디 + * @param userIDs 사용자 아이디 + * @return 저장된 정보수 + */ + public int removeUsers(String authID, String... userIDs) { + return authorityMapper.removeUsers(authID, userIDs); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/service/bean/AuthorityServiceBean.java b/src/main/java/cokr/xit/base/security/access/service/bean/AuthorityServiceBean.java new file mode 100644 index 0000000..7c276c9 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/service/bean/AuthorityServiceBean.java @@ -0,0 +1,82 @@ +package cokr.xit.base.security.access.service.bean; + +import java.util.List; + +import javax.annotation.Resource; + +import org.springframework.stereotype.Service; + +import cokr.xit.base.security.Authority; +import cokr.xit.base.security.access.service.AuthorityQuery; +import cokr.xit.base.security.access.service.AuthorityService; +import cokr.xit.foundation.component.AbstractServiceBean; +import cokr.xit.foundation.data.DataObject; + +/**권한 정보 서비스 인터페이스의 구현체 + * @author mjkhan + */ +@Service("authorityService") +public class AuthorityServiceBean extends AbstractServiceBean implements AuthorityService { + @Resource(name="authorityBean") + private AuthorityBean authorityMapper; + + @Override + public List getAuthorityList(AuthorityQuery req) { + return authorityMapper.getAuthorityList(req); + } + + @Override + public List getAuthorities(String... authIDs) { + return authorityMapper.getAuthorities(authIDs); + } + + @Override + public Authority getAuthority(String authID) { + return authorityMapper.getAuthority(authID); + } + + @Override + public boolean create(Authority authority) { + return authorityMapper.create(authority); + } + + @Override + public boolean update(Authority authority) { + return authorityMapper.update(authority); + } + + @Override + public int removeAuthorities(String... authIDs) { + return authorityMapper.removeAuthorities(authIDs); + } + + @Override + public List getActionGroupList(AuthorityQuery req) { + return authorityMapper.getActionGroupList(req); + } + + @Override + public int addActionGroups(String authID, String... groupIDs) { + return authorityMapper.addActionGroups(authID, groupIDs); + } + + @Override + public int removeActionGroups(String authID, String... groupIDs) { + return authorityMapper.removeActionGroups(authID, groupIDs); + } + + @Override + public List getUserList(AuthorityQuery req) { + return authorityMapper.getUserList(req); + } + + @Override + public int addUsers(String authID, String... userIDs) { + return authorityMapper.addUsers(authID, userIDs); + } + + @Override + public int removeUsers(String authID, String... userIDs) { + return authorityMapper.removeUsers(authID, userIDs); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/service/bean/package-info.java b/src/main/java/cokr/xit/base/security/access/service/bean/package-info.java new file mode 100644 index 0000000..aa8fc0b --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/service/bean/package-info.java @@ -0,0 +1,3 @@ +/**사용자 접근 정보 관리 서비스 구현부. + */ +package cokr.xit.base.security.access.service.bean; \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/service/package-info.java b/src/main/java/cokr/xit/base/security/access/service/package-info.java new file mode 100644 index 0000000..9585b15 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/service/package-info.java @@ -0,0 +1,3 @@ +/**사용자 접근 정보 관리 서비스 인터페이스. + */ +package cokr.xit.base.security.access.service; \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/web/ActionGroupController.java b/src/main/java/cokr/xit/base/security/access/web/ActionGroupController.java new file mode 100644 index 0000000..65c7a6b --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/web/ActionGroupController.java @@ -0,0 +1,206 @@ +package cokr.xit.base.security.access.web; + +import java.util.List; +import java.util.stream.Collectors; + +import javax.annotation.Resource; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; + +import cokr.xit.base.security.access.ActionGroup; +import cokr.xit.base.security.access.service.ActionGroupService; +import cokr.xit.base.security.access.service.ActionQuery; +import cokr.xit.foundation.data.DataObject; +import cokr.xit.foundation.web.AbstractController; + +/**기능 그룹 서비스 웹 컨트롤러.
+ * {웹 컨텍스트}/actionGroup/*로 접근할 수 있다. + * @author mjkhan + */ +@RequestMapping(name="기능 그룹", value="/actionGroup") +public class ActionGroupController extends AbstractController { + @Resource(name="actionGroupService") + private ActionGroupService actionGroupService; + + /**기능 그룹 서비스를 반환한다. + * @return 기능 그룹 서비스 + */ + protected ActionGroupService actionGroupService() { + return actionGroupService; + } + + /**기능 그룹 관리 메인 화면을 연다.
+ * 이 때 {@link #getActionGroupList(String, String, Integer) 기능 그룹 조회} 결과도 같이 보낸다. + * @return "actionGroup/actionGroup-main" + */ + @RequestMapping(name="기능 그룹 메인", value="/main.do") + public ModelAndView main() { + ModelAndView mav = getActionGroupList(new ActionQuery().setPageNum(1)); + mav.setViewName("base/actionGroup/actionGroup-main"); + return mav.addObject("groupList", toJson(mav.getModel().get("groupList"))); + } + + /**주어진 조건에 따라 기능 그룹 목록을 조회한다. + * @param by 조건 필드 이름 + *
  • groupName - 기능 그룹 이름
  • + *
  • 그 외는 기능 그룹 아이디
  • + *
+ * @param term 조건 필드값 + * @param pageNum 시작 페이지 번호 + * @return jsonView + *
 {
+	 *    "groupList": 기능 그룹 목록,
+	 *    "groupStart": 목록 시작 인덱스,
+	 *    "groupFetch": 한 번에 가져오는 기능 그룹 정보수,
+	 *    "groupTotal": 조회 결과 찾은 기능 그룹 전체 수
+	 * }
+ */ + @RequestMapping(name="기능 그룹 조회", value="/list.do") + public ModelAndView getActionGroupList(ActionQuery req) { + setFetchSize(req); + return setCollectionInfo( + new ModelAndView("jsonView"), + actionGroupService.getGroupList(req), + "group" + ); + } + + /**사용자의 기능 그룹 선택을 위한 화면을 연다. + * @return "actionGroup/select" + */ + @RequestMapping(name="기능 그룹 선택", value="/select.do") + public ModelAndView select(boolean multiple) { + ModelAndView mav = getActionGroupList(new ActionQuery().setPageNum(1)); + mav.setViewName("base/actionGroup/select-actionGroup"); + List list = (List)mav.getModel().remove("groupList"); + mav.addObject("groupList", toJson(list.stream().filter(info -> !info.bool("builtIn")).collect(Collectors.toList()))); + return mav; + } + + /**지정한 기능 그룹 아이디 중복된 것인지 확인한다. + * @param groupID 기능 그룹 아이디 + * @return jsonView + *
 {
+	 *    "duplicate": true || false
+	 * }
+ */ + @RequestMapping(name="중복 확인", value="/duplicate.do") + public ModelAndView isDuplicate(String groupID) { + ActionGroup group = actionGroupService.getGroup(groupID); + return new ModelAndView("jsonView") + .addObject("duplicate", group != null); + } + + @RequestMapping(name="기능 그룹 정보 조회", value="/info.do") + public ModelAndView getInfo(String groupID) { + if (!isEmpty(groupID)) { + ActionQuery req = new ActionQuery() + .setBy("groupID") + .setTerm(groupID); + List groupList = actionGroupService.getGroupList(req); + DataObject groupInfo = !groupList.isEmpty() ? groupList.get(0) : null; + boolean json = jsonResponse(); + return new ModelAndView(json ? "jsonView" : "base/actionGroup/actionGroup-info") + .addObject("groupInfo", json ? groupInfo : toJson(groupInfo)); + } else { + return new ModelAndView("base/actionGroup/actionGroup-info"); + } + } + + /**기능 그룹을 등록한다. + * @param group 기능 그룹 + * @return jsonView + *
 {
+	 *    "saved": true || false
+	 * }
+ */ + @PostMapping(name="기능 그룹 등록", value="/create.do") + public ModelAndView create(ActionGroup group) { + boolean saved = actionGroupService.create(group); + return new ModelAndView("jsonView") + .addObject("saved", saved); + } + + /**기능 그룹을 수정한다. + * @param group 기능 그룹 + * @return jsonView + *
 {
+	 *    "saved": true || false
+	 * }
+ */ + @PostMapping(name="기능 그룹 수정", value="/update.do") + public ModelAndView update(ActionGroup group) { + boolean saved = actionGroupService.update(group); + return new ModelAndView("jsonView") + .addObject("saved", saved); + } + + /**지정하는 기능 그룹을 제거한다. + * @param groupIDs 기능 그룹 아이디 + * @return jsonView + *
 {
+	 *    "affected": 저장된 정보수,
+	 *    "saved": true || false
+	 * }
+ */ + @PostMapping(name="기능 그룹 삭제", value="/remove.do") + public ModelAndView remove(String... groupIDs) { + int affected = actionGroupService.remove(groupIDs); + return new ModelAndView("jsonView") + .addObject("affected", affected) + .addObject("saved", affected > 0); + } + + /**지정하는 그룹의 기능 목록을 조회한다. + * @param groupID 기능 그룹 아이디 + * @return jsonView + *
 {
+	 *    "actionList": [...]
+	 * }
+ */ + @RequestMapping(name="기능 그룹별 기능(URL) 조회", value="/action/list.do") + public ModelAndView getActionList(ActionQuery req) { + setFetchSize(req); + return setCollectionInfo( + new ModelAndView("jsonView"), + actionGroupService.getActionList(req), + "action" + ); + } + + /**지정하는 기능 그룹에 기능들을 추가한다. + * @param groupID 그룹 아이디 + * @param actions 기능(URL) + * @return jsonView + *
 {
+	 *    "affected": 저장된 정보수,
+	 *    "saved": true || false
+	 * }
+ */ + @RequestMapping(name="기능 그룹별 기능(URL) 추가", value="/action/add.do") + public ModelAndView addActions(String groupID, String... actions) { + int affected = actionGroupService.addActions(groupID, actions); + return new ModelAndView("jsonView") + .addObject("affected", affected) + .addObject("saved", affected > 0); + } + + /**지정하는 기능 그룹의 기능을 제거한다. + * @param groupID 기능 그룹 아이디 + * @param actions 기능 + * @return jsonView + *
 {
+	 *    "affected": 저장된 정보수,
+	 *    "saved": true || false
+	 * }
+ */ + @RequestMapping(name="기능 그룹별 기능(URL) 삭제", value="/action/remove.do") + public ModelAndView removeActions(String groupID, String... actions) { + int affected = actionGroupService.removeActions(groupID, actions); + return new ModelAndView("jsonView") + .addObject("affected", affected) + .addObject("saved", affected > 0); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/web/AuthorityController.java b/src/main/java/cokr/xit/base/security/access/web/AuthorityController.java new file mode 100644 index 0000000..dcf6bac --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/web/AuthorityController.java @@ -0,0 +1,254 @@ +package cokr.xit.base.security.access.web; + +import java.util.List; + +import javax.annotation.Resource; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; + +import cokr.xit.base.security.Authority; +import cokr.xit.base.security.access.service.AuthorityQuery; +import cokr.xit.base.security.access.service.AuthorityService; +import cokr.xit.foundation.data.DataObject; +import cokr.xit.foundation.web.AbstractController; + +/**권한 정보 서비스 웹 컨트롤러.
+ * {웹 컨텍스트}/authority/*로 접근할 수 있다. + * @author mjkhan + */ +@RequestMapping(name="권한", value="/authority") +public class AuthorityController extends AbstractController { + @Resource(name="authorityService") + private AuthorityService authorityService; + + /**권한 정보 서비스를 반환한다. + * @return 권한 정보 서비스 + */ + protected AuthorityService authorityService() { + return authorityService; + } + + /**권한 관리 메인 화면을 연다.
+ * 이 때 권한 목록 정보를 같이 보낸다. + * @return "authority/authority-main" + */ + @RequestMapping(name="권한 메인", value="/main.do") + public ModelAndView main() { + ModelAndView mav = getAuthorityList(new AuthorityQuery().setPageNum(1)); + mav.setViewName("base/authority/authority-main"); + return mav.addObject("authorityList", toJson(mav.getModel().remove("authorityList"))); + } + + /**주어진 조건의 권한 목록을 조회한다. + * @param by 조건 필드 이름 + *
  • authID - 권한 아이디
  • + *
  • 그 외는 권한 이름
  • + *
+ * @param term 조건 필드값 + * @param pageNum 시작 페이지 번호 + * @return jsonView + *
{
+	 *     "authorityList": 권한 목록,
+	 *     "authorityStart": 권한 목록 시작 인덱스,
+	 *     "authorityFetch": 한 번에 가져오는 권한 정보수,
+	 *     "authorityTotal": 조회로 찾은 전체 권한수
+	 * }
+ */ + @RequestMapping(name="권한 조회", value="/list.do") + public ModelAndView getAuthorityList(AuthorityQuery req) { + setFetchSize(req); + return setCollectionInfo( + new ModelAndView("jsonView"), + authorityService.getAuthorityList(req), + "authority" + ); + } + + /**지정한 권한 아이디가 중복된 것인지 확인한다. + * @param authID 권한 아이디 + * @return jsonView + *
{
+	 *     "duplicate": true || false
+	 * }
+ */ + @RequestMapping(name="중복 확인", value="/duplicate.do") + public ModelAndView isDuplicate(String authID) { + Authority authority = authorityService.getAuthority(authID); + return new ModelAndView("jsonView") + .addObject("duplicate", authority != null); + } + + @RequestMapping(name="권한 정보 조회", value="/info.do") + public ModelAndView getInfo(String authID) { + if (!isEmpty(authID)) { + AuthorityQuery req = new AuthorityQuery() + .setBy("authID") + .setTerm(authID); + List authList = authorityService.getAuthorityList(req); + DataObject authInfo = !authList.isEmpty() ? authList.get(0) : null; + boolean json = jsonResponse(); + return new ModelAndView(json ? "jsonView" : "base/authority/authority-info") + .addObject("authorityInfo", json ? authInfo : toJson(authInfo)); + } else { + return new ModelAndView("base/authority/authority-info"); + } + } + + /**권한을 등록한다. + * @param authority 권한 + * @return jsonView + *
{
+	 *     "saved": true || false
+	 * }
+ */ + @PostMapping(name="권한 등록", value="/create.do") + public ModelAndView create(Authority authority) { + boolean saved = authorityService.create(authority); + return new ModelAndView("jsonView") + .addObject("saved", saved); + } + + /**권한을 수정한다. + * @param authority 권한 + * @return jsonView + *
{
+	 *     "saved": true || false
+	 * }
+ */ + @PostMapping(name="권한 수정", value="/update.do") + public ModelAndView update(Authority authority) { + boolean saved = authorityService.update(authority); + return new ModelAndView("jsonView") + .addObject("saved", saved); + } + + /**지정하는 권한들을 삭제한다. + * @param authIDs 권한 아이디 + * @return jsonView + *
{
+	 *     "affected": 저장된 정보수,
+	 *     "saved": true || false
+	 * }
+ */ + @PostMapping(name="권한 삭제", value="/remove.do") + public ModelAndView remove(String... authIDs) { + int affected = authorityService.removeAuthorities(authIDs); + return new ModelAndView("jsonView") + .addObject("affected", affected) + .addObject("saved", affected > 0); + } + + /**지정하는 권한의 기능 그룹 목록을 조회한다. + * @param pageNum 시작 페이지 번호 + * @param authIDs 권한 아이디 + * @return jsonView + *
{
+	 *     "actionList": 기능 목록,
+	 *     "actionStart": 기능 목록 시작 인덱스,
+	 *     "actionFetch": 한 번에 가져오는 기능 정보수,
+	 *     "actionTotal": 조회로 찾은 전체 기능수
+	 * }
+ */ + @RequestMapping(name="권한별 기능 조회", value="/action/list.do") + public ModelAndView getActionList(AuthorityQuery req) { + setFetchSize(req); + return setCollectionInfo( + new ModelAndView("jsonView"), + authorityService.getActionGroupList(req), + "action" + ); + } + + /**지정하는 권한에 기능 그룹들을 추가한다. + * @param authID 권한 아이디 + * @param groupIDs 기능 그룹 아이디 + * @return jsonView + *
{
+	 *     "affected": 저장된 정보수,
+	 *     "saved": true || false
+	 * }
+ */ + @PostMapping(name="권한별 기능 추가", value="/action/add.do") + public ModelAndView addActions(String authID, String... groupIDs) { + int affected = authorityService.addActionGroups(authID, groupIDs); + return new ModelAndView("jsonView") + .addObject("affected", affected) + .addObject("saved", affected > 0); + } + + /**지정하는 권한에서 기능 그룹들을 삭제한다.
+ * 기능 그룹을 지정하지 않으면 해당 권한의 모든 기능 그룹을 삭제한다. + * @param authID 권한 아이디 + * @param groupIDs 기능 그룹 아이디 + * @return jsonView + *
{
+	 *     "affected": 저장된 정보수,
+	 *     "saved": true || false
+	 * }
+ */ + @PostMapping(name="권한별 기능 삭제", value="/action/remove.do") + public ModelAndView removeActions(String authID, String... groupIDs) { + int affected = authorityService.removeActionGroups(authID, groupIDs); + return new ModelAndView("jsonView") + .addObject("affected", affected) + .addObject("saved", affected > 0); + } + + /**지정하는 권한의 사용자 목록을 조회한다. + * @param pageNum 시작 페이지 번호 + * @param authIDs 권한 아이디 + * @return jsonView + *
{
+	 *     "userList": 사용자 목록,
+	 *     "userStart": 사용자 목록 시작 인덱스,
+	 *     "userFetch": 한 번에 가져오는 사용자 정보수,
+	 *     "userTotal": 조회로 찾은 전체 사용자수
+	 * }
+ */ + @RequestMapping(name="권한별 사용자 조회", value="/user/list.do") + public ModelAndView getUserList(AuthorityQuery req) { + setFetchSize(req); + return setCollectionInfo( + new ModelAndView("jsonView"), + authorityService.getUserList(req), + "user" + ); + } + + /**지정하는 권한에 사용자를 추가한다. + * @param authID 권한 아이디 + * @param userIDs 사용자 아이디 + * @return jsonView + *
{
+	 *     "affected": 저장된 정보수,
+	 *     "saved": true || false
+	 * }
+ */ + @PostMapping(name="권한별 사용자 추가", value="/user/add.do") + public ModelAndView addUsers(String authID, String... userIDs) { + int affected = authorityService.addUsers(authID, userIDs); + return new ModelAndView("jsonView") + .addObject("affected", affected) + .addObject("saved", affected > 0); + } + + /**지정하는 권한에서 사용자를 삭제한다.
+ * 사용자를 지정하지 않으면 해당 권한의 모든 사용자를 삭제한다. + * @param authID 권한 아이디 + * @param userIDs 사용자 아이디 + * @return jsonView + *
{
+	 *     "affected": 저장된 정보수,
+	 *     "saved": true || false
+	 * }
+ */ + @PostMapping(name="권한별 사용자 삭제", value="/user/remove.do") + public ModelAndView removeUsers(String authID, String... userIDs) { + int affected = authorityService.removeUsers(authID, userIDs); + return new ModelAndView("jsonView") + .addObject("affected", affected) + .addObject("saved", affected > 0); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/access/web/package-info.java b/src/main/java/cokr/xit/base/security/access/web/package-info.java new file mode 100644 index 0000000..3337728 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/access/web/package-info.java @@ -0,0 +1,3 @@ +/**사용자 접근 관리 서비스 웹 컨트롤러 + */ +package cokr.xit.base.security.access.web; \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/AuthenticationPolicy.java b/src/main/java/cokr/xit/base/security/authentication/AuthenticationPolicy.java new file mode 100644 index 0000000..e274fec --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/AuthenticationPolicy.java @@ -0,0 +1,165 @@ +package cokr.xit.base.security.authentication; + +import java.util.Date; + +import cokr.xit.foundation.Assert; + +/**사용자 인증(로그인) 정책 정보 + * @author mjkhan + */ +public class AuthenticationPolicy { + private String + userID, + ipAddress, + duplicateYN, + limitYN, + createdBy, + modifiedBy; + private Date + createdAt, + lastModified; + + /**사용자 ID를 반환한다. + * @return 사용자 ID + */ + public String getUserID() { + return userID; + } + + /**사용자 ID를 설정한다. + * @param userID 사용자 ID + */ + public void setUserID(String userID) { + this.userID = userID; + } + + /**IP 주소를 반환한다. + * @return IP 주소 + */ + public String getIpAddress() { + return ipAddress; + } + + /**IP 주소를 설정한다. + * @param ipAddress IP 주소 + */ + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + /**중복접근 허용 여부를 반환한다. + * @return 중복접근 허용 여부 + *
  • Y - 중복접근 허용함
  • + *
  • N - 중복접근 허용하지 않음
  • + *
+ */ + public String getDuplicateYN() { + return Assert.ifEmpty(duplicateYN, "N"); + } + + /**중복접근 허용 여부를 반환한다. + * @return 중복접근 허용 여부 + *
  • true - 중복접근 허용함
  • + *
  • false - 중복접근 허용하지 않음
  • + *
+ */ + public boolean allowsDuplicateAccess() { + return "Y".equals(getDuplicateYN()); + } + + /**중복접근 허용 여부를 설정한다. + * @param duplicateYN 중복접근 허용 여부 + *
  • Y - 중복접근 허용함
  • + *
  • N - 중복접근 허용하지 않음
  • + *
+ */ + public void setDuplicateYN(String duplicateYN) { + this.duplicateYN = duplicateYN; + } + + /**접근제한 여부를 반환한다. + * @return 접근제한 여부 + *
  • Y - 접근 제한함
  • + *
  • N - 접근 제한하지 않음
  • + *
+ */ + public String getLimitYN() { + return Assert.ifEmpty(limitYN, "N"); + } + + /**접근제한 여부를 반환한다. + * @return + *
  • true - 접근 제한함
  • + *
  • false - 접근 제한하지 않음
  • + *
+ */ + public boolean limits() { + return "Y".equals(getLimitYN()); + } + + /**접근제한 여부를 설정한다. + * @param limitYN 접근제한 여부 + *
  • Y - 접근 제한함
  • + *
  • N - 접근 제한하지 않음
  • + *
+ */ + public void setLimitYN(String limitYN) { + this.limitYN = limitYN; + } + + /**등록자 ID를 반환한다. + * @return 등록자 ID + */ + public String getCreatedBy() { + return createdBy; + } + + /**등록자 ID를 설정한다. + * @param createdBy 등록자 ID + */ + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + /**수정자 ID를 반환한다. + * @return 수정자 ID + */ + public String getModifiedBy() { + return modifiedBy; + } + + /**수정자 ID를 설정한다. + * @param modifiedBy 수정자 ID + */ + public void setModifiedBy(String modifiedBy) { + this.modifiedBy = modifiedBy; + } + + /**등록 일시를 반환한다. + * @return 등록 일시 + */ + public Date getCreatedAt() { + return createdAt; + } + + /**등록 일시를 설정한다. + * @param createdAt 등록 일시 + */ + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + /**변경 일시를 반환한다. + * @return 변경 일시 + */ + public Date getLastModified() { + return lastModified; + } + + /**변경 일시를 설정한다. + * @param lastModified 변경 일시 + */ + public void setLastModified(Date lastModified) { + this.lastModified = lastModified; + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/dao/AuthenticationDao.java b/src/main/java/cokr/xit/base/security/authentication/dao/AuthenticationDao.java new file mode 100644 index 0000000..3ccd273 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/dao/AuthenticationDao.java @@ -0,0 +1,166 @@ +package cokr.xit.base.security.authentication.dao; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.security.core.Authentication; + +import cokr.xit.base.security.authentication.AuthenticationPolicy; +import cokr.xit.foundation.User; +import cokr.xit.foundation.component.AbstractDao; +import cokr.xit.foundation.data.DataObject; + +/**사용자 인증 정보 DAO + * @author mjkhan + */ +public class AuthenticationDao extends AbstractDao { + /**새 AuthenticationDao를 생성한다. + */ + public AuthenticationDao() { + setNamespace("authentication"); + } + + /**주어진 파라미터에 해당하는 사용자를 가져온다. + * @param params 조회 조건 파라미터 + * @return 사용자 + */ + public User getUser(Map params) { + HashMap copy = new HashMap<>(params); + if (isEmpty(copy.get("userIDs"))) + copy.put("userIDs", new Object[] {params.get("userID")}); + copy.put("password", true); + List users = selectList( + sqlID("user.getUsers"), + copy + ); + return !users.isEmpty() ? users.get(0) : null; + } + + /**주어진 아이디의 사용자를 가져온다. + * @param userID 사용자 아이디 + * @return 사용자 + */ + public User getUser(String userID) { + return getUser( + params().set("userIDs", new String[] {userID}) + ); + } + + /**인증 성공 시 실행되는 핸들러 메소드 + * @param authentication 인증 정보 + * @return 결과값 + */ + public int onSuccess(Authentication authentication) { + return 0; + } + + /**인증 실패 시 실행되는 핸들러 메소드 + * @param params 인증 시 사용한 파라미터 + * @return 결과값 + */ + public int onFailure(Map params) { + return 0; + } + + /**로그 아웃 시 실행되는 핸들러 메소드 + * @param authentication 인증 정보 + * @return 결과값 + */ + public int onLogout(Authentication authentication) { + return 0; + } + + /**주어진 조건의 사용자 인증 정책 목록을 반환한다. + * @param by 조건 필드 이름 + *
  • userName - 사용자 이름
  • + *
  • 그 이외는 사용자 아이디
  • + *
+ * @param term 조건 필드값 + * @param pageNum 페이지 이름 + * @param fetchSize 한 번에 가져올 인증 정책 갯수 + * @return 사용자 인증 정책 목록 + */ + public List getPolicyList(String by, String term, int pageNum, int fetchSize) { + switch (ifEmpty(by, () -> "")) { + case "userName": by = "USER_NM"; break; + default: by = "USER_ID"; break; + } + + DataObject params = params() + .set("by", by) + .set("term", ifEmpty(term, () -> null)) + .set("pageNum", pageNum) + .set("fetchSize", fetchSize); + return selectList(sqlID("getPolicyList"), params); + } + + /**지정한 사용자들의 인증 정책 목록을 가져온다. + * @param userIDs 사용자 아이디 + * @return 인증 정책 목록 + */ + public List getPolicies(String... userIDs) { + DataObject params = params() + .set("userIDs", ifEmpty(userIDs, () -> null)) + .set("pageNum", 1) + .set("fetchSize", userIDs.length); + return selectList(sqlID("getPolicies"), params); + } + + /**지정한 사용자의 인증 정책을 가져온다. + * @param userID 사용자 아이디 + * @return 인증 정책 + */ + public AuthenticationPolicy getPolicy(String userID) { + List found = getPolicies(userID); + return !found.isEmpty() ? found.get(0) : null; + } + + /**인증 정책을 등록한다. + * @param policy 인증 정책 + * @return 등록 여부 + *
  • 등록 되었으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean create(AuthenticationPolicy policy) { + if (policy == null) + return false; + + AuthenticationPolicy found = getPolicy(policy.getUserID()); + if (found != null) + throw applicationException(null); + + DataObject params = params() + .set("policy", policy) + .set("currentUser", currentUser()); + return insert(sqlID("insertPolicy"), params) > 0; + } + + /**인증 정책을 수정한다. + * @param policy 인증 정책 + * @return 수정 여부 + *
  • 수정 되었으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean update(AuthenticationPolicy policy) { + if (policy == null) + return false; + + DataObject params = params() + .set("policy", policy) + .set("currentUser", currentUser()); + return insert(sqlID("updatePolicy"), params) > 0; + } + + /**지정한 사용자의 인증 정책을 제거한다. + * @param userIDs 사용자 아이디 + * @return 저장된 정보수 + */ + public int removePolicies(String... userIDs) { + if (isEmpty(userIDs)) return 0; + + return delete(sqlID("removePolicy"), params().set("userIDs", userIDs)); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/dao/package-info.java b/src/main/java/cokr/xit/base/security/authentication/dao/package-info.java new file mode 100644 index 0000000..53a0568 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/dao/package-info.java @@ -0,0 +1,3 @@ +/**사용자 인증 정보 테이블 접근 객체 + */ +package cokr.xit.base.security.authentication.dao; \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/package-info.java b/src/main/java/cokr/xit/base/security/authentication/package-info.java new file mode 100644 index 0000000..85ebf55 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/package-info.java @@ -0,0 +1,3 @@ +/**사용자 인증 모듈 + */ +package cokr.xit.base.security.authentication; \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/service/AuthenticationService.java b/src/main/java/cokr/xit/base/security/authentication/service/AuthenticationService.java new file mode 100644 index 0000000..124e695 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/service/AuthenticationService.java @@ -0,0 +1,87 @@ +package cokr.xit.base.security.authentication.service; + +import java.util.List; +import java.util.Map; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetailsService; + +import cokr.xit.base.security.authentication.AuthenticationPolicy; +import cokr.xit.foundation.data.DataObject; + +/**사용자 인증 서비스를 제공한다. + *
  • 사용자 로그인 및 로그아웃 관련 핸들러 메소드
  • + *
  • {@link #getPolicyList(String, String, int, int) 사용자 인증 정책 조회}
  • + *
  • {@link #create(AuthenticationPolicy) 사용자 인증 정책 등록}
  • + *
  • {@link #update(AuthenticationPolicy) 사용자 인증 정책 수정}
  • + *
  • {@link #removePolicies(String...) 사용자 인증 정책 제거}
  • + *
+ * @author mjkhan + */ +public interface AuthenticationService extends UserDetailsService { + /**인증 성공 시 실행되는 핸들러 메소드 + * @param authentication 인증 정보 + */ + void onSuccess(Authentication authentication); + + /**인증 실패 시 실행되는 핸들러 메소드 + * @param params 인증 시 사용한 파라미터 + */ + void onFailure(Map params); + + /**로그 아웃 시 실행되는 핸들러 메소드 + * @param authentication 인증 정보 + */ + void onLogout(Authentication authentication); + + /**주어진 조건의 사용자 인증 정책 목록을 반환한다. + * @param by 조건 필드 이름 + *
  • userName - 사용자 이름
  • + *
  • 그 이외는 사용자 아이디
  • + *
+ * @param term 조건 필드값 + * @param pageNum 페이지 이름 + * @param fetchSize 한 번에 가져올 인증 정책 갯수 + * @return 사용자 인증 정책 목록 + */ + List getPolicyList(String by, String term, int pageNum, int fetchSize); + + /**지정한 사용자들의 인증 정책 목록을 가져온다. + * @param userIDs 사용자 아이디 + * @return 인증 정책 목록 + */ + List getPolicies(String... userIDs); + + /**지정한 사용자의 인증 정책을 가져온다. + * @param userID 사용자 아이디 + * @return 인증 정책 + */ + default AuthenticationPolicy getPolicy(String userID) { + List list = getPolicies(userID); + return !list.isEmpty() ? list.get(0) : null; + } + + /**인증 정책을 등록한다. + * @param policy 인증 정책 + * @return 등록 여부 + *
  • 등록 되었으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + boolean create(AuthenticationPolicy policy); + + /**인증 정책을 수정한다. + * @param policy 인증 정책 + * @return 수정 여부 + *
  • 수정 되었으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + boolean update(AuthenticationPolicy policy); + + /**지정한 사용자의 인증 정책을 제거한다. + * @param userIDs 사용자 아이디 + * @return 저장된 정보수 + */ + int removePolicies(String... userIDs); +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/service/bean/AuthenticationBean.java b/src/main/java/cokr/xit/base/security/authentication/service/bean/AuthenticationBean.java new file mode 100644 index 0000000..19bcf84 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/service/bean/AuthenticationBean.java @@ -0,0 +1,163 @@ +package cokr.xit.base.security.authentication.service.bean; + +import java.util.List; +import java.util.Map; + +import javax.annotation.Resource; + +import org.springframework.security.core.Authentication; + +import cokr.xit.base.security.authentication.AuthenticationPolicy; +import cokr.xit.base.user.dao.UserMapper; +import cokr.xit.base.user.service.UserQuery; +import cokr.xit.foundation.User; +import cokr.xit.foundation.component.AbstractDao; +import cokr.xit.foundation.data.DataObject; + +/**사용자 인증 정보 DAO + * @author mjkhan + */ +public class AuthenticationBean extends AbstractDao { + @Resource(name = "userMapper") + private UserMapper userMapper; + + /**주어진 파라미터에 해당하는 사용자를 가져온다. + * @param params 조회 조건 파라미터 + * @return 사용자 + */ + public User getUser(Map params) { + UserQuery req = new UserQuery() + .setUserIDs((String)params.get("userID")); + req.setPassword(true); + List users = userMapper.getUsers(req); + + return !users.isEmpty() ? users.get(0) : null; + } + + /**주어진 아이디의 사용자를 가져온다. + * @param userID 사용자 아이디 + * @return 사용자 + */ + public User getUser(String userID) { + return getUser( + params().set("userIDs", new String[] {userID}) + ); + } + + /**인증 성공 시 실행되는 핸들러 메소드 + * @param authentication 인증 정보 + * @return 결과값 + */ + public int onSuccess(Authentication authentication) { + return 0; + } + + /**인증 실패 시 실행되는 핸들러 메소드 + * @param params 인증 시 사용한 파라미터 + * @return 결과값 + */ + public int onFailure(Map params) { + return 0; + } + + /**로그 아웃 시 실행되는 핸들러 메소드 + * @param authentication 인증 정보 + * @return 결과값 + */ + public int onLogout(Authentication authentication) { + return 0; + } + + /**주어진 조건의 사용자 인증 정책 목록을 반환한다. + * @param by 조건 필드 이름 + *
  • userName - 사용자 이름
  • + *
  • 그 이외는 사용자 아이디
  • + *
+ * @param term 조건 필드값 + * @param pageNum 페이지 이름 + * @param fetchSize 한 번에 가져올 인증 정책 갯수 + * @return 사용자 인증 정책 목록 + */ + public List getPolicyList(String by, String term, int pageNum, int fetchSize) { + switch (ifEmpty(by, () -> "")) { + case "userName": by = "USER_NM"; break; + default: by = "USER_ID"; break; + } + + DataObject params = params() + .set("by", by) + .set("term", ifEmpty(term, () -> null)) + .set("pageNum", pageNum) + .set("fetchSize", fetchSize); + return selectList(sqlID("getPolicyList"), params); + } + + /**지정한 사용자들의 인증 정책 목록을 가져온다. + * @param userIDs 사용자 아이디 + * @return 인증 정책 목록 + */ + public List getPolicies(String... userIDs) { + DataObject params = params() + .set("userIDs", ifEmpty(userIDs, () -> null)) + .set("pageNum", 1) + .set("fetchSize", userIDs.length); + return selectList(sqlID("getPolicies"), params); + } + + /**지정한 사용자의 인증 정책을 가져온다. + * @param userID 사용자 아이디 + * @return 인증 정책 + */ + public AuthenticationPolicy getPolicy(String userID) { + List found = getPolicies(userID); + return !found.isEmpty() ? found.get(0) : null; + } + + /**인증 정책을 등록한다. + * @param policy 인증 정책 + * @return 등록 여부 + *
  • 등록 되었으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean create(AuthenticationPolicy policy) { + if (policy == null) + return false; + + AuthenticationPolicy found = getPolicy(policy.getUserID()); + if (found != null) + throw applicationException(null); + + DataObject params = params() + .set("policy", policy) + .set("currentUser", currentUser()); + return insert(sqlID("insertPolicy"), params) > 0; + } + + /**인증 정책을 수정한다. + * @param policy 인증 정책 + * @return 수정 여부 + *
  • 수정 되었으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public boolean update(AuthenticationPolicy policy) { + if (policy == null) + return false; + + DataObject params = params() + .set("policy", policy) + .set("currentUser", currentUser()); + return insert(sqlID("updatePolicy"), params) > 0; + } + + /**지정한 사용자의 인증 정책을 제거한다. + * @param userIDs 사용자 아이디 + * @return 저장된 정보수 + */ + public int removePolicies(String... userIDs) { + if (isEmpty(userIDs)) return 0; + + return delete(sqlID("removePolicy"), params().set("userIDs", userIDs)); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/service/bean/AuthenticationServiceBean.java b/src/main/java/cokr/xit/base/security/authentication/service/bean/AuthenticationServiceBean.java new file mode 100644 index 0000000..4c4a6d1 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/service/bean/AuthenticationServiceBean.java @@ -0,0 +1,229 @@ +package cokr.xit.base.security.authentication.service.bean; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.annotation.Resource; + +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import cokr.xit.base.security.Authority; +import cokr.xit.base.security.SecuredUserInfo; +import cokr.xit.base.security.access.AccessContext; +import cokr.xit.base.security.access.dao.AuthorityMapper; +import cokr.xit.base.security.authentication.AuthenticationPolicy; +import cokr.xit.base.security.authentication.dao.AuthenticationDao; +import cokr.xit.base.security.authentication.service.AuthenticationService; +import cokr.xit.base.security.authentication.web.AuthenticationExtraDetails; +import cokr.xit.foundation.Access; +import cokr.xit.foundation.ApplicationContainer; +import cokr.xit.foundation.Log; +import cokr.xit.foundation.User; +import cokr.xit.foundation.UserInfo; +import cokr.xit.foundation.data.DataObject; + +/**사용자 인증 서비스 인터페이스 구현체 + * @author mjkhan + */ +public class AuthenticationServiceBean extends DaoAuthenticationProvider implements AuthenticationService { + /** 애플리케이션 컨테이너 */ + @Resource(name="applicationContainer") + protected ApplicationContainer applicationContainer; + /** 사용자 인증 DAO */ + @Resource(name="authenticationDao") + private AuthenticationDao authenticationDao; + /** 접근 권한 정보 */ + @Resource(name="accessContext") + protected AccessContext accessContext; + /** 접근 권한 DAO */ + @Resource(name="authorityMapper") + private AuthorityMapper authorityMapper; + + /**새 AuthenticationServiceBean를 생성한다. + */ + public AuthenticationServiceBean() { + super(); + setUserDetailsService(this); + } + + /**로그를 반환한다. + * @return 로그 + */ + protected Log log() { + return Log.get(getClass()); + } + + /**사용자 인증 DAO를 반환한다. + * @return 사용자 인증 DAO + */ + protected AuthenticationDao authenticationDao() { + return authenticationDao; + } + + @Override + public SecuredUserInfo loadUserByUsername(String username) throws UsernameNotFoundException { + User user = authenticationDao.getUser(username); + if (user == null) + new BadCredentialsException("Bad credentials"); + + SecuredUserInfo userInfo = new SecuredUserInfo(); + userInfo.setUser(user); + setAuthorities(userInfo); + loadDetails(userInfo); + + return userInfo; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + Object obj = authentication.getPrincipal(); + if (obj instanceof UserInfo) + return authentication; + + obj = authentication.getDetails(); + if (!(obj instanceof AuthenticationExtraDetails)) + return authentication; + + AuthenticationExtraDetails extraDetails = (AuthenticationExtraDetails)obj; + Map params = extraDetails.getParams(); + log().debug("Authentication details: {}", params); + + SecuredUserInfo userInfo = loadUser(params); + + boolean valid = validate(userInfo, params); + if (valid) + checkAgainstPolicy(extraDetails, userInfo); + + Authentication result = createSuccessAuthentication(userInfo.getUsername(), authentication, userInfo); + return new UsernamePasswordAuthenticationToken(userInfo, result.getCredentials(), result.getAuthorities()); + } + + /**인증하려는 사용자의 인증 정책을 가져와 확인한다. + * @param extraDetails 인증 추가 정보 + * @param user 사용자 정보 + */ + protected void checkAgainstPolicy(AuthenticationExtraDetails extraDetails, UserInfo user) { + AuthenticationPolicy policy = authenticationDao.getPolicy(user.getId()); + if (policy == null || !policy.limits()) return; + + String clientAddress = Access.getClientAddress(extraDetails.getRemoteAddress(), applicationContainer.getHostAddress()); + if (!clientAddress.equals(policy.getIpAddress())) + throw new AuthenticationServiceException(messages.getMessage("authenticationFailure.policyViolated")); + } + + /**사용자 정보를 가져온다. + * @param params 사용자 조회 파라미터 + * @return 사용자 정보 + */ + private SecuredUserInfo loadUser(Map params) { + User user = authenticationDao.getUser(params); + if (user == null) + throw new BadCredentialsException("Bad credentials"); + + SecuredUserInfo userInfo = new SecuredUserInfo(); + userInfo.setUser(user); + setAuthorities(userInfo); + loadDetails(userInfo); + + return userInfo; + } + + /**인증한 사용자의 권한정보를 설정한다. + * @param userInfo 사용자 정보 + */ + protected void setAuthorities(SecuredUserInfo userInfo) { + List authIDs = authorityMapper.getUserAuths(userInfo.getId()); + if (!authIDs.contains("ROLE_ADMIN")) { +// authIDs.add("ROLE_USER"); +// authIDs.add("ROLE_ANONYMOUS"); + } + userInfo.setAuthorities(getAuthorities(authIDs)); + } + + /**사용자 정보에 추가 정보를 설정한다. + * @param user 사용자 정보 + */ + protected void loadDetails(UserInfo user) {} + + /**지정한 아이디의 권한을 가져온다. + * @param authIDs 권한 아이디 + * @return 권한 목록 + */ + protected List getAuthorities(List authIDs) { + if (accessContext == null) + return Collections.emptyList(); + + return accessContext.getAuthorities(authIDs); + } + + /**사용자 비밀번호가 맞는지 확인한다. + * @param userInfo 사용자 정보 + * @param params 인증 파라미터 + * @return 비밀번호 유효성 여부 + *
  • 비밀번호가 맞으면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + protected boolean validate(UserInfo userInfo, Map params) { + boolean valid = getPasswordEncoder().matches((String)params.get("password"), userInfo.getPassword()); + if (!valid) + throw new BadCredentialsException("Bad credentials"); + + return valid; + } + + @Override + public void onSuccess(Authentication authentication) { + log().debug("{} authenticated.", authentication.getPrincipal()); + authenticationDao.onSuccess(authentication); + } + + @Override + public void onFailure(Map params) { + log().debug("Authentication failed: {}", params); + authenticationDao.onFailure(params); + } + + @Override + public void onLogout(Authentication authentication) { + log().debug("{} logged out.", authentication.getPrincipal()); + authenticationDao.onLogout(authentication); + } + + @Override + public boolean supports(Class authentication) { + return authentication.equals(UsernamePasswordAuthenticationToken.class); + } + + @Override + public List getPolicyList(String by, String term, int pageNum, int fetchSize) { + return authenticationDao.getPolicyList(by, term, pageNum, fetchSize); + } + + @Override + public List getPolicies(String... userIDs) { + return authenticationDao.getPolicies(userIDs); + } + + @Override + public boolean create(AuthenticationPolicy policy) { + return authenticationDao.create(policy); + } + + @Override + public boolean update(AuthenticationPolicy policy) { + return authenticationDao.update(policy); + } + + @Override + public int removePolicies(String... userIDs) { + return authenticationDao.removePolicies(userIDs); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/service/bean/PasswordEncoder.java b/src/main/java/cokr/xit/base/security/authentication/service/bean/PasswordEncoder.java new file mode 100644 index 0000000..f4b1d31 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/service/bean/PasswordEncoder.java @@ -0,0 +1,13 @@ +package cokr.xit.base.security.authentication.service.bean; + +import cokr.xit.foundation.util.CharsEncoder; + +/**비밀번호 인코더 + * @author mjkhan + */ +public class PasswordEncoder extends CharsEncoder implements org.springframework.security.crypto.password.PasswordEncoder { + @Override + public boolean matches(CharSequence rawChars, String encodedChars) { + return super.matches(rawChars, encodedChars); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/service/bean/package-info.java b/src/main/java/cokr/xit/base/security/authentication/service/bean/package-info.java new file mode 100644 index 0000000..81431b4 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/service/bean/package-info.java @@ -0,0 +1,3 @@ +/**사용자 인증 서비스 구현부 + */ +package cokr.xit.base.security.authentication.service.bean; \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/service/package-info.java b/src/main/java/cokr/xit/base/security/authentication/service/package-info.java new file mode 100644 index 0000000..14e275a --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/service/package-info.java @@ -0,0 +1,3 @@ +/**사용자 인증 서비스 인터페이스 + */ +package cokr.xit.base.security.authentication.service; \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/web/AuthenticationExtraDetails.java b/src/main/java/cokr/xit/base/security/authentication/web/AuthenticationExtraDetails.java new file mode 100644 index 0000000..914e9cb --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/web/AuthenticationExtraDetails.java @@ -0,0 +1,44 @@ +package cokr.xit.base.security.authentication.web; + +import java.util.Collections; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.security.web.authentication.WebAuthenticationDetails; + +import cokr.xit.foundation.Assert; +import cokr.xit.foundation.data.DataObject; + +/**인증 요청 처리 시 사용할 추가 정보 + * @author mjkhan + */ +public class AuthenticationExtraDetails extends WebAuthenticationDetails { + private static final long serialVersionUID = 1L; + + private DataObject params; + + /**새 AuthenticationExtraDetails를 생성한다. + * @param request 서블릿 요청 + */ + public AuthenticationExtraDetails(HttpServletRequest request) { + super(request); + + Map paramMap = request.getParameterMap(); + paramMap.forEach((paramName, paramValue) -> { + if (Assert.isEmpty(paramValue)) return; + + if (params == null) + params = new DataObject(); + + params.put(paramName, paramValue[0]); + }); + } + + /**서블릿 요청의 파라미터들을 반환한다. + * @return 서블릿 요청의 파라미터 + */ + public Map getParams() { + return Assert.ifEmpty(params, Collections::emptyMap); + } +} diff --git a/src/main/java/cokr/xit/base/security/authentication/web/AuthenticationExtraDetailsSource.java b/src/main/java/cokr/xit/base/security/authentication/web/AuthenticationExtraDetailsSource.java new file mode 100644 index 0000000..6df3df6 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/web/AuthenticationExtraDetailsSource.java @@ -0,0 +1,15 @@ +package cokr.xit.base.security.authentication.web; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.security.authentication.AuthenticationDetailsSource; + +/**인증 요청 처리 시 추가 정보를 제공한다. + * @author mjkhan + */ +public class AuthenticationExtraDetailsSource implements AuthenticationDetailsSource { + @Override + public AuthenticationExtraDetails buildDetails(HttpServletRequest context) { + return new AuthenticationExtraDetails(context); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/web/AuthenticationFailure.java b/src/main/java/cokr/xit/base/security/authentication/web/AuthenticationFailure.java new file mode 100644 index 0000000..5044f00 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/web/AuthenticationFailure.java @@ -0,0 +1,83 @@ +package cokr.xit.base.security.authentication.web; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.annotation.Resource; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.context.MessageSource; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.CredentialsExpiredException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import cokr.xit.base.security.authentication.service.AuthenticationService; +import cokr.xit.foundation.Access; +import cokr.xit.foundation.Assert; +import cokr.xit.foundation.data.StringMap; + +/**사용자 인증(로그인) 실패 핸들러 + * @author mjkhan + */ +public class AuthenticationFailure extends SimpleUrlAuthenticationFailureHandler { + @Resource(name="objectMapper") + private ObjectMapper objectMapper; + @Resource(name="authenticationService") + private AuthenticationService authenticationService; + @Resource(name="messageSource") + private MessageSource messageSource; + + /**사용자 인증 서비스 인터페이스를 반환한다. + * @return 사용자 인증 서비스 인터페이스 + */ + protected AuthenticationService authenticationService() { + return authenticationService; + } + + private String failureMessage(String code) { + return messageSource.getMessage("authenticationFailure." + code, null, Access.current().getLocale()); + } + + @Override + public void onAuthenticationFailure(HttpServletRequest hreq, HttpServletResponse hresp, AuthenticationException exception) throws IOException, ServletException { + String reason = "authenticationFailed"; + if (exception instanceof UsernameNotFoundException) + reason = failureMessage("usernameNotFound"); + else if (exception instanceof BadCredentialsException) + reason = failureMessage("badCredentials"); + else if (exception instanceof CredentialsExpiredException) + reason = failureMessage("credentialsExpired"); + else + reason = Assert.rootCause(exception).getMessage(); + + Map params = hreq.getParameterMap().entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, (entry) -> entry.getValue()[0])); + authenticationService.onFailure(params); + + StringMap result = new StringMap<>().set("authenticated", false); + setContentType(hresp, "application/json"); + print(hresp, objectMapper.writeValueAsString(result.set("reason", reason))); + } + + private void setContentType(HttpServletResponse hresp, String contentType) { + hresp.setContentType(contentType); + hresp.setCharacterEncoding("utf-8"); + } + + private void print(HttpServletResponse hresp, String data) throws IOException { + try (PrintWriter out = hresp.getWriter()) { + out.print(data); + out.flush(); + } catch (IOException e) { + throw e; + } + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/web/AuthenticationSuccess.java b/src/main/java/cokr/xit/base/security/authentication/web/AuthenticationSuccess.java new file mode 100644 index 0000000..b9a929c --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/web/AuthenticationSuccess.java @@ -0,0 +1,99 @@ +package cokr.xit.base.security.authentication.web; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Date; + +import javax.annotation.Resource; +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import cokr.xit.base.security.authentication.service.AuthenticationService; +import cokr.xit.foundation.UserInfo; +import cokr.xit.foundation.data.DataObject; + +/**사용자 인증(로그인) 성공 핸들러 + * @author mjkhan + */ +public class AuthenticationSuccess extends SavedRequestAwareAuthenticationSuccessHandler { + @Resource(name="authenticationService") + private AuthenticationService authenticationService; + @Resource(name="objectMapper") + private ObjectMapper objectMapper; + + private boolean rememberUsername; + + /**로그인 유지 여부를 설정한다. + * @param remember 로그인 유지 여부 + *
  • 로그인을 유지하려면 true
  • + *
  • 그렇지 않으면 false
  • + *
+ */ + public void setRememberUsername(boolean remember) { + this.rememberUsername = remember; + } + + /**사용자 인증 서비스 인터페이스를 반환한다. + * @return 사용자 인증 서비스 인터페이스 + */ + protected AuthenticationService authenticationService() { + return authenticationService; + } + + @Override + public void onAuthenticationSuccess(HttpServletRequest hreq, HttpServletResponse hresp, Authentication authentication) throws IOException, ServletException { + DataObject result = onSuccess(hreq, hresp, authentication); + setContentType(hresp, "application/json"); + print(hresp, objectMapper.writeValueAsString(result)); + } + + /**사용자 인증 성공 핸들러
+ * 로그인 시간을 사용자 정보에 저장한다. + * @param hreq 서블릿 요청 + * @param hresp 서블릿 응답 + * @param authentication 사용자 인증 + * @return 인증 정보 + *
{@code {
+	 *     "authenticated": true
+	 * }}
+ */ + protected DataObject onSuccess(HttpServletRequest hreq, HttpServletResponse hresp, Authentication authentication) { + Object principal = authentication.getPrincipal(); + if (principal instanceof UserInfo) { + UserInfo user = (UserInfo)principal; + user.setInfo("loggedInAt", new Date()); + remember(user, hreq, hresp); + } + authenticationService.onSuccess(authentication); + + return new DataObject().set("authenticated", true); + } + + private void setContentType(HttpServletResponse hresp, String contentType) { + hresp.setContentType(contentType); + hresp.setCharacterEncoding("utf-8"); + } + + private void remember(UserInfo user, HttpServletRequest hreq, HttpServletResponse hresp) { + boolean remember = rememberUsername && "true".equals(hreq.getParameter("remember")); + Cookie cookie = new Cookie("username", remember ? user.getUsername() : ""); + cookie.setMaxAge(remember ? Integer.MAX_VALUE : 0); + hresp.addCookie(cookie); + } + + private void print(HttpServletResponse hresp, String data) throws IOException { + try (PrintWriter out = hresp.getWriter()) { + out.print(data); + out.flush(); + } catch (IOException e) { + throw e; + } + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/web/LogoutSuccess.java b/src/main/java/cokr/xit/base/security/authentication/web/LogoutSuccess.java new file mode 100644 index 0000000..ceb45d9 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/web/LogoutSuccess.java @@ -0,0 +1,132 @@ +package cokr.xit.base.security.authentication.web; + +import java.io.IOException; + +import javax.annotation.Resource; +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.web.context.WebApplicationContext; + +import cokr.xit.base.security.authentication.service.AuthenticationService; +import cokr.xit.foundation.Assert; +import cokr.xit.foundation.Log; + +/**로그아웃 성공 핸들러 + * @author mjkhan + */ +public class LogoutSuccess extends SimpleUrlLogoutSuccessHandler implements ApplicationContextAware, HttpSessionListener { + private String successUrl; + + /**로그아웃이 성공했을 때 redirect할 url을 반환한다. + * @param contextPath 웹 애플리케이션 컨텍스트 경로 + * @return 로그아웃이 성공했을 때 redirect할 url + *
  • contextPath + successUrl
  • + *
  • successUrl이 지정되어 있지 않으면 contextPath
  • + *
+ */ + public String getSuccessUrl(String contextPath) { + String path = Assert.ifEmpty(successUrl, "/"); + if (!path.startsWith("/")) + path= "/" + path; + return contextPath + path; + } + + /**로그아웃이 성공했을 때 redirect할 url을 설정한다. + * @param successUrl 로그아웃이 성공했을 때 redirect할 url + */ + public void setSuccessUrl(String successUrl) { + this.successUrl = successUrl; + } + + @Resource(name="authenticationService") + private AuthenticationService authenticationService; + + /**사용자 인증 서비스 인터페이스를 반환한다. + * @return 사용자 인증 서비스 인터페이스 + */ + protected AuthenticationService authenticationService() { + return authenticationService; + } + + /**LogoutSuccess.class와 연계된 로그 객체를 반환한다. + * @return 로그 객체 + */ + protected Log log() { + return Log.get(getClass()); + } + + @Override + public void onLogoutSuccess(HttpServletRequest hreq, HttpServletResponse hresp, Authentication authentication) throws IOException, ServletException { + authenticationService.onLogout(authentication); + HttpSession session = hreq.getSession(false); + if (session != null) + session.invalidate(); + removeCookies(hreq, hresp); + hreq.logout(); + + hresp.setStatus(HttpServletResponse.SC_OK); + hresp.sendRedirect(getSuccessUrl(hreq.getContextPath())); + } + + /**쿠키에 저장된 값들을 삭제한다. + * @param hreq 서블릿 요청 + * @param hresp 서블릿 응답 + */ + protected void removeCookies(HttpServletRequest hreq, HttpServletResponse hresp) { + Cookie[] cookies = hreq.getCookies(); + if (cookies == null || cookies.length < 1) return; + + for (Cookie cookie: cookies) { + removeCookie(cookie, "JSESSIONID", "username"); + hresp.addCookie(cookie); + } + } + + /**지정하는 이름의 쿠키값을 삭제한다. + * @param cookie 쿠키 + * @param cookieNames 쿠키값의 이름 + */ + protected void removeCookie(Cookie cookie, String... cookieNames) { + for (String name: cookieNames) { + if (!name.equals(cookie.getName())) continue; + + cookie.setMaxAge(0); + cookie.setValue(""); + } + } + + @Override + public void setApplicationContext(ApplicationContext actx) throws BeansException { + if (!(actx instanceof WebApplicationContext)) return; + + WebApplicationContext wactx = (WebApplicationContext)actx; + wactx.getServletContext().addListener(this); + } + + @Override + public void sessionCreated(HttpSessionEvent evt) {} + + @Override + public void sessionDestroyed(HttpSessionEvent evt) { + HttpSession session = evt.getSession(); + SecurityContext sctx = (SecurityContext)session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); + if (sctx == null) return; + + Authentication authentication = sctx.getAuthentication(); + log().debug("Session expired for {}", authentication.getPrincipal()); + authenticationService.onLogout(authentication); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/web/PolicyController.java b/src/main/java/cokr/xit/base/security/authentication/web/PolicyController.java new file mode 100644 index 0000000..b1d3646 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/web/PolicyController.java @@ -0,0 +1,97 @@ +package cokr.xit.base.security.authentication.web; + +import javax.annotation.Resource; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.ModelAndView; + +import cokr.xit.base.security.authentication.AuthenticationPolicy; +import cokr.xit.base.security.authentication.service.AuthenticationService; +import cokr.xit.foundation.web.AbstractController; + +/**인증 정책 컨트롤러
+ * {웹 컨텍스트}/authentication/policy/*로 접근할 수 있다. + * @param 인증 정책 유형 + * @author mjkhan + */ +@RequestMapping(name="인증 정책", value="/authentication/policy") +public class PolicyController extends AbstractController { + @Resource(name="authenticationService") + private AuthenticationService authenticationService; + + /**사용자 인증 서비스를 반환한다. + * @return 사용자 인증 서비스 + */ + protected AuthenticationService authenticationService() { + return authenticationService; + } + + /**주어진 조건의 사용자 인증 정책 목록을 반환한다. + * @param by 조건 필드 이름 + *
  • userName - 사용자 이름
  • + *
  • 그 이외는 사용자 아이디
  • + *
+ * @param term 조건 필드값 + * @param pageNum 페이지 이름 + * @return jsonView + *
{
+	 *     "policyList": 정책 목록,
+	 *     "policyStart": 정책 시작,
+	 *     "policyFetch": 한 번에 가져온 정책수,
+	 *     "policyTotal": 조회 결과 찾은 전체 정책수
+	 * }
+ */ + @RequestMapping(name="인증 정책 조회", value="/list.do") + public ModelAndView getPolicies(String by, String term, @RequestParam(required=false, defaultValue="1") Integer pageNum) { + int fetchSize = properties.getInt("pageSize"); + return setCollectionInfo( + new ModelAndView("jsonView"), + authenticationService.getPolicyList(by, term, pageNum, fetchSize), + "policy" + ); + } + + /**인증 정책을 등록한다. + * @param policy 인증 정책 + * @return jsonView + *
{
+	 *     "saved": true || false
+	 * }
+ */ + @PostMapping(name="인증 정책 등록", value="/created.do") + public ModelAndView create(T policy) { + return new ModelAndView("jsonView") + .addObject("saved", authenticationService.create(policy)); + } + + /**인증 정책을 수정한다. + * @param policy 인증 정책 + * @return jsonView + *
{
+	 *     "saved": true || false
+	 * }
+ */ + @PostMapping(name="인증 정책 수정", value="/update.do") + public ModelAndView update(T policy) { + return new ModelAndView("jsonView") + .addObject("saved", authenticationService.update(policy)); + } + + /**지정한 사용자의 인증 정책을 제거한다. + * @param userIDs 사용자 아이디 + * @return jsonView + *
{
+	 *     "affected": 저장된 정보수,
+	 *     "saved": true || false
+	 * }
+ */ + @PostMapping(name="인증 정책 제거", value="/remove.do") + public ModelAndView remove(String... userIDs) { + int affected = authenticationService.removePolicies(userIDs); + return new ModelAndView("jsonView") + .addObject("affected", affected) + .addObject("saved", affected > 0); + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/authentication/web/package-info.java b/src/main/java/cokr/xit/base/security/authentication/web/package-info.java new file mode 100644 index 0000000..44aaea8 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/authentication/web/package-info.java @@ -0,0 +1,3 @@ +/**사용자 인증 웹 구현부 + */ +package cokr.xit.base.security.authentication.web; \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/crypto/Encryptor.java b/src/main/java/cokr/xit/base/security/crypto/Encryptor.java new file mode 100644 index 0000000..209f7d1 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/crypto/Encryptor.java @@ -0,0 +1,80 @@ +package cokr.xit.base.security.crypto; + +import java.security.Key; +import java.util.Base64; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; + +import cokr.xit.foundation.AbstractComponent; + +public class Encryptor extends AbstractComponent { + private String algorithm; + private Key key; + private IvParameterSpec iv; + private Cipher cipher; + + /**알고리즘을 반환한다. + * @return 알고리즘 + */ + public String getAlgorithm() { + return algorithm; + } + + /**알고리즘을 설정한다. + * @param algorithm 알고리즘 + * @return Encryptor + */ + public Encryptor setAlgorithm(String algorithm) { + this.algorithm = algorithm; + return this; + } + + /**SecretKey를 반환한다. + * @return SecretKey + */ + public Key getKey() { + return key; + } + + /**SecretKey를 설정한다. + * @param key SecretKey + * @return Encryptor + */ + public Encryptor setKey(Key key) { + this.key = key; + return this; + } + + /**iv를 반환한다. + * @return iv + */ + public IvParameterSpec getIv() { + return iv; + } + + /**iv를 설정한다. + * @param iv iv + * @return Encryptor + */ + public Encryptor setIv(IvParameterSpec iv) { + this.iv = iv; + return this; + } + + public String encrypt(String str) { + if (isEmpty(str)) return ""; + + try { + if (cipher == null) { + cipher = Cipher.getInstance(algorithm); + cipher.init(Cipher.ENCRYPT_MODE, key, iv); + } + byte[] cipherText = cipher.doFinal(str.getBytes()); + return Base64.getEncoder() + .encodeToString(cipherText); + } catch (Exception e) { + throw applicationException(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/crypto/KeyFactory.java b/src/main/java/cokr/xit/base/security/crypto/KeyFactory.java new file mode 100644 index 0000000..cb76a3a --- /dev/null +++ b/src/main/java/cokr/xit/base/security/crypto/KeyFactory.java @@ -0,0 +1,85 @@ +package cokr.xit.base.security.crypto; + +import java.security.Key; +import java.security.SecureRandom; +import java.security.spec.KeySpec; +import java.util.Arrays; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import cokr.xit.foundation.AbstractComponent; + +public class KeyFactory extends AbstractComponent { + private int keySize; + private String algorithm; + private KeyGenerator keyGen; + + /**키 사이즈를 반환한다. + * @return 키 사이즈 + */ + public int getKeySize() { + return Math.max(128, keySize); + } + + /**키 사이즈를 설정한다. + * @param keySize 키 사이즈(128, 192, 또는 256 비트) + * @return KeyFactory + */ + public KeyFactory setKeySize(int keySize) { + if (!Arrays.asList(128, 192, 256).contains(keySize)) + throw new IllegalArgumentException("keySize must be either of 128, 192, 256"); + + this.keySize = keySize; + return this; + } + + /**알고리즘을 반환한다. + * @return 알고리즘 + */ + public String getAlgorithm() { + return ifEmpty(algorithm, "AES"); + } + + /**알고리즘을 설정한다. + * @param algorithm 알고리즘 + * @return KeyFactory + */ + public KeyFactory setAlgorithm(String algorithm) { + this.algorithm = algorithm; + return this; + } + + public static IvParameterSpec generateIv(int size) { + byte[] iv = new byte[size]; + new SecureRandom().nextBytes(iv); + return new IvParameterSpec(iv); + } + + public Key create() { + try { + if (keyGen == null) { + keyGen = KeyGenerator.getInstance(getAlgorithm()); + keyGen.init(getKeySize()); + } + return keyGen.generateKey(); + } catch (Exception e) { + throw applicationException(e); + } + } + + public Key create(String str, String salt) { + if (isEmpty(str) || isEmpty(salt)) return null; + + try { + SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + KeySpec spec = new PBEKeySpec(str.toCharArray(), salt.getBytes(), 65536, getKeySize()); + return new SecretKeySpec(skf.generateSecret(spec).getEncoded(), getAlgorithm()); + } catch (Exception e) { + throw applicationException(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/security/package-info.java b/src/main/java/cokr/xit/base/security/package-info.java new file mode 100644 index 0000000..a4c9981 --- /dev/null +++ b/src/main/java/cokr/xit/base/security/package-info.java @@ -0,0 +1,13 @@ +/**보안 정보 서비스 모듈. + *

애플리케이션이 스프링 프레임워크의 보안 모듈(spring-security-*) 중 + *

  • 사용자 인증(authentication)
  • + *
  • 사용자 권한(authority)
  • + *
+ * 부분을 사용하는 것을 돕는다. + *

보안 정보 서비스는 인증 정보와 권한 정보를 데이터베이스 테이블에 저장하여 관리한다.
+ * 보안 정보 테이블의 자세한 내용은 각 하위 모듈의 문서를 참고한다. + *

  • 배포 모듈: xit-security-yy.mm.dd.jar
  • + *
  • 의존 모듈: {@link cokr.xit.foundation xit-foundation-yy.mm.dd.jar}
  • + *
+ */ +package cokr.xit.base.security; \ No newline at end of file diff --git a/src/test/java/cokr/xit/base/security/access/service/ActionGroupServiceTest.java b/src/test/java/cokr/xit/base/security/access/service/ActionGroupServiceTest.java new file mode 100644 index 0000000..05b67e4 --- /dev/null +++ b/src/test/java/cokr/xit/base/security/access/service/ActionGroupServiceTest.java @@ -0,0 +1,101 @@ +package cokr.xit.base.security.access.service; + +import java.util.List; + +import javax.annotation.Resource; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import cokr.xit.base.security.access.ActionGroup; +import cokr.xit.foundation.data.DataObject; +import cokr.xit.foundation.test.TestSupport; + +public class ActionGroupServiceTest extends TestSupport { + @Resource(name = "actionGroupService") + private ActionGroupService actionGroupService; + + @Test + public void getGroupList() { + ActionQuery req = new ActionQuery(); + actionGroupService.getGroupList(req); + actionGroupService.getGroupList(req.setBy("groupID")); + actionGroupService.getGroupList(req.setTerm("group-0")); + actionGroupService.getGroupList(req.setBy(null).setTerm("group-zero")); + + req.setBy(null).setTerm(null).setFetchSize(10); + actionGroupService.getGroupList(req); + actionGroupService.getGroupList(req.setBy("groupID")); + actionGroupService.getGroupList(req.setTerm("group-0")); + actionGroupService.getGroupList(req.setBy("groupID").setTerm("group-zero")); + + actionGroupService.getGroups("group-0", "group-1", "group-2"); + actionGroupService.getGroups("group-0"); + } + + private ActionGroup createGroup(String id) { + ActionGroup group = new ActionGroup(); + group.setId(id); + group.setName(id + " name"); + group.setDescription(id + " description"); + boolean created = actionGroupService.create(group); + return created ? group : null; + } + + @Test + public void saveGroup() { + String groupID = "group-0"; + ActionGroup created = createGroup(groupID); + Assertions.assertNotNull(created); + + ActionGroup loaded = actionGroupService.getGroup(groupID); + Assertions.assertNotNull(loaded); + + Assertions.assertThrows(Exception.class, () -> actionGroupService.create(created)); + + created.setName("그룹 이름"); + Assertions.assertTrue(actionGroupService.update(created)); + + loaded = actionGroupService.getGroup(groupID); + Assertions.assertEquals(created.getName(), loaded.getName()); + + deleteGroup(groupID); + loaded = actionGroupService.getGroup(groupID); + Assertions.assertNull(loaded); + } + + @Test + public void saveActions() { + String groupID = "group-0"; + String[] actions = {"action-0", "action-1", "action-2"}; + ActionQuery req = new ActionQuery().setGroupIDs(groupID).setPageNum(1); + + createGroup(groupID); + + actionGroupService.addActions(groupID, actions); + + List actionList = actionGroupService.getActionList(req); + Assertions.assertEquals(actions.length, actionList.size()); + + actionGroupService.addActions(groupID, actions); + actionList = actionGroupService.getActionList(req); + Assertions.assertEquals(actions.length, actionList.size()); + + actions = new String[] {"action-0", "action-1", "action-2", "action-3"}; + actionGroupService.addActions(groupID, actions); + actionList = actionGroupService.getActionList(req); + Assertions.assertEquals(actions.length, actionList.size()); + + actionGroupService.removeActions(groupID, "action-2", "action-3"); + actionList = actionGroupService.getActionList(req); + Assertions.assertEquals(2, actionList.size()); + + deleteGroup(groupID); + actionList = actionGroupService.getActionList(req); + Assertions.assertTrue(actionList.isEmpty()); + } + + private void deleteGroup(String... groupIDs) { + actionGroupService.remove(groupIDs); + } +} diff --git a/src/test/java/cokr/xit/base/security/access/service/AuthorityServiceTest.java b/src/test/java/cokr/xit/base/security/access/service/AuthorityServiceTest.java new file mode 100644 index 0000000..f50ec47 --- /dev/null +++ b/src/test/java/cokr/xit/base/security/access/service/AuthorityServiceTest.java @@ -0,0 +1,159 @@ +package cokr.xit.base.security.access.service; + +import java.util.List; + +import javax.annotation.Resource; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import cokr.xit.base.security.Authority; +import cokr.xit.foundation.data.DataObject; +import cokr.xit.foundation.test.TestSupport; + +public class AuthorityServiceTest extends TestSupport { + @Resource(name = "authorityService") + private AuthorityService authorityService; + + @Test + public void getAuthorityList() { + AuthorityQuery req = new AuthorityQuery(); + authorityService.getAuthorityList(req); + authorityService.getAuthorityList(req.setBy("authID")); + authorityService.getAuthorityList(req.setTerm("auth-0")); + authorityService.getAuthorityList(req.setBy(null).setTerm("auth-zero")); + authorityService.getAuthorityList(req.setBy(null).setTerm("auth-zero")); + + req = new AuthorityQuery(); + authorityService.getAuthorityList(req.setPageNum(1).setFetchSize(10)); + authorityService.getAuthorityList(req.setBy("authID")); + authorityService.getAuthorityList(req.setTerm("auth-0")); + authorityService.getAuthorityList(req.setBy(null).setTerm("auth-zero")); + + authorityService.getAuthorities("auth-0", "auth-1", "auth-2"); + authorityService.getAuthorities("auth-0"); + + Authority auth = authorityService.getAuthority("ROLE_ADMIN"); + Assertions.assertNotNull(auth); + Assertions.assertEquals(Authority.Type.SUPER, auth.type()); + + auth = authorityService.getAuthority("ROLE_USER"); + Assertions.assertNotNull(auth); + Assertions.assertEquals(Authority.Type.IMPLICIT, auth.type()); + + auth = authorityService.getAuthority("ROLE_ANONYMOUS"); + Assertions.assertNotNull(auth); + Assertions.assertEquals(Authority.Type.IMPLICIT, auth.type()); + } + + private Authority createAuthority(String id) { + Authority authority = new Authority(); + authority.setId(id); + authority.setName(id + " name"); + authority.setDescription(id + " description"); + boolean created = authorityService.create(authority); + return created ? authority : null; + } + + @Test + public void saveAuthority() { + String authID = "auth-0"; + Authority created = createAuthority(authID); + Assertions.assertNotNull(created); + + Authority loaded = authorityService.getAuthority(authID); + Assertions.assertNotNull(loaded); + Assertions.assertEquals(Authority.Type.SYS_DEFINED, loaded.type()); + + created.setName("권한 이름"); + Assertions.assertTrue(authorityService.update(created)); + + loaded = authorityService.getAuthority(authID); + Assertions.assertEquals(created.getName(), loaded.getName()); + + deleteAuthority(authID); + loaded = authorityService.getAuthority(authID); + Assertions.assertNull(loaded); + } + + @Test + public void saveActionGroups() { + String authID = "auth-0"; + String[] groupIDs = {"group-0", "group-1", "group-2"}; + AuthorityQuery req = new AuthorityQuery().setAuthIDs(authID); + + authorityService.addActionGroups(authID, groupIDs); + List actionList = authorityService.getActionGroupList(req); + Assertions.assertEquals(groupIDs.length, actionList.size()); + req.setPageNum(1).setFetchSize(10); + actionList = authorityService.getActionGroupList(req); + Assertions.assertEquals(groupIDs.length, actionList.size()); + + authorityService.addActionGroups(authID, groupIDs); + actionList = authorityService.getActionGroupList(req.setPageNum(0).setFetchSize(0)); + Assertions.assertEquals(groupIDs.length, actionList.size()); + actionList = authorityService.getActionGroupList(req.setPageNum(1).setFetchSize(10)); + Assertions.assertEquals(groupIDs.length, actionList.size()); + + groupIDs = new String[] {"group-0", "group-1", "group-2", "group-3"}; + authorityService.addActionGroups(authID, groupIDs); + actionList = authorityService.getActionGroupList(req.setPageNum(0).setFetchSize(0)); + Assertions.assertEquals(groupIDs.length, actionList.size()); + actionList = authorityService.getActionGroupList(req.setPageNum(1).setFetchSize(10)); + Assertions.assertEquals(groupIDs.length, actionList.size()); + + authorityService.removeActionGroups(authID, "group-2", "group-3"); + actionList = authorityService.getActionGroupList(req.setPageNum(0).setFetchSize(0)); + Assertions.assertEquals(2, actionList.size()); + actionList = authorityService.getActionGroupList(req.setPageNum(1).setFetchSize(10)); + Assertions.assertEquals(2, actionList.size()); + + deleteAuthority(authID); + actionList = authorityService.getActionGroupList(req.setPageNum(0).setFetchSize(0)); + Assertions.assertTrue(actionList.isEmpty()); + actionList = authorityService.getActionGroupList(req.setPageNum(1).setFetchSize(10)); + Assertions.assertTrue(actionList.isEmpty()); + } + + @Test + public void saveUsers() { + String authID = "auth-0"; + String[] userIDs = {"user-0", "user-1", "user-2"}; + AuthorityQuery req = new AuthorityQuery().setAuthIDs(authID); + + authorityService.addUsers(authID, userIDs); + List userList = authorityService.getUserList(req); + Assertions.assertEquals(userIDs.length, userList.size()); + userList = authorityService.getUserList(req.setPageNum(1).setFetchSize(10)); + Assertions.assertEquals(userIDs.length, userList.size()); + + authorityService.addUsers(authID, userIDs); + userList = authorityService.getUserList(req.setPageNum(0).setFetchSize(0)); + Assertions.assertEquals(userIDs.length, userList.size()); + userList = authorityService.getUserList(req.setPageNum(1).setFetchSize(10)); + Assertions.assertEquals(userIDs.length, userList.size()); + + userIDs = new String[] {"user-0", "user-1", "user-2", "user-3"}; + authorityService.addUsers(authID, userIDs); + userList = authorityService.getUserList(req.setPageNum(0).setFetchSize(0)); + Assertions.assertEquals(userIDs.length, userList.size()); + userList = authorityService.getUserList(req.setPageNum(1).setFetchSize(10)); + Assertions.assertEquals(userIDs.length, userList.size()); + + authorityService.removeUsers(authID, "user-2", "user-3"); + userList = authorityService.getUserList(req.setPageNum(0).setFetchSize(0)); + Assertions.assertEquals(2, userList.size()); + userList = authorityService.getUserList(req.setPageNum(1).setFetchSize(10)); + Assertions.assertEquals(2, userList.size()); + + deleteAuthority(authID); + userList = authorityService.getUserList(req.setPageNum(0).setFetchSize(0)); + Assertions.assertTrue(userList.isEmpty()); + userList = authorityService.getUserList(req.setPageNum(1).setFetchSize(10)); + Assertions.assertTrue(userList.isEmpty()); + } + + private void deleteAuthority(String... authIDs) { + authorityService.removeAuthorities(authIDs); + } +} diff --git a/src/test/java/cokr/xit/base/security/authentication/service/AuthenticationServiceTest.java b/src/test/java/cokr/xit/base/security/authentication/service/AuthenticationServiceTest.java new file mode 100644 index 0000000..4302c13 --- /dev/null +++ b/src/test/java/cokr/xit/base/security/authentication/service/AuthenticationServiceTest.java @@ -0,0 +1,70 @@ +package cokr.xit.base.security.authentication.service; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.annotation.Resource; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.security.core.userdetails.UserDetails; + +import cokr.xit.base.security.authentication.service.bean.PasswordEncoder; +import cokr.xit.foundation.test.TestSupport; + +public class AuthenticationServiceTest extends TestSupport { + @Resource(name = "passwordEncoder") + private PasswordEncoder passwordEncoder; + @Resource(name = "authenticationService") + private AuthenticationService authenticationService; + + @Test + void passwordEncoder() { + String str = "5811807"; + String encoded = passwordEncoder.encode(str); + System.out.println("encoded: " + encoded); + Assertions.assertTrue(passwordEncoder.matches(str, encoded)); + } + + @Test + void loadUserByUsername() { + String userID = "test-user"; + create(userID); + + UserDetails userDetails = authenticationService.loadUserByUsername(userID); + Assertions.assertNotNull(userDetails); + + deleteUsers(userID); + } + + protected boolean create(String userID) { + String name = userID + " name"; + String password = userID + "-password"; + String insert = "INSERT INTO TBL_USER (\r\n" + + " USER_ID\r\n" + + " , USER_NM\r\n" + + " , PASSWD\r\n" + + " , LOCK_CNT\r\n" + + " , INS_DT\r\n" + + " , STATUS\r\n" + + ") VALUES (\r\n" + + " '" + userID + "'\r\n" + + " , '" + name + "'\r\n" + + " , '" + password + "'\r\n" + + " , 0\r\n" + + " , CURRENT_TIMESTAMP()\r\n" + + " , 'A'\r\n" + + ")"; + return testDao.execInsert(insert) == 1; + } + + protected void deleteUsers(String... userIDs) { + String condition = Stream.of(userIDs) + .map(userID -> "'" + userID + "'") + .collect(Collectors.joining(",")); + + testDao.execDelete("DELETE FROM TBL_USER WHERE USER_ID IN (" + condition + ")"); + testDao.commit(); + } + +} \ No newline at end of file diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml new file mode 100644 index 0000000..30db4b4 --- /dev/null +++ b/src/test/resources/log4j2.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/log4jdbc.log4j2.properties b/src/test/resources/log4jdbc.log4j2.properties new file mode 100644 index 0000000..3b8ff2b --- /dev/null +++ b/src/test/resources/log4jdbc.log4j2.properties @@ -0,0 +1,4 @@ +log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator + +log4jdbc.dump.sql.maxlinelength=0 +log4jdbc.drivers=org.mariadb.jdbc.Driver diff --git a/src/test/resources/lombok.config b/src/test/resources/lombok.config new file mode 100644 index 0000000..0a8874c --- /dev/null +++ b/src/test/resources/lombok.config @@ -0,0 +1,2 @@ +# see https://projectlombok.org/features/constructor lombok.copyableAnnotations +lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier diff --git a/src/test/resources/message/authentication-message.properties b/src/test/resources/message/authentication-message.properties new file mode 100644 index 0000000..a043355 --- /dev/null +++ b/src/test/resources/message/authentication-message.properties @@ -0,0 +1,5 @@ +authenticationFailure.usernameNotFound=\uc0ac\uc6a9\uc790\ub97c \ucc3e\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. +authenticationFailure.badCredentials=\uc0ac\uc6a9\uc790 \uc544\uc774\ub514\ub098 \ube44\ubc00\ubc88\ud638\uac00 \uc798\ubabb\ub410\uc2b5\ub2c8\ub2e4. +authenticationFailure.credentialsExpired=\ube44\ubc00\ubc88\ud638\uac00 \ub9cc\ub8cc\ub410\uc2b5\ub2c8\ub2e4. +authenticationFailure.authenticationFailed=\uc0ac\uc6a9\uc790\ub97c \uc778\uc99d\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. +authenticationFailure.policyViolated=\ub85c\uadf8\uc778 \uc815\ucc45\uc5d0 \ub530\ub77c \uc811\uadfc\uc774 \ucc28\ub2e8\ub410\uc2b5\ub2c8\ub2e4. \ No newline at end of file diff --git a/src/test/resources/message/message-common.properties b/src/test/resources/message/message-common.properties new file mode 100644 index 0000000..02e921c --- /dev/null +++ b/src/test/resources/message/message-common.properties @@ -0,0 +1,382 @@ +valueRequired={0}\uac00 \uc124\uc815\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. + +pageNotFound=\uc694\uccad\ud558\uc2e0 \ud398\uc774\uc9c0\ub97c \ucc3e\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. +sessionExpired=\uc138\uc158\uc774 \ub9cc\ub8cc\ub410\uc2b5\ub2c8\ub2e4. + +duplicate.object=\uc774\ubbf8 \ub4f1\ub85d\ub41c {0}\uc785\ub2c8\ub2e4. + +fail.common.msg=\uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! +fail.common.sql=sql \uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! error code: {0}, error msg: {1} +info.nodata.msg=\ud574\ub2f9 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. + +#UI Common resource# +table.num=\ubc88\ud638 +table.regdate=\ub4f1\ub85d\uc77c +table.reger=\ub4f1\ub85d\uc790 +table.select=\uc120\ud0dd +title.html=egovframe common component +title.detail=\uc0c1\uc138\uc870\ud68c +title.inquire=\uc870\ud68c +title.update=\uc218\uc815 +title.create=\ub4f1\ub85d +title.delete=\uc0ad\uc81c +title.save=\uc800\uc7a5 +title.list=\ubaa9\ub85d +title.searchCondition=\uac80\uc0c9\uc870\uac74 +title.search=\uac80\uc0c9\uc5b4 +title.reply=\ub2f5\uae00 +title.scrap=\uc2a4\ud06c\ub7a9 +title.comment=\ub313\uae00 +title.attachedFileSelect=\ud30c\uc77c\uc120\ud0dd +title.attachedFileDelete=\ud30c\uc77c\uc0ad\uc81c +title.link=\ub9c1\ud06c +title.management=\uad00\ub9ac +title.all=\uc804\uccb4 + +input.select=\uc120\ud0dd\ud558\uc138\uc694 +input.cSelect=\uc120\ud0dd +input.input=\uc785\ub825 +input.button=\ubc84\ud2bc +input.selectAll.title=\uc804\uccb4\uc120\ud0dd\uccb4\ud06c\ubc15\uc2a4 +input.yes=\uc608 +input.no=\uc544\ub2c8\uc624 + +select.searchCondition=\uc870\ud68c\uc870\uac74 \uc120\ud0dd + +button.select=\uc120\ud0dd +button.search=\uac80\uc0c9 +button.use=\uc0ac\uc6a9 +button.notUsed=\uc0ac\uc6a9\uc911\uc9c0 +button.inquire=\uc870\ud68c +button.update=\uc218\uc815 +button.create=\ub4f1\ub85d +button.delete=\uc0ad\uc81c +button.deleteDatabase=\uc644\uc804\uc0ad\uc81c +button.close=\ub2eb\uae30 +button.save=\uc800\uc7a5 +button.list=\ubaa9\ub85d +button.reset=\ucde8\uc18c +button.passwordUpdate=\uc554\ud638\ubcc0\uacbd +button.subscribe=\uac00\uc785\uc2e0\uccad +button.realname=\uc2e4\uba85\ud655\uc778 +button.moveToGpin=GPIN\uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.moveToIhidnum=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638 \uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.agree=\ub3d9\uc758 +button.disagree=\ube44\ub3d9\uc758 +button.possible=\uac00\ub2a5 +button.impossible=\ubd88\uac00\ub2a5 +button.qnaregist=Q&A\ub4f1\ub85d +button.cnsltregist=\uc0c1\ub2f4\ub4f1\ub85d +button.preview=\ubbf8\ub9ac\ubcf4\uae30 +button.next=\ub2e4\uc74c +button.add=\ubc14\ub85c\ucd94\uac00 +button.confirm=\ud655\uc778 +button.back = \ub4a4\ub85c +button.yes = \uc608 +button.no = \uc544\ub2c8\uc624 +button.home = \ud648 +button.user = \uc0ac\uc6a9\uc790\uc9c0\uc6d0 +button.cop = \ud611\uc5c5 +button.wrkstart = \ucd9c\uadfc +button.wrkend = \ud1f4\uadfc +button.reply = \ub2f5\uae00 +button.scrap = \uc2a4\ud06c\ub7a9 +button.comment = \ub313\uae00 +button.excel = \uc5d1\uc140 +button.init=\ucd08\uae30\ud654 +button.acknowledgment=\uc2b9\uc778 +button.cancelAcknowledgment=\uc2b9\uc778\ucde8\uc18c +button.bulkUpload=\uc77c\uad04\ub4f1\ub85d +button.log = \ub85c\uadf8 +button.set = \uc124\uc815 +button.move = \uc774\ub3d9 + + +#UI Common Message# +common.noScriptTitle.msg=\uc790\ubc14\uc2a4\ud06c\ub9bd\ud2b8\ub97c \uc9c0\uc6d0\ud558\uc9c0 \uc54a\ub294 \ube0c\ub77c\uc6b0\uc800\uc5d0\uc11c\ub294 \uc77c\ubd80 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uc2e4 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +common.searchCondition.msg=\uc774 \ub808\uc774\uc544\uc6c3\uc740 \ud558\ub2e8 \uc815\ubcf4\ub97c \ub300\ud55c \uac80\uc0c9 \uc815\ubcf4\ub85c \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4. + +common.summary.list={0}\uc758 \ub0b4\uc5ed\uc5d0 \ub300\ud55c \ubaa9\ub85d\uc744 \ucd9c\ub825\ud569\ub2c8\ub2e4. +common.summary.regist={0}\uc758 \ub4f1\ub85d \uac00\ub2a5\ud55c \ub0b4\uc6a9\uc744 \uc785\ub825\ud558\uc5ec \ub4f1\ub85d \ubc84\ud2bc\uc744 \ud074\ub9ad\ud558\uc5ec \ub4f1\ub85d\ud55c\ub2e4. +common.summary.update={0}\uc758 \ub4f1\ub85d \uac00\ub2a5\ud55c \ub0b4\uc6a9\uc744 \uc785\ub825\ud558\uc5ec \ub4f1\ub85d \ubc84\ud2bc\uc744 \ud074\ub9ad\ud558\uc5ec \uc218\uc815\ud55c\ub2e4. +common.summary.inqire={0}\uc758 \ub0b4\uc5ed\uc5d0 \ub300\ud55c \uc0c1\uc138\uc870\ud68c \ub0b4\uc5ed\uc744 \ucd9c\ub825\ud569\ub2c8\ub2e4. + +common.save.msg=\uc800\uc7a5\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.regist.msg=\ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.delete.msg=\uc0ad\uc81c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.update.msg=\uc218\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.nodata.msg=\uc790\ub8cc\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\ub978 \uac80\uc0c9\uc870\uac74\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694 +common.required.msg=(\uc740)\ub294 \ud544\uc218\uc785\ub825\ud56d\ubaa9\uc785\ub2c8\ub2e4. +common.acknowledgement.msg=\uc2b9\uc778\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.acknowledgementcancel.msg=\uc2b9\uc778\ucde8\uc18c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.nocomment.msg=\ub313\uae00\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. +common.noguest.msg=\uc791\uc131\ub41c \ubc29\uba85\ub85d\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. + +success.request.msg = \uc694\uccad\ucc98\ub9ac\uac00 \uc131\uacf5\uc801\uc73c\ub85c \uc218\ud589\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.select=\uc815\uc0c1\uc801\uc73c\ub85c \uc870\ud68c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.insert=\uc815\uc0c1\uc801\uc73c\ub85c \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.update=\uc815\uc0c1\uc801\uc73c\ub85c \uc218\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.delete=\uc815\uc0c1\uc801\uc73c\ub85c \uc0ad\uc81c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +common.imposbl.fileupload = \ub354 \uc774\uc0c1 \ud30c\uc77c\uc744 \ucca8\ubd80\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +common.isConfmDe.msg=\uc2b9\uc778\uc77c\uc790\ub97c \ud655\uc778 \ubc14\ub78d\ub2c8\ub2e4. +common.isExist.msg = \uc774\ubbf8 \uc874\uc7ac\ud558\uac70\ub098 \uacfc\uac70\uc5d0 \ub4f1\ub85d\uc774 \ub418\uc5c8\ub358 \uc0c1\ud0dc\uc785\ub2c8\ub2e4. + +fail.common.insert = \uc0dd\uc131\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.update = \uc218\uc815\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete = \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete.upperMenuExist = \ucc38\uc870\ub418\ub294 \uba54\ub274\uac00 \uc788\uc5b4 \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.select = \uc870\ud68c\uc5d0 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.login = \ub85c\uadf8\uc778 \uc815\ubcf4\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.common.loginIncorrect = {0}\ud68c \uc774\uc0c1 \ub85c\uadf8\uc778 \uc811\uc18d\uc774 \uc2dc\ub3c4 \ub418\uc5b4 \uacc4\uc815\uc774 \uc7a0\uaca8\uc2b5\ub2c8\ub2e4! +fail.common.login.password = \ud328\uc2a4\uc6cc\ub4dc \uc790\ub9ac \uc218\uac00 \uc77c\uce58 \ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.(8\uc790\ub9ac \uc774\uc0c1 20\uc790\ub9ac \uc774\ud558) +fail.common.idsearch = \uc544\uc774\ub514\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.common.pwsearch = \ube44\ubc00\ubc88\ud638\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.request.msg = \uc694\uccad\ucc98\ub9ac\ub97c \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.login.ip = \ub4f1\ub85d\ub41c IP\uac00 \uc544\ub2c8\ubbc0\ub85c \ub85c\uadf8\uc778\uc774 \uac70\ubd80\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +#UI User Message# +fail.user.passwordUpdate1=\ud604\uc7ac \ube44\ubc00\ubc88\ud638\uac00 \ub9de\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.passwordUpdate2=\ube44\ubc00\ubc88\ud638\uc640 \ube44\ubc00\ubc88\ud638 \ud655\uc778\uc774 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +info.user.rlnmCnfirm=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.connectFail=\uc2dc\uc2a4\ud15c \uc7a5\uc560\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.(\uc778\uc99d\uc11c\ubc84 \uc5f0\uacb0 \uc2e4\ud328) +info.user.rlnmPinCnfirm=\uacf5\uacf5 \uc544\uc774\ud540 \uc544\uc774\ub514\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + + +#UI Cop Message# +cop.extrlUser = \uc678\ubd80\uc0ac\uc6a9\uc790 +cop.intrlUser = \ub0b4\ubd80\uc0ac\uc6a9\uc790 +cop.private = \ube44\uacf5\uac1c +cop.public = \uacf5\uac1c + +cop.adbkNm = \uc8fc\uc18c\ub85d\uba85 +cop.othbcScope = \uacf5\uac1c\ubc94\uc704 +cop.company = \ud68c\uc0ac +cop.part = \ubd80\uc11c +cop.man = \uac1c\uc778 +cop.adbkUser = \uad6c\uc131\uc6d0 +cop.bbsNm = \uac8c\uc2dc\ud310\uba85 +cop.bbsIntrcn = \uac8c\uc2dc\ud310\uc18c\uac1c +cop.bbsTyCode = \uac8c\uc2dc\ud310 \uc720\ud615 +cop.bbsAttrbCode = \uac8c\uc2dc\ud310 \uc18d\uc131 +cop.replyPosblAt = \ub2f5\uc7a5\uac00\ub2a5\uc5ec\ubd80 +cop.fileAtchPosblAt = \ud30c\uc77c\ucca8\ubd80\uac00\ub2a5\uc5ec\ubd80 +cop.posblAtchFileNumber = \ucca8\ubd80\uac00\ub2a5\ud30c\uc77c \uc22b\uc790 +cop.tmplatId = \ud15c\ud50c\ub9bf \uc815\ubcf4 +cop.guestList.subject = \ubc29\uba85\ub85d \uac8c\uc2dc\uae00\uc785\ub2c8\ub2e4. +cop.nttSj = \uc81c\ubaa9 +cop.nttCn = \uae00\ub0b4\uc6a9 +cop.ntceBgnde = \uac8c\uc2dc\uc2dc\uc791\uc77c +cop.ntceEndde = \uac8c\uc2dc\uc885\ub8cc\uc77c +cop.ntcrNm = \uc791\uc131\uc790 +cop.password = \ud328\uc2a4\uc6cc\ub4dc +cop.atchFile = \ud30c\uc77c\ucca8\ubd80 +cop.guestList = \ubc29\uba85\ub85d +cop.guestListCn = \ubc29\uba85\ub85d \ub0b4\uc6a9 +cop.noticeTerm = \uac8c\uc2dc\uae30\uac04 +cop.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +cop.cmmntyNm = \ucee4\ubba4\ub2c8\ud2f0\uba85 +cop.cmmntyIntrcn = \ucee4\ubba4\ub2c8\ud2f0 \uc18c\uac1c +cop.cmmntyMngr = \ucee4\ubba4\ub2c8\ud2f0 \uad00\ub9ac\uc790 +cop.clbOprtr = \ub3d9\ud638\ud68c \uc6b4\uc601\uc790 +cop.clbIntrcn = \ub3d9\ud638\ud68c \uc18c\uac1c +cop.clbNm = \ub3d9\ud638\ud68c \uba85 +cop.tmplatNm = \ud15c\ud50c\ub9bf\uba85 +cop.tmplatSeCode = \ud15c\ud50c\ub9bf \uad6c\ubd84 +cop.tmplatCours = \ud15c\ud50c\ub9bf\uacbd\ub85c +cop.useAt = \uc0ac\uc6a9\uc5ec\ubd80 +cop.ncrdNm = \uc774\ub984 +cop.cmpnyNm = \ud68c\uc0ac\uba85 +cop.deptNm = \ubd80\uc11c\uba85 +cop.ofcpsNm = \uc9c1\uc704 +cop.clsfNm = \uc9c1\uae09 +cop.emailAdres = \uc774\uba54\uc77c\uc8fc\uc18c +cop.telNo = \uc804\ud654\ubc88\ud638 +cop.mbtlNum = \ud734\ub300\ud3f0\ubc88\ud638 +cop.adres = \uc8fc\uc18c +cop.extrlUserAt = \uc678\ubd80\uc0ac\uc6a9\uc790\uc5ec\ubd80 +cop.publicAt = \uacf5\uac1c\uc5ec\ubd80 +cop.remark = \ube44\uace0 +cop.trgetNm = \ucee4\ubba4\ub2c8\ud2f0/\ub3d9\ud638\ud68c \uc815\ubcf4 +cop.preview = \ubbf8\ub9ac\ubcf4\uae30 + +cop.withdraw.msg=\ud0c8\ud1f4\ucc98\ub9ac \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.reregist.msg=\uc7ac\uac00\uc785 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.registmanager.msg=\uc6b4\uc601\uc9c4\uc73c\ub85c \ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.use.msg=\uc0ac\uc6a9 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.unuse.msg=\uc0ac\uc6a9\uc911\uc9c0 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.delete.confirm.msg=\uc0ac\uc6a9\uc911\uc9c0\ub97c \uc120\ud0dd\ud558\uc2e4 \uacbd\uc6b0 \ub2e4\uc2dc \uc0ac\uc6a9\uc73c\ub85c \ubcc0\uacbd\uc774 \ubd88\uac00\ub2a5\ud569\ub2c8\ub2e4. +cop.ing.msg=\uc2b9\uc778\uc694\uccad \uc911\uc785\ub2c8\ub2e4. +cop.request.msg=\uac00\uc785\uc2e0\uccad\uc774 \uc815\uc0c1\uc801\uc73c\ub85c \uc694\uccad\ub418\uc5c8\uc2b5\ub2c8\ub2e4 +cop.password.msg=\ud328\uc2a4\uc6cc\ub4dc\ub97c \uc785\ub825\ud574 \uc8fc\uc2ed\uc2dc\uc624. +cop.password.not.same.msg=\ud328\uc2a4\uc6cc\ub4dc\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +cop.comment.wrterNm = \uc791\uc131\uc790 +cop.comment.commentCn = \ub0b4\uc6a9 +cop.comment.commentPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.satisfaction.wrterNm = \uc791\uc131\uc790 +cop.satisfaction.stsfdgCn = \ub0b4\uc6a9 +cop.satisfaction.stsfdg = \ub9cc\uc871\ub3c4 +cop.satisfaction.stsfdgPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.scrap.scrapNm = \uc2a4\ud06c\ub7a9\uba85 + +#UI USS Message# +uss.ion.noi.ntfcSj=\uc81c\ubaa9 +uss.ion.noi.ntfcCn=\ub0b4\uc6a9 +uss.ion.noi.ntfcDate=\uc54c\ub9bc\uc77c\uc790 +uss.ion.noi.ntfcTime=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcHH=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcMM=\uc54c\ub9bc\ubd84 +uss.ion.noi.bhNtfcIntrvl=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 +uss.ion.noi.bhNtfcIntrvl.msg=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. +uss.ion.noi.alertNtfcTime=\uc54c\ub9bc\uc77c\uc790 \ubc0f \uc2dc\uac04\uc774 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +#UI COP Message# +cop.sms.trnsmitTelno=\ubc1c\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.trnsmitCn=\uc804\uc1a1\ub0b4\uc6a9 +cop.sms.recptnTelno=\uc218\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.send=\uc804\uc1a1 +cop.sms.addRecptn=\ucd94\uac00 +cop.sms.recptnTelno.msg=\uc218\uc2e0\uc804\ud654\ubc88\ud638 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. + +#UI sym.log Message# +sym.log.histSeCode = \uc774\ub825\uad6c\ubd84 +sym.log.sysNm = \uc2dc\uc2a4\ud15c\uba85 +sym.log.histCn = \uc774\ub825\ub0b4\uc6a9 +sym.log.atchFile = \ucca8\ubd80\ud30c\uc77c +sym.log.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +sym.ems.receiver = \ubc1b\ub294\uc0ac\ub78c +sym.ems.title = \uc81c\ubaa9 +sym.ems.content = \ubc1c\uc2e0\ub0b4\uc6a9 + +#Vlidator Errors# +errors.prefix=
+errors.suffix=

+ +errors.required={0}\uc740(\ub294) \ud544\uc218 \uc785\ub825\uac12\uc785\ub2c8\ub2e4. +errors.minlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.maxlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.invalid={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uac12\uc785\ub2c8\ub2e4. +errors.minInteger={0}\uc740(\ub294) \uc720\ud6a8\ud55c \uac12\uc774 \uc544\ub2d9\ub2c8\ub2e4. 1 \uc774\uc0c1\uc758 \uac12\uc744 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.byte={0}\uc740(\ub294) byte\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.short={0}\uc740(\ub294) short\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.integer={0}\uc740(\ub294) \uc815\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.long={0}\uc740(\ub294) long \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.float={0}\uc740(\ub294) \uc2e4\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.double={0}\uc740(\ub294) double \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. + +errors.date={0}\uc740(\ub294) \ub0a0\uc9dc \uc720\ud615\uc774 \uc544\ub2d9\ub2c8\ub2e4. +errors.range={0}\uc740(\ub294) {1}\uacfc {2} \uc0ac\uc774\uc758 \uac12\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.creditcard={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc2e0\uc6a9\uce74\ub4dc \ubc88\ud638\uc785\ub2c8\ub2e4. +errors.email={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc774\uba54\uc77c \uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.ihidnum=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\uc785\ub2c8\ub2e4. +errors.korean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc785\ub825\ud558\uc154\uc57c \ud569\ub2c8\ub2e4. +errors.ip=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 IP\uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.password1={0}\uc740(\ub294) 8~20\uc790 \ub0b4\uc5d0\uc11c \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.password2={0}\uc740(\ub294) \ud55c\uae00,\ud2b9\uc218\ubb38\uc790,\ub744\uc5b4\uc4f0\uae30\ub294 \ud5c8\uc6a9\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +errors.password3={0}\uc740(\ub294) \uc21c\ucc28\uc801\uc778 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.password4={0}\uc740(\ub294) \ubc18\ubcf5\ub418\ub294 \ubb38\uc790\ub098 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. + +errors.notKorean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc0ac\uc6a9\ud558\uc2e4\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +error.security.runtime.error = error + +#Xss Errors# +errors.xss.checkerUser=\ud574\ub2f9 \uae30\ub2a5\uc5d0 \ub300\ud55c \uc0ac\uc6a9 \ubc0f \ucc98\ub9ac \uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. + +#File Upload / Download +errors.file.extension=\uc9c0\uc6d0\ub418\ub294 \ud30c\uc77c\uc720\ud615\uc774 \uc544\ub2d9\ub2c8\ub2e4. +errors.file.transfer=\ud30c\uc77c\uc804\uc1a1\uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4. +success.file.transfer=\ud30c\uc77c\uc804\uc1a1\uc774 \uc644\ub8cc\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +#Vlidator Errors- wordDicaryVO# +wordDicaryVO.wordNm=\uc6a9\uc5b4\uba85 +wordDicaryVO.engNm=\uc601\ubb38\uba85 +wordDicaryVO.wordDc=\uc6a9\uc5b4\uc124\uba85 +wordDicaryVO.synonm=\ub3d9\uc758\uc5b4 + +#Vlidator Errors- cnsltManageVO# +cnsltManageVO.cnsltSj=\uc0c1\ub2f4\uc81c\ubaa9 +cnsltManageVO.cnsltCn=\uc0c1\ub2f4\ub0b4\uc6a9 +cnsltManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +cnsltManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +cnsltManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +cnsltManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +cnsltManageVO.wrterNm=\uc791\uc131\uc790\uba85 +cnsltManageVO.managtCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- siteManageVO# +siteManageVO.siteNm=\uc0ac\uc774\ud2b8\uba85 +siteManageVO.siteUrl=\uc0ac\uc774\ud2b8 URL +siteManageVO.siteDc=\uc0ac\uc774\ud2b8\uc124\uba85 +siteManageVO.siteThemaClCode=\uc0ac\uc774\ud2b8\uc8fc\uc81c\ubd84\ub958 +siteManageVO.actvtyAt=\ud65c\uc131\uc5ec\ubd80 +siteManageVO.useAt=\uc0ac\uc6a9\uc5ec\ubd80 + +#Vlidator Errors- recomendSiteManageVO# +recomendSiteManageVO.recomendSiteNm=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uba85 +recomendSiteManageVO.recomendSiteUrl=\ucd94\ucc9c\uc0ac\uc774\ud2b8 URL +recomendSiteManageVO.recomendSiteDc=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc124\uba85 +recomendSiteManageVO.recomendResnCn=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc2b9\uc778\uc0ac\uc720 +recomendSiteManageVO.confmDe=\uc2b9\uc778\uc77c\uc790 + +#Vlidator Errors- hpcmManageVO# +hpcmManageVO.hpcmSeCode=\ub3c4\uc6c0\ub9d0\uad6c\ubd84 +hpcmManageVO.hpcmDf=\ub3c4\uc6c0\ub9d0\uc815\uc758 +hpcmManageVO.hpcmDc=\ub3c4\uc6c0\ub9d0\uc124\uba85 + +#Vlidator Errors- newsManageVO# +newsManageVO.newsSj=\ub274\uc2a4\uc81c\ubaa9 +newsManageVO.newsCn=\ub274\uc2a4\ub0b4\uc6a9 +newsManageVO.ntceDe=\uac8c\uc2dc\uc77c\uc790 + +#Vlidator Errors- faqManageVO# +faqManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +faqManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +faqManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- stplatManageVO# +stplatManageVO.useStplatNm=\uc774\uc6a9\uc57d\uad00\uba85 +stplatManageVO.useStplatCn=\uc774\uc6a9\uc57d\uad00\ub0b4\uc6a9 +stplatManageVO.infoProvdAgreCn=\uc815\ubcf4\uc81c\uacf5\ub3d9\uc758\ub0b4\uc6a9 + +#Vlidator Errors- cpyrhtPrtcPolicyVO# +cpyrhtPrtcPolicyVO.cpyrhtPrtcPolicyCn=\uc800\uc791\uad8c\ubcf4\ud638\uc815\ucc45\ub0b4\uc6a9 + +#Vlidator Errors- qnaManageVO# +qnaManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +qnaManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +qnaManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +qnaManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +qnaManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +qnaManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +qnaManageVO.wrterNm=\uc791\uc131\uc790\uba85 +qnaManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- ReprtStatsVO# +sts.reprtId = \ubcf4\uace0\uc11cID +sts.title = \ubcf4\uace0\uc11c\uba85 +sts.category = \ubcf4\uace0\uc11c\uc720\ud615 +sts.status = \uc9c4\ud589\uc0c1\ud0dc +sts.regDate = \ub4f1\ub85d\uc77c\uc2dc + +#Rest day messages# +sym.cal.restDay = \ud734\uc77c\uc77c\uc790 +sym.cal.restName = \ud734\uc77c\uba85 +sym.cal.restDetail = \ud734\uc77c\uc124\uba85 +sym.cal.restCategory = \ud734\uc77c\uad6c\ubd84 + +image.errorBg = \uc624\ub958\uc774\ubbf8\uc9c0 + + +#Custom message# +custom.fail.access=\uc815\uc0c1\uc801\uc778 \uc811\uadfc\uc774 \uc544\ub2d9\ub2c8\ub2e4. \ub85c\uadf8\uc778 \ud6c4 \uc774\uc6a9\ud558\uc138\uc694. +custom.fail.accessDenied=\uc694\uccad\uc5d0 \ub300\ud55c \uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. +custom.isNotExist.msg=\ucc98\ub9ac\uc5d0 \ud544\uc694\ud55c \uc790\ub8cc\uac00 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. diff --git a/src/test/resources/message/message-common_en.properties b/src/test/resources/message/message-common_en.properties new file mode 100644 index 0000000..5caa11d --- /dev/null +++ b/src/test/resources/message/message-common_en.properties @@ -0,0 +1,277 @@ +fail.common.msg=error ocurred! +fail.common.sql=sql error ocurred! error code: {0}, error msg: {1} +info.nodata.msg=no data found. + +#UI Common resource# +table.num=num. +table.regdate=reg.date +table.reger=registrant +table.select=select +title.html=egovframe common component +title.detail=Detail Inquiry +title.inquire=Inquire +title.update=Modify +title.create=Create +title.delete=Delete +title.save=Save +title.list=List +title.searchCondition=search condition +title.search=keyword +title.reply=reply +title.scrap=scrap +title.comment=comment +title.attachedFileSelect=attached file +title.attachedFileDelete=attached file delete +title.link=link +title.management=Management +title.all=All + +input.select=Select +input.cSelect=Select +input.input=input +input.button=button +input.selectAll.title=Checkbox select all +input.yes=Yes +input.no=No + +select.searchCondition=select condition select + +button.select=select +button.search=Search +button.use=use +button.notUsed=Not used +button.inquire=inquire +button.update=update +button.create=create +button.delete=delete +button.deleteDatabase=Wiping +button.close=close +button.save=save +button.list=list +button.reset=reset +button.passwordUpdate=password update +button.subscribe=subscribe +button.realname=realname confirm +button.moveToGpin=move to gpin confirm +button.moveToIhidnum=move to ihidnum confirm +button.agree=agree +button.disagree=disagree +button.possible=possible +button.impossible=impossible +button.qnaregist=Q&A create +button.cnsltregist=Counsel create +button.preview=preview +button.next=nexut +button.add=add it now +button.confirm=confirm +button.back =back +button.yes =yes +button.no =no +button.home =home +button.user =user support +button.cop =cooperation +button.wrkstart = work start +button.wrkend = work end +button.reply = reply +button.scrap = scrap +button.comment = comment +button.excel = excel +button.init=init +button.acknowledgment=acknowledgment +button.cancelAcknowledgment=cancel acknowledgment +button.bulkUpload=bulk upload +button.log = log +button.set = set +button.move = move + + +#UI Common Message# +common.noScriptTitle.msg=I can't use all functions in browser JavaScript is not supported. +common.searchCondition.msg=This layout is a lower information searches made up of information. + +common.summary.list={0} A list of the details of the output. +common.summary.regist={0} Registered by typing the possible contents of registers by clicking the button. +common.summary.update={0} Registered by typing the possible content of modification by clicking the button. +common.summary.inqire={0} Full inquiry details about the details of the output. + +common.save.msg=confirm save? +common.regist.msg=confirm regist? +common.delete.msg=confirm delete? +common.update.msg=confirm update? +common.nodata.msg=There is no data. please choose another seach keyword +common.required.msg=is required field +common.acknowledgement.msg=confirm acknowledgement? +common.acknowledgementcancel.msg=confirm acknowledgement cancel? +common.nocomment.msg=There is no comment. +common.noguest.msg=There is no guest notice. + +success.request.msg=you're request successfully done +success.common.select=successfully selected +success.common.insert=successfully inserted +success.common.update=successfully updated +success.common.delete=successfully deleted + +common.imposbl.fileupload = cannot upload files +common.isConfmDe.msg=Please check the approval date box +common.isExist.msg = already exist + +fail.common.insert = fail to insert. +fail.common.update = fail to update +fail.common.delete = fail to delete +fail.common.delete.upperMenuExist = fail to delete[upperMenuId foreign key error] +fail.common.select = fail to select +fail.common.login = login information is not correct +fail.common.loginIncorrect = login in more than {0} account will be locked! +fail.common.login.password = password information is not correct(password digit should be 8 to 20) +fail.common.idsearch = can not find id +fail.common.pwsearch = can not find password +fail.request.msg = Failed to handle the request +fail.common.login.ip = Login is refused because it is not a registered IP. + + +#UI User Message# +fail.user.passwordUpdate1=current password is not correct +fail.user.passwordUpdate2=password confirm is not correct +info.user.rlnmCnfirm=realname confirm ready +success.user.rlnmCnfirm=it is realname +fail.user.rlnmCnfirm=it is not realname +fail.user.connectFail=connection fail + +#UI Cop Message# +cop.extrlUser = External User +cop.intrlUser = Internal User +cop.private = private +cop.public = public + +cop.bbsNm = BBS Name +cop.bbsIntrcn = BBS Introduction +cop.bbsTyCode = BBS Type +cop.bbsAttrbCode = BBS Attribute +cop.replyPosblAt = Reply Possible Alternative +cop.fileAtchPosblAt = File Attach Possible Alternative +cop.posblAtchFileNumber = Possible Attach File Number +cop.tmplatId = Template Information +cop.guestList.subject = This article registered by Guest List +cop.nttSj = Notice Subject +cop.nttCn = Notice Contents +cop.ntceBgnde = Notice Start Date +cop.ntceEndde = Notice End Date +cop.ntcrNm = Noticer Name +cop.password = PassWord +cop.atchFile = Attach Files +cop.guestList = Guest List +cop.guestListCn = Guest List Contents +cop.noticeTerm = Notice term +cop.atchFileList = Attached File List +cop.cmmntyNm = Community Name +cop.cmmntyIntrcn = Community Introduction +cop.cmmntyMngr = Community Manager +cop.clbOprtr = Club Operator +cop.clbIntrcn = Club Introduction +cop.clbNm = Club Name +cop.tmplatNm = Template Name +cop.tmplatSeCode = Template Se Code +cop.tmplatCours = Template Cours +cop.useAt = Use Alternative +cop.ncrdNm = NameCard user name +cop.cmpnyNm = Company name +cop.deptNm = Department name +cop.ofcpsNm = OFCPS name +cop.clsfNm = Class Name +cop.emailAdres = E-mail +cop.telNo = Tel No. +cop.mbtlNum = Mobile +cop.adres = Address +cop.extrlUserAt = External User alternative +cop.publicAt = Public open alternative +cop.remark = Remark +cop.trgetNm = Company/Club Information +cop.preview = preview + +cop.withdraw.msg=confirm withdrawal memebership? +cop.reregist.msg=confirm re-registration? +cop.registmanager.msg=confirm registration of manager? +cop.use.msg=confirm use? +cop.unuse.msg=confirm stop using? +cop.delete.confirm.msg=If you choose to disable the re-use change is impossible. +cop.ing.msg=Approval is being requested. +cop.request.msg=Signup is normally requested. +cop.password.msg=Please enter your password. +cop.password.not.same.msg=Password do not match. + +cop.comment.wrterNm = Writer Name +cop.comment.commentCn = Comment +cop.comment.commentPassword = Password + +cop.satisfaction.wrterNm = Writer Name +cop.satisfaction.stsfdgCn = Satisfaction +cop.satisfaction.stsfdg = Satisfaction Degree +cop.satisfaction.stsfdgPassword = Password + +cop.scrap.scrapNm = Scrap Name + +#UI USS Message# +uss.ion.noi.ntfcSj=Subject +uss.ion.noi.ntfcCn=Contents +uss.ion.noi.ntfcDate=Notification Date +uss.ion.noi.ntfcTime=Notification Time +uss.ion.noi.ntfcHH=Notification Hour +uss.ion.noi.ntfcMM=Notification Minute +uss.ion.noi.bhNtfcIntrvl=Beforehand Interval +uss.ion.noi.bhNtfcIntrvl.msg=Beforehand Interval is required. +uss.ion.noi.alertNtfcTime=Date and time of notification is not valid. + +#UI COP Message# +cop.sms.trnsmitTelno=Sender +cop.sms.trnsmitCn=Contents +cop.sms.recptnTelno=Receiver(s) +cop.sms.send=Send +cop.sms.addRecptn=Add +cop.sms.recptnTelno.msg=The phone number of receiver is required. + +#UI sym.log Message# +sym.log.histSeCode = History Code +sym.log.sysNm = System Name +sym.log.histCn = History Contents +sym.log.atchFile = Attached File +sym.log.atchFileList = Attached File List +sym.ems.receiver = Receiver +sym.ems.title = Title +sym.ems.content = Content + +#Vlidator Errors# +errors.required={0} is required. +errors.minlength={0} can not be less than {1} characters. +errors.maxlength={0} can not be greater than {1} characters. +errors.invalid={0} is invalid. + +errors.byte={0} must be a byte. +errors.short={0} must be a short. +errors.integer={0} must be an integer. +errors.long={0} must be a long. +errors.float={0} must be a float. +errors.double={0} must be a double. + +errors.date={0} is not a date. +errors.range={0} is not in the range {1} through {2}. +errors.creditcard={0} is an invalid credit card number. +errors.email={0} is an invalid e-mail address. + +#Vlidator Errors- ReprtStatsVO# +sts.reprtId = Report ID +sts.title = Report Title +sts.category = Report Category +sts.status = Report Status +sts.regDate = Registration Date + +#Rest day messages# +sym.cal.restDay = Holiday Date +sym.cal.restName = Holiday Name +sym.cal.restDetail = Holiday Detail +sym.cal.restCategory = Holiday Category + + +#Custom message# +custom.fail.access=It's not a normal approach. Log in and use it. +custom.fail.accessDenied=You do not have permission to request. +custom.isNotExist.msg=Data required for processing does not exist. diff --git a/src/test/resources/message/message-common_ko.properties b/src/test/resources/message/message-common_ko.properties new file mode 100644 index 0000000..be0b930 --- /dev/null +++ b/src/test/resources/message/message-common_ko.properties @@ -0,0 +1,375 @@ +fail.common.msg=\uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! +fail.common.sql=sql \uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4! error code: {0}, error msg: {1} +info.nodata.msg=\ud574\ub2f9 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. + +#UI Common resource# +table.num=\ubc88\ud638 +table.regdate=\ub4f1\ub85d\uc77c +table.reger=\ub4f1\ub85d\uc790 +table.select=\uc120\ud0dd +title.html=egovframe common component +title.detail=\uc0c1\uc138\uc870\ud68c +title.inquire=\uc870\ud68c +title.update=\uc218\uc815 +title.create=\ub4f1\ub85d +title.delete=\uc0ad\uc81c +title.save=\uc800\uc7a5 +title.list=\ubaa9\ub85d +title.searchCondition=\uac80\uc0c9\uc870\uac74 +title.search=\uac80\uc0c9\uc5b4 +title.reply=\ub2f5\uae00 +title.scrap=\uc2a4\ud06c\ub7a9 +title.comment=\ub313\uae00 +title.attachedFileSelect=\ud30c\uc77c\uc120\ud0dd +title.attachedFileDelete=\ud30c\uc77c\uc0ad\uc81c +title.link=\ub9c1\ud06c +title.management=\uad00\ub9ac +title.all=\uc804\uccb4 + +input.select=\uc120\ud0dd\ud558\uc138\uc694 +input.cSelect=\uc120\ud0dd +input.input=\uc785\ub825 +input.button=\ubc84\ud2bc +input.selectAll.title=\uc804\uccb4\uc120\ud0dd\uccb4\ud06c\ubc15\uc2a4 +input.yes=\uc608 +input.no=\uc544\ub2c8\uc624 + +select.searchCondition=\uc870\ud68c\uc870\uac74 \uc120\ud0dd + +button.select=\uc120\ud0dd +button.search=\uac80\uc0c9 +button.use=\uc0ac\uc6a9 +button.notUsed=\uc0ac\uc6a9\uc911\uc9c0 +button.inquire=\uc870\ud68c +button.update=\uc218\uc815 +button.create=\ub4f1\ub85d +button.delete=\uc0ad\uc81c +button.deleteDatabase=\uc644\uc804\uc0ad\uc81c +button.close=\ub2eb\uae30 +button.save=\uc800\uc7a5 +button.list=\ubaa9\ub85d +button.reset=\ucde8\uc18c +button.passwordUpdate=\uc554\ud638\ubcc0\uacbd +button.subscribe=\uac00\uc785\uc2e0\uccad +button.realname=\uc2e4\uba85\ud655\uc778 +button.moveToGpin=GPIN\uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.moveToIhidnum=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638 \uc2e4\uba85\ud655\uc778\uc73c\ub85c \uc774\ub3d9 +button.agree=\ub3d9\uc758 +button.disagree=\ube44\ub3d9\uc758 +button.possible=\uac00\ub2a5 +button.impossible=\ubd88\uac00\ub2a5 +button.qnaregist=Q&A\ub4f1\ub85d +button.cnsltregist=\uc0c1\ub2f4\ub4f1\ub85d +button.preview=\ubbf8\ub9ac\ubcf4\uae30 +button.next=\ub2e4\uc74c +button.add=\ubc14\ub85c\ucd94\uac00 +button.confirm=\ud655\uc778 +button.back = \ub4a4\ub85c +button.yes = \uc608 +button.no = \uc544\ub2c8\uc624 +button.home = \ud648 +button.user = \uc0ac\uc6a9\uc790\uc9c0\uc6d0 +button.cop = \ud611\uc5c5 +button.wrkstart = \ucd9c\uadfc +button.wrkend = \ud1f4\uadfc +button.reply = \ub2f5\uae00 +button.scrap = \uc2a4\ud06c\ub7a9 +button.comment = \ub313\uae00 +button.excel = \uc5d1\uc140 +button.init=\ucd08\uae30\ud654 +button.acknowledgment=\uc2b9\uc778 +button.cancelAcknowledgment=\uc2b9\uc778\ucde8\uc18c +button.bulkUpload=\uc77c\uad04\ub4f1\ub85d +button.log = \ub85c\uadf8 +button.set = \uc124\uc815 +button.move = \uc774\ub3d9 + + +#UI Common Message# +common.noScriptTitle.msg=\uc790\ubc14\uc2a4\ud06c\ub9bd\ud2b8\ub97c \uc9c0\uc6d0\ud558\uc9c0 \uc54a\ub294 \ube0c\ub77c\uc6b0\uc800\uc5d0\uc11c\ub294 \uc77c\ubd80 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uc2e4 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +common.searchCondition.msg=\uc774 \ub808\uc774\uc544\uc6c3\uc740 \ud558\ub2e8 \uc815\ubcf4\ub97c \ub300\ud55c \uac80\uc0c9 \uc815\ubcf4\ub85c \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4. + +common.summary.list={0}\uc758 \ub0b4\uc5ed\uc5d0 \ub300\ud55c \ubaa9\ub85d\uc744 \ucd9c\ub825\ud569\ub2c8\ub2e4. +common.summary.regist={0}\uc758 \ub4f1\ub85d \uac00\ub2a5\ud55c \ub0b4\uc6a9\uc744 \uc785\ub825\ud558\uc5ec \ub4f1\ub85d \ubc84\ud2bc\uc744 \ud074\ub9ad\ud558\uc5ec \ub4f1\ub85d\ud55c\ub2e4. +common.summary.update={0}\uc758 \ub4f1\ub85d \uac00\ub2a5\ud55c \ub0b4\uc6a9\uc744 \uc785\ub825\ud558\uc5ec \ub4f1\ub85d \ubc84\ud2bc\uc744 \ud074\ub9ad\ud558\uc5ec \uc218\uc815\ud55c\ub2e4. +common.summary.inqire={0}\uc758 \ub0b4\uc5ed\uc5d0 \ub300\ud55c \uc0c1\uc138\uc870\ud68c \ub0b4\uc5ed\uc744 \ucd9c\ub825\ud569\ub2c8\ub2e4. + +common.save.msg=\uc800\uc7a5\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.regist.msg=\ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.delete.msg=\uc0ad\uc81c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.update.msg=\uc218\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.nodata.msg=\uc790\ub8cc\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\ub978 \uac80\uc0c9\uc870\uac74\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694 +common.required.msg=(\uc740)\ub294 \ud544\uc218\uc785\ub825\ud56d\ubaa9\uc785\ub2c8\ub2e4. +common.acknowledgement.msg=\uc2b9\uc778\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.acknowledgementcancel.msg=\uc2b9\uc778\ucde8\uc18c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +common.nocomment.msg=\ub313\uae00\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. +common.noguest.msg=\uc791\uc131\ub41c \ubc29\uba85\ub85d\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. + +success.request.msg = \uc694\uccad\ucc98\ub9ac\uac00 \uc131\uacf5\uc801\uc73c\ub85c \uc218\ud589\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.select=\uc815\uc0c1\uc801\uc73c\ub85c \uc870\ud68c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.insert=\uc815\uc0c1\uc801\uc73c\ub85c \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.update=\uc815\uc0c1\uc801\uc73c\ub85c \uc218\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4. +success.common.delete=\uc815\uc0c1\uc801\uc73c\ub85c \uc0ad\uc81c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +common.imposbl.fileupload = \ub354 \uc774\uc0c1 \ud30c\uc77c\uc744 \ucca8\ubd80\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +common.isConfmDe.msg=\uc2b9\uc778\uc77c\uc790\ub97c \ud655\uc778 \ubc14\ub78d\ub2c8\ub2e4. +common.isExist.msg = \uc774\ubbf8 \uc874\uc7ac\ud558\uac70\ub098 \uacfc\uac70\uc5d0 \ub4f1\ub85d\uc774 \ub418\uc5c8\ub358 \uc0c1\ud0dc\uc785\ub2c8\ub2e4. + +fail.common.insert = \uc0dd\uc131\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.update = \uc218\uc815\uc774 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete = \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.delete.upperMenuExist = \ucc38\uc870\ub418\ub294 \uba54\ub274\uac00 \uc788\uc5b4 \uc0ad\uc81c\uac00 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.select = \uc870\ud68c\uc5d0 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.login = \ub85c\uadf8\uc778 \uc815\ubcf4\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.common.loginIncorrect = {0}\ud68c \uc774\uc0c1 \ub85c\uadf8\uc778 \uc811\uc18d\uc774 \uc2dc\ub3c4 \ub418\uc5b4 \uacc4\uc815\uc774 \uc7a0\uaca8\uc2b5\ub2c8\ub2e4! +fail.common.login.password = \ud328\uc2a4\uc6cc\ub4dc \uc790\ub9ac \uc218\uac00 \uc77c\uce58 \ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.(8\uc790\ub9ac \uc774\uc0c1 20\uc790\ub9ac \uc774\ud558) +fail.common.idsearch = \uc544\uc774\ub514\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.common.pwsearch = \ube44\ubc00\ubc88\ud638\ub97c \ucc3e\uc744\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +fail.request.msg = \uc694\uccad\ucc98\ub9ac\ub97c \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. +fail.common.login.ip = \ub4f1\ub85d\ub41c IP\uac00 \uc544\ub2c8\ubbc0\ub85c \ub85c\uadf8\uc778\uc774 \uac70\ubd80\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +#UI User Message# +fail.user.passwordUpdate1=\ud604\uc7ac \ube44\ubc00\ubc88\ud638\uac00 \ub9de\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.passwordUpdate2=\ube44\ubc00\ubc88\ud638\uc640 \ube44\ubc00\ubc88\ud638 \ud655\uc778\uc774 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +info.user.rlnmCnfirm=\uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmCnfirm=\ud589\uc815\uc548\uc804\ubd80\uc758 \uc8fc\ubbfc\ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +fail.user.connectFail=\uc2dc\uc2a4\ud15c \uc7a5\uc560\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.(\uc778\uc99d\uc11c\ubc84 \uc5f0\uacb0 \uc2e4\ud328) +info.user.rlnmPinCnfirm=\uacf5\uacf5 \uc544\uc774\ud540 \uc544\uc774\ub514\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2e4\uba85\ud655\uc778\uc744 \ud558\uc2ed\uc2dc\uc624. +success.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud569\ub2c8\ub2e4. +fail.user.rlnmPinCnfirm=\uacf5\uacf5\uc544\uc774\ud540\uc758 \ub4f1\ub85d\uc790\ub8cc\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + + +#UI Cop Message# +cop.extrlUser = \uc678\ubd80\uc0ac\uc6a9\uc790 +cop.intrlUser = \ub0b4\ubd80\uc0ac\uc6a9\uc790 +cop.private = \ube44\uacf5\uac1c +cop.public = \uacf5\uac1c + +cop.adbkNm = \uc8fc\uc18c\ub85d\uba85 +cop.othbcScope = \uacf5\uac1c\ubc94\uc704 +cop.company = \ud68c\uc0ac +cop.part = \ubd80\uc11c +cop.man = \uac1c\uc778 +cop.adbkUser = \uad6c\uc131\uc6d0 +cop.bbsNm = \uac8c\uc2dc\ud310\uba85 +cop.bbsIntrcn = \uac8c\uc2dc\ud310\uc18c\uac1c +cop.bbsTyCode = \uac8c\uc2dc\ud310 \uc720\ud615 +cop.bbsAttrbCode = \uac8c\uc2dc\ud310 \uc18d\uc131 +cop.replyPosblAt = \ub2f5\uc7a5\uac00\ub2a5\uc5ec\ubd80 +cop.fileAtchPosblAt = \ud30c\uc77c\ucca8\ubd80\uac00\ub2a5\uc5ec\ubd80 +cop.posblAtchFileNumber = \ucca8\ubd80\uac00\ub2a5\ud30c\uc77c \uc22b\uc790 +cop.tmplatId = \ud15c\ud50c\ub9bf \uc815\ubcf4 +cop.guestList.subject = \ubc29\uba85\ub85d \uac8c\uc2dc\uae00\uc785\ub2c8\ub2e4. +cop.nttSj = \uc81c\ubaa9 +cop.nttCn = \uae00\ub0b4\uc6a9 +cop.ntceBgnde = \uac8c\uc2dc\uc2dc\uc791\uc77c +cop.ntceEndde = \uac8c\uc2dc\uc885\ub8cc\uc77c +cop.ntcrNm = \uc791\uc131\uc790 +cop.password = \ud328\uc2a4\uc6cc\ub4dc +cop.atchFile = \ud30c\uc77c\ucca8\ubd80 +cop.guestList = \ubc29\uba85\ub85d +cop.guestListCn = \ubc29\uba85\ub85d \ub0b4\uc6a9 +cop.noticeTerm = \uac8c\uc2dc\uae30\uac04 +cop.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +cop.cmmntyNm = \ucee4\ubba4\ub2c8\ud2f0\uba85 +cop.cmmntyIntrcn = \ucee4\ubba4\ub2c8\ud2f0 \uc18c\uac1c +cop.cmmntyMngr = \ucee4\ubba4\ub2c8\ud2f0 \uad00\ub9ac\uc790 +cop.clbOprtr = \ub3d9\ud638\ud68c \uc6b4\uc601\uc790 +cop.clbIntrcn = \ub3d9\ud638\ud68c \uc18c\uac1c +cop.clbNm = \ub3d9\ud638\ud68c \uba85 +cop.tmplatNm = \ud15c\ud50c\ub9bf\uba85 +cop.tmplatSeCode = \ud15c\ud50c\ub9bf \uad6c\ubd84 +cop.tmplatCours = \ud15c\ud50c\ub9bf\uacbd\ub85c +cop.useAt = \uc0ac\uc6a9\uc5ec\ubd80 +cop.ncrdNm = \uc774\ub984 +cop.cmpnyNm = \ud68c\uc0ac\uba85 +cop.deptNm = \ubd80\uc11c\uba85 +cop.ofcpsNm = \uc9c1\uc704 +cop.clsfNm = \uc9c1\uae09 +cop.emailAdres = \uc774\uba54\uc77c\uc8fc\uc18c +cop.telNo = \uc804\ud654\ubc88\ud638 +cop.mbtlNum = \ud734\ub300\ud3f0\ubc88\ud638 +cop.adres = \uc8fc\uc18c +cop.extrlUserAt = \uc678\ubd80\uc0ac\uc6a9\uc790\uc5ec\ubd80 +cop.publicAt = \uacf5\uac1c\uc5ec\ubd80 +cop.remark = \ube44\uace0 +cop.trgetNm = \ucee4\ubba4\ub2c8\ud2f0/\ub3d9\ud638\ud68c \uc815\ubcf4 +cop.preview = \ubbf8\ub9ac\ubcf4\uae30 + +cop.withdraw.msg=\ud0c8\ud1f4\ucc98\ub9ac \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.reregist.msg=\uc7ac\uac00\uc785 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.registmanager.msg=\uc6b4\uc601\uc9c4\uc73c\ub85c \ub4f1\ub85d\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.use.msg=\uc0ac\uc6a9 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.unuse.msg=\uc0ac\uc6a9\uc911\uc9c0 \ucc98\ub9ac\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? +cop.delete.confirm.msg=\uc0ac\uc6a9\uc911\uc9c0\ub97c \uc120\ud0dd\ud558\uc2e4 \uacbd\uc6b0 \ub2e4\uc2dc \uc0ac\uc6a9\uc73c\ub85c \ubcc0\uacbd\uc774 \ubd88\uac00\ub2a5\ud569\ub2c8\ub2e4. +cop.ing.msg=\uc2b9\uc778\uc694\uccad \uc911\uc785\ub2c8\ub2e4. +cop.request.msg=\uac00\uc785\uc2e0\uccad\uc774 \uc815\uc0c1\uc801\uc73c\ub85c \uc694\uccad\ub418\uc5c8\uc2b5\ub2c8\ub2e4 +cop.password.msg=\ud328\uc2a4\uc6cc\ub4dc\ub97c \uc785\ub825\ud574 \uc8fc\uc2ed\uc2dc\uc624. +cop.password.not.same.msg=\ud328\uc2a4\uc6cc\ub4dc\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +cop.comment.wrterNm = \uc791\uc131\uc790 +cop.comment.commentCn = \ub0b4\uc6a9 +cop.comment.commentPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.satisfaction.wrterNm = \uc791\uc131\uc790 +cop.satisfaction.stsfdgCn = \ub0b4\uc6a9 +cop.satisfaction.stsfdg = \ub9cc\uc871\ub3c4 +cop.satisfaction.stsfdgPassword = \ud328\uc2a4\uc6cc\ub4dc + +cop.scrap.scrapNm = \uc2a4\ud06c\ub7a9\uba85 + +#UI USS Message# +uss.ion.noi.ntfcSj=\uc81c\ubaa9 +uss.ion.noi.ntfcCn=\ub0b4\uc6a9 +uss.ion.noi.ntfcDate=\uc54c\ub9bc\uc77c\uc790 +uss.ion.noi.ntfcTime=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcHH=\uc54c\ub9bc\uc2dc\uac04 +uss.ion.noi.ntfcMM=\uc54c\ub9bc\ubd84 +uss.ion.noi.bhNtfcIntrvl=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 +uss.ion.noi.bhNtfcIntrvl.msg=\uc0ac\uc804\uc54c\ub9bc\uac04\uaca9 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. +uss.ion.noi.alertNtfcTime=\uc54c\ub9bc\uc77c\uc790 \ubc0f \uc2dc\uac04\uc774 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. + +#UI COP Message# +cop.sms.trnsmitTelno=\ubc1c\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.trnsmitCn=\uc804\uc1a1\ub0b4\uc6a9 +cop.sms.recptnTelno=\uc218\uc2e0\uc804\ud654\ubc88\ud638 +cop.sms.send=\uc804\uc1a1 +cop.sms.addRecptn=\ucd94\uac00 +cop.sms.recptnTelno.msg=\uc218\uc2e0\uc804\ud654\ubc88\ud638 \uc9c0\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. + +#UI sym.log Message# +sym.log.histSeCode = \uc774\ub825\uad6c\ubd84 +sym.log.sysNm = \uc2dc\uc2a4\ud15c\uba85 +sym.log.histCn = \uc774\ub825\ub0b4\uc6a9 +sym.log.atchFile = \ucca8\ubd80\ud30c\uc77c +sym.log.atchFileList = \ucca8\ubd80\ud30c\uc77c\ubaa9\ub85d +sym.ems.receiver = \ubc1b\ub294\uc0ac\ub78c +sym.ems.title = \uc81c\ubaa9 +sym.ems.content = \ubc1c\uc2e0\ub0b4\uc6a9 + +#Vlidator Errors# +errors.prefix=
+errors.suffix=

+ +errors.required={0}\uc740(\ub294) \ud544\uc218 \uc785\ub825\uac12\uc785\ub2c8\ub2e4. +errors.minlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.maxlength={0}\uc740(\ub294) {1}\uc790 \uc774\uc0c1 \uc785\ub825\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.invalid={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uac12\uc785\ub2c8\ub2e4. +errors.minInteger={0}\uc740(\ub294) \uc720\ud6a8\ud55c \uac12\uc774 \uc544\ub2d9\ub2c8\ub2e4. 1 \uc774\uc0c1\uc758 \uac12\uc744 \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.byte={0}\uc740(\ub294) byte\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.short={0}\uc740(\ub294) short\ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.integer={0}\uc740(\ub294) \uc815\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.long={0}\uc740(\ub294) long \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.float={0}\uc740(\ub294) \uc2e4\uc218 \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.double={0}\uc740(\ub294) double \ud0c0\uc785\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. + +errors.date={0}\uc740(\ub294) \ub0a0\uc9dc \uc720\ud615\uc774 \uc544\ub2d9\ub2c8\ub2e4. +errors.range={0}\uc740(\ub294) {1}\uacfc {2} \uc0ac\uc774\uc758 \uac12\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +errors.creditcard={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc2e0\uc6a9\uce74\ub4dc \ubc88\ud638\uc785\ub2c8\ub2e4. +errors.email={0}\uc740(\ub294) \uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc774\uba54\uc77c \uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.ihidnum=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \uc8fc\ubbfc\ub4f1\ub85d\ubc88\ud638\uc785\ub2c8\ub2e4. +errors.korean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc785\ub825\ud558\uc154\uc57c \ud569\ub2c8\ub2e4. +errors.ip=\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 IP\uc8fc\uc18c\uc785\ub2c8\ub2e4. + +errors.password1={0}\uc740(\ub294) 8~20\uc790 \ub0b4\uc5d0\uc11c \uc785\ub825\ud574\uc57c \ud569\ub2c8\ub2e4. +errors.password2={0}\uc740(\ub294) \ud55c\uae00,\ud2b9\uc218\ubb38\uc790,\ub744\uc5b4\uc4f0\uae30\ub294 \ud5c8\uc6a9\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. +errors.password3={0}\uc740(\ub294) \uc21c\ucc28\uc801\uc778 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +errors.password4={0}\uc740(\ub294) \ubc18\ubcf5\ub418\ub294 \ubb38\uc790\ub098 \uc22b\uc790\ub97c 4\uac1c\uc774\uc0c1 \uc5f0\uc18d\ud574\uc11c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. + +errors.notKorean={0}\uc740(\ub294) \ud55c\uae00\uc744 \uc0ac\uc6a9\ud558\uc2e4\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +error.security.runtime.error = error + +#Xss Errors# +errors.xss.checkerUser=\ud574\ub2f9 \uae30\ub2a5\uc5d0 \ub300\ud55c \uc0ac\uc6a9 \ubc0f \ucc98\ub9ac \uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. + +#File Upload / Download +errors.file.extension=\uc9c0\uc6d0\ub418\ub294 \ud30c\uc77c\uc720\ud615\uc774 \uc544\ub2d9\ub2c8\ub2e4. +errors.file.transfer=\ud30c\uc77c\uc804\uc1a1\uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4. +success.file.transfer=\ud30c\uc77c\uc804\uc1a1\uc774 \uc644\ub8cc\ub418\uc5c8\uc2b5\ub2c8\ub2e4. + +#Vlidator Errors- wordDicaryVO# +wordDicaryVO.wordNm=\uc6a9\uc5b4\uba85 +wordDicaryVO.engNm=\uc601\ubb38\uba85 +wordDicaryVO.wordDc=\uc6a9\uc5b4\uc124\uba85 +wordDicaryVO.synonm=\ub3d9\uc758\uc5b4 + +#Vlidator Errors- cnsltManageVO# +cnsltManageVO.cnsltSj=\uc0c1\ub2f4\uc81c\ubaa9 +cnsltManageVO.cnsltCn=\uc0c1\ub2f4\ub0b4\uc6a9 +cnsltManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +cnsltManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +cnsltManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +cnsltManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +cnsltManageVO.wrterNm=\uc791\uc131\uc790\uba85 +cnsltManageVO.managtCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- siteManageVO# +siteManageVO.siteNm=\uc0ac\uc774\ud2b8\uba85 +siteManageVO.siteUrl=\uc0ac\uc774\ud2b8 URL +siteManageVO.siteDc=\uc0ac\uc774\ud2b8\uc124\uba85 +siteManageVO.siteThemaClCode=\uc0ac\uc774\ud2b8\uc8fc\uc81c\ubd84\ub958 +siteManageVO.actvtyAt=\ud65c\uc131\uc5ec\ubd80 +siteManageVO.useAt=\uc0ac\uc6a9\uc5ec\ubd80 + +#Vlidator Errors- recomendSiteManageVO# +recomendSiteManageVO.recomendSiteNm=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uba85 +recomendSiteManageVO.recomendSiteUrl=\ucd94\ucc9c\uc0ac\uc774\ud2b8 URL +recomendSiteManageVO.recomendSiteDc=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc124\uba85 +recomendSiteManageVO.recomendResnCn=\ucd94\ucc9c\uc0ac\uc774\ud2b8\uc2b9\uc778\uc0ac\uc720 +recomendSiteManageVO.confmDe=\uc2b9\uc778\uc77c\uc790 + +#Vlidator Errors- hpcmManageVO# +hpcmManageVO.hpcmSeCode=\ub3c4\uc6c0\ub9d0\uad6c\ubd84 +hpcmManageVO.hpcmDf=\ub3c4\uc6c0\ub9d0\uc815\uc758 +hpcmManageVO.hpcmDc=\ub3c4\uc6c0\ub9d0\uc124\uba85 + +#Vlidator Errors- newsManageVO# +newsManageVO.newsSj=\ub274\uc2a4\uc81c\ubaa9 +newsManageVO.newsCn=\ub274\uc2a4\ub0b4\uc6a9 +newsManageVO.ntceDe=\uac8c\uc2dc\uc77c\uc790 + +#Vlidator Errors- faqManageVO# +faqManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +faqManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +faqManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- stplatManageVO# +stplatManageVO.useStplatNm=\uc774\uc6a9\uc57d\uad00\uba85 +stplatManageVO.useStplatCn=\uc774\uc6a9\uc57d\uad00\ub0b4\uc6a9 +stplatManageVO.infoProvdAgreCn=\uc815\ubcf4\uc81c\uacf5\ub3d9\uc758\ub0b4\uc6a9 + +#Vlidator Errors- cpyrhtPrtcPolicyVO# +cpyrhtPrtcPolicyVO.cpyrhtPrtcPolicyCn=\uc800\uc791\uad8c\ubcf4\ud638\uc815\ucc45\ub0b4\uc6a9 + +#Vlidator Errors- qnaManageVO# +qnaManageVO.qestnSj=\uc9c8\ubb38\uc81c\ubaa9 +qnaManageVO.qestnCn=\uc9c8\ubb38\ub0b4\uc6a9 +qnaManageVO.writngPassword=\uc791\uc131\ube44\ubc00\ubc88\ud638 +qnaManageVO.areaNo=\uc9c0\uc5ed\ubc88\ud638 +qnaManageVO.middleTelno=\uc911\uac04\uc804\ud654\ubc88\ud638 +qnaManageVO.endTelno=\ub05d\uc804\ud654\ubc88\ud638 +qnaManageVO.wrterNm=\uc791\uc131\uc790\uba85 +qnaManageVO.answerCn=\ub2f5\ubcc0\ub0b4\uc6a9 + +#Vlidator Errors- ReprtStatsVO# +sts.reprtId = \ubcf4\uace0\uc11cID +sts.title = \ubcf4\uace0\uc11c\uba85 +sts.category = \ubcf4\uace0\uc11c\uc720\ud615 +sts.status = \uc9c4\ud589\uc0c1\ud0dc +sts.regDate = \ub4f1\ub85d\uc77c\uc2dc + +#Rest day messages# +sym.cal.restDay = \ud734\uc77c\uc77c\uc790 +sym.cal.restName = \ud734\uc77c\uba85 +sym.cal.restDetail = \ud734\uc77c\uc124\uba85 +sym.cal.restCategory = \ud734\uc77c\uad6c\ubd84 + +image.errorBg = \uc624\ub958\uc774\ubbf8\uc9c0 + + +#Custom message# +custom.fail.access=\uc815\uc0c1\uc801\uc778 \uc811\uadfc\uc774 \uc544\ub2d9\ub2c8\ub2e4. \ub85c\uadf8\uc778 \ud6c4 \uc774\uc6a9\ud558\uc138\uc694. +custom.fail.accessDenied=\uc694\uccad\uc5d0 \ub300\ud55c \uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. +custom.isNotExist.msg=\ucc98\ub9ac\uc5d0 \ud544\uc694\ud55c \uc790\ub8cc\uac00 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. diff --git a/src/test/resources/spring/context-beans.xml b/src/test/resources/spring/context-beans.xml new file mode 100644 index 0000000..d5b4ec7 --- /dev/null +++ b/src/test/resources/spring/context-beans.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/src/test/resources/spring/context-common.xml b/src/test/resources/spring/context-common.xml new file mode 100644 index 0000000..5a3e076 --- /dev/null +++ b/src/test/resources/spring/context-common.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + classpath:message/message-common + classpath:message/authentication-message + classpath:org/egovframe/rte/fdl/property/messages/properties + + + + + 60 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/spring/context-datasource.xml b/src/test/resources/spring/context-datasource.xml new file mode 100644 index 0000000..dc3465a --- /dev/null +++ b/src/test/resources/spring/context-datasource.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/spring/context-security.xml b/src/test/resources/spring/context-security.xml new file mode 100644 index 0000000..9ebff05 --- /dev/null +++ b/src/test/resources/spring/context-security.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/sql/mapper/base/actiongroup-mapper.xml b/src/test/resources/sql/mapper/base/actiongroup-mapper.xml new file mode 100644 index 0000000..60646cf --- /dev/null +++ b/src/test/resources/sql/mapper/base/actiongroup-mapper.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + +SELECT A.* + FROM TBL_ACTION_GRP A + +AND GRP_ID IN (#{groupID}) +AND ${by} LIKE CONCAT('%', #{term}, '%') + + + + + + + +/* 기능그룹 등록(actionGroupMapper.insertGroup) */ +INSERT INTO TBL_ACTION_GRP ( + GRP_ID + , GRP_NM + , DSCRP + , INS_DT +) VALUES ( + #{id} + , #{name} + , #{description} + , CURRENT_TIMESTAMP() +) + +/* 기능그룹 수정(actionGroupMapper.updateGroup) */ +UPDATE TBL_ACTION_GRP SET + GRP_NM = #{name} + , DSCRP = #{description} + WHERE GRP_ID = #{id} + +/* 기능그룹 삭제(actionGroupMapper.removeGroups) */ +DELETE FROM TBL_ACTION_GRP + WHERE GRP_ID IN (#{groupID}) + + + +/* 그룹별 기능 추가(actionGroupMapper.addActions) */ +INSERT INTO TBL_GRP_ACTION (GRP_ID, ACTION, INS_DT) +SELECT GRP_ID, ACTION, CURRENT_TIMESTAMP() + FROM ( + SELECT #{groupID} GRP_ID, #{action} ACTION FROM DUAL + ) A + WHERE NOT EXISTS ( + SELECT GRP_ID, ACTION + FROM TBL_GRP_ACTION B + WHERE B.GRP_ID = A.GRP_ID + AND B.ACTION = A.ACTION + ) + +/* 그룹별 기능 삭제(actionGroupMapper.removeActions) */ +DELETE FROM TBL_GRP_ACTION + +AND GRP_ID IN (#{groupID}) +AND ACTION IN (#{action}) + + + + \ No newline at end of file diff --git a/src/test/resources/sql/mapper/base/authentication.xml b/src/test/resources/sql/mapper/base/authentication.xml new file mode 100644 index 0000000..115ee22 --- /dev/null +++ b/src/test/resources/sql/mapper/base/authentication.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + +/* 로그인 정책 등록(authentication.insertPolicy) */ +INSERT INTO TBL_LOGIN_POLICY ( + USER_ID + , IP_ADRS + , DPLCT_YN + , LIMIT_YN + , FRST_REGISTER_ID + , FRST_REGIST_PNTTM + , UPD_ID + , UPD_DT +) VALUES ( + #{policy.userID} + , #{policy.ipAddress} + , #{policy.duplicateYN} + , #{policy.limitYN} + , #{currentUser.id} + , CURRENT_TIMESTAMP() + , #{currentUser.id} + , CURRENT_TIMESTAMP() +) + +/* 로그인 정책 수정(authentication.updatePolicy) */ +UPDATE TBL_LOGIN_POLICY SET + IP_ADRS = #{policy.ipAddress} + , DPLCT_YN = #{policy.duplicateYN} + , LIMIT_YN = #{policy.limitYN} + , UPD_ID = #{currentUser.id} + , UPD_DT = CURRENT_TIMESTAMP() +WHERE USER_ID = #{policy.userID} + +/* 로그인 정책 삭제(authentication.removePolicy) */ +DELETE FROM TBL_LOGIN_POLICY + WHERE USER_ID IN (#{userID}) + + \ No newline at end of file diff --git a/src/test/resources/sql/mapper/base/authority-mapper.xml b/src/test/resources/sql/mapper/base/authority-mapper.xml new file mode 100644 index 0000000..945a075 --- /dev/null +++ b/src/test/resources/sql/mapper/base/authority-mapper.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + +SELECT * FROM ( + SELECT 0 AUTH_TYPE, 'ROLE_ADMIN' AUTH_ID, '시스템 관리자' AUTH_NM, '시스템 관리자' DSCRP, 'all' INF_SCP, 'all' USER_INF_SCP, CURRENT_TIMESTAMP() NOW UNION + SELECT 1 AUTH_TYPE, 'ROLE_ANONYMOUS' AUTH_ID, '익명 사용자' AUTH_NM, '모든 사용자' DSCRP, 'none' INF_SCP, 'none' USER_INF_SCP, CURRENT_TIMESTAMP() NOW UNION + SELECT 1 AUTH_TYPE, 'ROLE_USER' AUTH_ID, '시스템 사용자' AUTH_NM, '로그인한 사용자' DSCRP, 'self' INF_SCP, 'self' USER_INF_SCP, CURRENT_TIMESTAMP() NOW UNION + SELECT 2 AUTH_TYPE, AUTH_ID, AUTH_NM, DSCRP, INF_SCP, USER_INF_SCP, INS_DT + FROM TBL_AUTHORITY +) A + +AND AUTH_ID IN (#{authID}) +AND ${by} LIKE CONCAT('%', #{term}, '%') + + + + + + + + +/* 권한 등록(authorityMapper.insertAuthority) */ +INSERT INTO TBL_AUTHORITY ( + AUTH_ID + , AUTH_NM + , DSCRP + , INF_SCP + , USER_INF_SCP + , INS_DT +) VALUES ( + #{id} + , #{name} + , #{description} + , #{infoScope} + , #{userInfoScope} + , CURRENT_TIMESTAMP() +) + +/* 권한 수정(authorityMapper.updateAuthority) */ +UPDATE TBL_AUTHORITY SET + AUTH_NM = #{name} + , DSCRP = #{description} + , INF_SCP = #{infoScope} + , USER_INF_SCP = #{userInfoScope} + WHERE AUTH_ID = #{id} + +/* 권한 삭제(authorityMapper.removeAuthorities) */ +DELETE FROM TBL_AUTHORITY + WHERE AUTH_ID IN (#{authID}) + + + +/* 권한-기능그룹 추가(authorityMapper.addActionGroups) */ +INSERT INTO TBL_AUTH_ACTION (AUTH_ID, GRP_ID, INS_DT) +SELECT AUTH_ID, GRP_ID, CURRENT_TIMESTAMP() + FROM ( + SELECT #{authID} AUTH_ID, #{groupID} GRP_ID FROM DUAL + ) A + WHERE NOT EXISTS ( + SELECT AUTH_ID, GRP_ID + FROM TBL_AUTH_ACTION B + WHERE B.AUTH_ID = A.AUTH_ID + AND B.GRP_ID = A.GRP_ID + ) + +/* 권한-기능그룹 삭제(authorityMapper.removeActionGroups) */ +DELETE FROM TBL_AUTH_ACTION + +AND AUTH_ID IN (#{authID}) +AND GRP_ID IN (#{groupID}) + + + + + + + +SELECT * + FROM TBL_AUTH_USER +WHERE AUTH_ID IN (#{authID}) +WHERE USER_ID IN (#{userID}) + + + + + + + +/* 권한-사용자 추가(authorityMapper.addUsers) */ +INSERT INTO TBL_AUTH_USER (AUTH_ID, USER_ID, INS_DT) +SELECT AUTH_ID, USER_ID, CURRENT_TIMESTAMP() + FROM ( + SELECT #{authID} AUTH_ID, #{userID} USER_ID FROM DUAL + ) A + WHERE NOT EXISTS ( + SELECT AUTH_ID, USER_ID + FROM TBL_AUTH_USER B + WHERE B.AUTH_ID = A.AUTH_ID + AND B.USER_ID = A.USER_ID + ) + +/* 권한-사용자 삭제(authorityMapper.removeUsers) */ +DELETE FROM TBL_AUTH_USER + +AND AUTH_ID IN (#{authID}) +AND USER_ID IN (#{userID}) + + + + \ No newline at end of file diff --git a/src/test/resources/sql/mapper/base/test.xml b/src/test/resources/sql/mapper/base/test.xml new file mode 100644 index 0000000..f62c6fc --- /dev/null +++ b/src/test/resources/sql/mapper/base/test.xml @@ -0,0 +1,14 @@ + + + + +${sql} + +${sql} + +${sql} + +COMMIT + + + \ No newline at end of file diff --git a/src/test/resources/sql/mapper/base/user-mapper.xml b/src/test/resources/sql/mapper/base/user-mapper.xml new file mode 100644 index 0000000..7fea61f --- /dev/null +++ b/src/test/resources/sql/mapper/base/user-mapper.xml @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +SELECT USER_ID + , USER_KEY + , USER_NM + , PASSWD + , PASSWD_HINT + , PASSWD_NSR + , EMP_NO + , RSDNT_NO + , GENDER + , BIRTHDAY + , AREA_NO + , ZIP_CODE + , ADRS + , ADRS_DTL + , PHN_NO + , MBL_PHN_NO + , FX_NO + , EML_ADRS + , POS_NM + , GRP_ID + , ORG_ID + , NSTT_CD + , CRTFC_DN + , LOCK_YN + , LOCK_CNT + , LOCK_DT + , INS_DT + , STATUS + FROM TBL_USER +AND ${by} LIKE CONCAT('%', #{term}, '%') + USER_ID IN (#{userID}) + AND STATUS != 'D' + AND STATUS = #{status} + + + + + + +/* 사용자 정보 등록(userMapper.insertUser) */ +INSERT INTO TBL_USER ( + USER_ID + , USER_KEY + , USER_NM + , PASSWD + , PASSWD_HINT + , PASSWD_NSR + , EMP_NO + , RSDNT_NO + , GENDER + , BIRTHDAY + , AREA_NO + , ZIP_CODE + , ADRS + , ADRS_DTL + , PHN_NO + , MBL_PHN_NO + , FX_NO + , EML_ADRS + , POS_NM + , GRP_ID + , ORG_ID + , NSTT_CD + , CRTFC_DN + , LOCK_YN + , LOCK_CNT + , LOCK_DT + , INS_DT + , STATUS +) VALUES ( + #{id} + , #{key} + , #{name} + , #{password} + , #{passwordHint} + , #{passwordHintAnswer} + , #{empNo} + , #{residentRegNo} + , #{gender} + , #{birthday} + , #{areaNo} + , #{zipCode} + , #{address} + , #{addressDetail} + , #{phoneNo} + , #{mobilePhoneNo} + , #{faxNo} + , #{emailAddress} + , #{positionName} + , #{groupID} + , #{orgID} + , #{institute} + , #{certificateDn} + , 'N' + , 0 + , NULL + , CURRENT_TIMESTAMP() + , #{status} +) + +/* 사용자 정보 수정(userMapper.updateUser) */ +UPDATE TBL_USER SET + USER_KEY = #{key} + , USER_NM = #{name} + , PASSWD_HINT = #{passwordHint} + , PASSWD_NSR = #{passwordHintAnswer} + , EMP_NO = #{empNo} + , RSDNT_NO = #{residentRegNo} + , GENDER = #{gender} + , BIRTHDAY = #{birthday} + , AREA_NO = #{areaNo} + , ZIP_CODE = #{zipCode} + , ADRS = #{address} + , ADRS_DTL = #{addressDetail} + , PHN_NO = #{phoneNo} + , MBL_PHN_NO = #{mobilePhoneNo} + , FX_NO = #{faxNo} + , EML_ADRS = #{emailAddress} + , POS_NM = #{positionName} + , GRP_ID = #{groupID} + , ORG_ID = #{orgID} + , NSTT_CD = #{institute} + , CRTFC_DN = #{certificateDn} + WHERE USER_ID = #{id} + +/* 비밀번호 변경(userMapper.changePassword) */ +UPDATE TBL_USER SET PASSWD = CASE USER_ID + WHEN #{userPassword.userID} THEN #{userPassword.password} + ELSE PASSWD END +WHERE USER_ID IN (#{userID}) + + +/* 사용자 잠김 해제(userMapper.lockUsers) */ +UPDATE TBL_USER SET + LOCK_YN = 'Y' + , LOCK_CNT = LOCK_CNT + 1 + , LOCK_DT = CURRENT_TIMESTAMP() + LOCK_YN = 'N' + , LOCK_CNT = 0 + , LOCK_DT = NULL +WHERE USER_ID IN (#{userID}) + + +/* 사용자 상태 변경(userMapper.setStatus) */ +UPDATE TBL_USER SET STATUS = #{status} +WHERE USER_ID IN (#{userID}) +AND STATUS != #{status} + + + \ No newline at end of file diff --git a/src/test/resources/sql/mapper/base/utility.xml b/src/test/resources/sql/mapper/base/utility.xml new file mode 100644 index 0000000..6e39d6d --- /dev/null +++ b/src/test/resources/sql/mapper/base/utility.xml @@ -0,0 +1,27 @@ + + + + + + +SELECT QROWS.* FROM ( + SELECT ROW_NUMBER() OVER() ROW_NUM + , COUNT(*) OVER() TOT_CNT, QBODY.* + FROM ( + + ) QBODY + ) QROWS +WHERE ROW_NUM BETWEEN ((#{pageNum} - 1) * #{fetchSize}) + 1 AND (#{pageNum} * #{fetchSize}) + + + +ORDER BY ${orderBy} + + + +IFNULL(#{thisDay}, TO_CHAR(CURRENT_DATE, 'YYYYMMDD')) + +SELECTTHIS_DAY + + \ No newline at end of file diff --git a/src/test/resources/sql/mybatis-config.xml b/src/test/resources/sql/mybatis-config.xml new file mode 100644 index 0000000..03ad4e8 --- /dev/null +++ b/src/test/resources/sql/mybatis-config.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file