

250325 김혜민 중복로그인 yml -> db 변경
@ef0eafb7154688fe247fae31815b7addc8f80888
--- build/resources/main/mybatis/mapper/dept/dept-SQL.xml
... | ... | @@ -1,343 +0,0 @@ |
1 | -<?xml version="1.0" encoding="UTF-8"?> | |
2 | -<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |
3 | -<!-- | |
4 | - 작성자 : takensoft | |
5 | - 작성일 : 2024.04.24 | |
6 | - 내 용 : 부서 관련 | |
7 | ---> | |
8 | -<mapper namespace="com.takensoft.cms.dept.dao.DeptDAO"> | |
9 | - <!-- 부서 정보 resultMap(권한포함) --> | |
10 | - <resultMap id="deptMap" type="DeptVO"> | |
11 | - <result property="deptId" column="dept_id" /> | |
12 | - <result property="upDeptId" column="up_dept_id" /> | |
13 | - <result property="deptNm" column="dept_nm" /> | |
14 | - <result property="upDeptNm" column="up_dept_nm" /> | |
15 | - <result property="deptExpln" column="dept_expln" /> | |
16 | - <result property="deptGrd" column="dept_grd" /> | |
17 | - <result property="deptSn" column="dept_sn" /> | |
18 | - <result property="useYn" column="use_yn" /> | |
19 | - <result property="rgtr" column="rgtr" /> | |
20 | - <result property="regDt" column="reg_dt" /> | |
21 | - <result property="mdfr" column="mdfr" /> | |
22 | - <result property="mdfcnDt" column="mdfcn_dt" /> | |
23 | - <collection property="authrtList" column="{deptId = dept_id}" javaType="java.util.ArrayList" ofType="DeptAuthrtVO" select="findByDeptAuthrt" /> | |
24 | - </resultMap> | |
25 | - | |
26 | - <!-- 부서, 권한 매핑 --> | |
27 | - <resultMap id="authMap" type="DeptAuthrtVO"> | |
28 | - <result property="deptId" column="dept_id" /> | |
29 | - <result property="authrtCd" column="authrt_cd" /> | |
30 | - <result property="rgtr" column="rgtr" /> | |
31 | - <result property="regDt" column="reg_dt" /> | |
32 | - </resultMap> | |
33 | - | |
34 | - <!-- | |
35 | - 작성자 : takensoft | |
36 | - 작성일 : 2024.04.25 | |
37 | - 내 용 : 부서 등록 | |
38 | - --> | |
39 | - <insert id="save" parameterType="DeptVO"> | |
40 | - INSERT INTO dept_info ( | |
41 | - dept_id | |
42 | - , up_dept_id | |
43 | - , dept_nm | |
44 | - , dept_expln | |
45 | - , dept_grd | |
46 | - , dept_sn | |
47 | - , use_yn | |
48 | - , rgtr | |
49 | - , reg_dt | |
50 | - ) VALUES ( | |
51 | - #{deptId} | |
52 | - , #{upDeptId} | |
53 | - , #{deptNm} | |
54 | - , #{deptExpln} | |
55 | - , #{deptGrd} | |
56 | - , #{deptSn} | |
57 | - , 'Y' | |
58 | - , #{rgtr} | |
59 | - , NOW() | |
60 | - ) | |
61 | - </insert> | |
62 | - | |
63 | - <!-- | |
64 | - 작성자 : takensoft | |
65 | - 작성일 : 2024.04.25 | |
66 | - 내 용 : 부서 권한 등록 | |
67 | - --> | |
68 | - <insert id="authrtSave" parameterType="DeptAuthrtVO"> | |
69 | - INSERT INTO dept_authrt_info ( | |
70 | - authrt_cd | |
71 | - , dept_id | |
72 | - , rgtr | |
73 | - , reg_dt | |
74 | - ) VALUES ( | |
75 | - #{authrtCd} | |
76 | - , #{deptId} | |
77 | - , #{rgtr} | |
78 | - , NOW() | |
79 | - ) | |
80 | - </insert> | |
81 | - | |
82 | - <!-- | |
83 | - 작성자 : takensoft | |
84 | - 작성일 : 2024.04.29 | |
85 | - 내 용 : 부서 사용자 등록 | |
86 | - --> | |
87 | - <insert id="deptMbrSave" parameterType="DeptMbrVO"> | |
88 | - INSERT INTO dept_mbr_info ( | |
89 | - dept_id | |
90 | - , mbr_id | |
91 | - , rgtr | |
92 | - , reg_dt | |
93 | - ) VALUES ( | |
94 | - #{deptId} | |
95 | - , #{mbrId} | |
96 | - , #{rgtr} | |
97 | - , NOW() | |
98 | - ) | |
99 | - </insert> | |
100 | - | |
101 | - <!-- | |
102 | - 작성자 : takensoft | |
103 | - 작성일 : 2024.04.25 | |
104 | - 내 용 : 부서 깊이(레벨) 조회 | |
105 | - --> | |
106 | - <select id="findByDeptGrd" parameterType="String" resultType="Integer"> | |
107 | - SELECT dept_grd | |
108 | - FROM dept_info | |
109 | - WHERE dept_id = #{upDeptId} | |
110 | - </select> | |
111 | - | |
112 | - <!-- | |
113 | - 작성자 : takensoft | |
114 | - 작성일 : 2024.04.25 | |
115 | - 내 용 : 부서 순서 조회 | |
116 | - --> | |
117 | - <select id="findByDeptSn" parameterType="String" resultType="Integer"> | |
118 | - SELECT COALESCE(MAX(dept_sn), 0) | |
119 | - FROM dept_info | |
120 | - WHERE use_yn = 'Y' | |
121 | - <choose> | |
122 | - <when test="upDeptId != null and upDeptId != ''"> | |
123 | - AND up_dept_id = #{upDeptId} | |
124 | - </when> | |
125 | - <otherwise> | |
126 | - AND up_dept_id IS NULL | |
127 | - </otherwise> | |
128 | - </choose> | |
129 | - </select> | |
130 | - | |
131 | - <!-- 부서 관리(관리자 페이지 Tree 호출용) --> | |
132 | - <sql id="selecteNode"> | |
133 | - SELECT dept_id AS id | |
134 | - , up_dept_id AS up_id | |
135 | - , dept_nm AS nm | |
136 | - FROM dept_info | |
137 | - </sql> | |
138 | - <!-- | |
139 | - 작성자 : takensoft | |
140 | - 작성일 : 2024.04.25 | |
141 | - 내 용 : 상위 부서 조회 | |
142 | - --> | |
143 | - <select id="findByTopNode" resultType="HierachyVO"> | |
144 | - <include refid="selecteNode" /> | |
145 | - WHERE up_dept_id IS NULL | |
146 | - AND use_yn = 'Y' | |
147 | - ORDER BY dept_sn | |
148 | - </select> | |
149 | - <!-- | |
150 | - 작성자 : takensoft | |
151 | - 작성일 : 2024.04.25 | |
152 | - 내 용 : 하위 부서 조회 | |
153 | - --> | |
154 | - <select id="findChildNode" parameterType="String" resultType="HierachyVO"> | |
155 | - <include refid="selecteNode" /> | |
156 | - WHERE up_dept_id = #{id} | |
157 | - AND use_yn = 'Y' | |
158 | - ORDER BY dept_sn | |
159 | - </select> | |
160 | - <!-- 부서 관리(관리자 페이지 Tree 호출용) --> | |
161 | - | |
162 | - <!-- | |
163 | - 작성자 : takensoft | |
164 | - 작성일 : 2024.04.26 | |
165 | - 내 용 : 부서정보 조회 | |
166 | - --> | |
167 | - <select id="findByDept" parameterType="String" resultMap="deptMap"> | |
168 | - SELECT di.dept_id | |
169 | - , di.up_dept_id | |
170 | - , di.dept_nm | |
171 | - , di2.dept_nm AS up_dept_nm | |
172 | - , di.dept_expln | |
173 | - , di.dept_grd | |
174 | - , di.dept_sn | |
175 | - , di.use_yn | |
176 | - , di.rgtr | |
177 | - , TO_CHAR(di.reg_dt, 'YYYY-MM-DD HH24:MI') AS reg_dt | |
178 | - , di.mdfr | |
179 | - , TO_CHAR(di.mdfcn_dt, 'YYYY-MM-DD HH24:MI') AS mdfcn_dt | |
180 | - FROM dept_info di | |
181 | - LEFT JOIN dept_info di2 | |
182 | - ON di.up_dept_id = di2.dept_id | |
183 | - WHERE di.dept_id = #{deptId} | |
184 | - </select> | |
185 | - | |
186 | - <!-- | |
187 | - 작성자 : takensoft | |
188 | - 작성일 : 2024.04.26 | |
189 | - 내 용 : 부서 권한 정보 조회 | |
190 | - --> | |
191 | - <select id="findByDeptAuthrt" parameterType="DeptVO" resultMap="authMap"> | |
192 | - SELECT dai.authrt_cd | |
193 | - , dai.dept_id | |
194 | - , dai.rgtr | |
195 | - , TO_CHAR(dai.reg_dt, 'YYYY-MM-DD HH24:MI') AS reg_dt | |
196 | - FROM dept_authrt_info dai | |
197 | - WHERE dai.dept_id = #{deptId} | |
198 | - </select> | |
199 | - | |
200 | - <!-- | |
201 | - 작성자 : takensoft | |
202 | - 작성일 : 2024.04.26 | |
203 | - 내 용 : 부서정보 수정 | |
204 | - --> | |
205 | - <update id="update" parameterType="DeptVO"> | |
206 | - UPDATE dept_info | |
207 | - SET up_dept_id = #{upDeptId} | |
208 | - , dept_nm = #{deptNm} | |
209 | - , dept_expln = #{deptExpln} | |
210 | - , dept_grd = #{deptGrd} | |
211 | - , dept_sn = #{deptSn} | |
212 | - , use_yn = #{useYn} | |
213 | - , mdfr = #{mdfr} | |
214 | - , mdfcn_dt = NOW() | |
215 | - WHERE dept_id = #{deptId} | |
216 | - </update> | |
217 | - | |
218 | - <!-- | |
219 | - 작성자 : takensoft | |
220 | - 작성일 : 2024.04.26 | |
221 | - 내 용 : 부서권한 삭제 | |
222 | - --> | |
223 | - <delete id="deleteAuth" parameterType="String"> | |
224 | - DELETE | |
225 | - FROM dept_authrt_info | |
226 | - WHERE dept_id = #{deptId} | |
227 | - </delete> | |
228 | - | |
229 | - <!-- | |
230 | - 작성자 : takensoft | |
231 | - 작성일 : 2024.04.26 | |
232 | - 내 용 : 부서 사용자 등록을 위한 사용자 정보 목록 조회 (부서에 등록되지 않은 사용자 목록) | |
233 | - --> | |
234 | - <select id="findByMber" parameterType="Map" resultType="DeptMbrVO"> | |
235 | - SELECT mi.mbr_id | |
236 | - , mi.lgn_id | |
237 | - , mi.mbr_nm | |
238 | - , mi.ncnm | |
239 | - , mi.eml | |
240 | - , mi.mbl_telno | |
241 | - FROM mbr_info mi | |
242 | - WHERE NOT EXISTS ( | |
243 | - SELECT 1 | |
244 | - FROM dept_mbr_info dmi | |
245 | - WHERE dmi.mbr_id = mi.mbr_id | |
246 | - ) | |
247 | - AND mi.use_yn = 'Y' | |
248 | - AND mi.mbr_stts = '1' | |
249 | - <if test="searchText != null and searchText != ''"> | |
250 | - AND mi.mbr_nm LIKE '%' || #{searchText} || '%' | |
251 | - </if> | |
252 | - </select> | |
253 | - | |
254 | - <!-- | |
255 | - 작성자 : takensoft | |
256 | - 작성일 : 2024.04.26 | |
257 | - 내 용 : 부서에 등록된 사용자 목록 조회 | |
258 | - --> | |
259 | - <select id="findByDeptMber" parameterType="String" resultType="DeptMbrVO"> | |
260 | - SELECT dmi.dept_id | |
261 | - , mi.mbr_id | |
262 | - , mi.lgn_id | |
263 | - , mi.mbr_nm | |
264 | - , mi.ncnm | |
265 | - , mi.eml | |
266 | - , mi.mbl_telno | |
267 | - , dmi.rgtr | |
268 | - , TO_CHAR(dmi.reg_dt, 'YYYY-MM-DD HH24:MI') AS reg_dt | |
269 | - FROM dept_mbr_info dmi | |
270 | - LEFT JOIN mbr_info mi | |
271 | - ON dmi.mbr_id = mi.mbr_id | |
272 | - WHERE dmi.dept_id = #{deptId} | |
273 | - </select> | |
274 | - | |
275 | - <!-- | |
276 | - 작성자 : takensoft | |
277 | - 작성일 : 2024.04.26 | |
278 | - 내 용 : 상위,하위 부서 삭제 | |
279 | - --> | |
280 | - <update id="deleteDept" parameterType="String"> | |
281 | - WITH RECURSIVE DeptHierarchy AS ( | |
282 | - SELECT dept_id | |
283 | - , up_dept_id | |
284 | - FROM dept_info | |
285 | - WHERE dept_id = #{deptId} | |
286 | - UNION ALL | |
287 | - SELECT di.dept_id | |
288 | - , di.up_dept_id | |
289 | - FROM dept_info di | |
290 | - JOIN DeptHierarchy dh | |
291 | - ON di.up_dept_id = dh.dept_id | |
292 | - ) | |
293 | - UPDATE dept_info SET use_yn = 'N' WHERE dept_id IN (SELECT dept_id FROM DeptHierarchy); | |
294 | - </update> | |
295 | - | |
296 | - <!-- | |
297 | - 작성자 : takensoft | |
298 | - 작성일 : 2024.04.26 | |
299 | - 내 용 : 상위, 하위 부서 삭제에 따른 부서의 사용자 삭제 | |
300 | - --> | |
301 | - <delete id="deleteDeptInDeptMbr" parameterType="String"> | |
302 | - WITH RECURSIVE DeptHierarchy AS ( | |
303 | - SELECT dept_id | |
304 | - , up_dept_id | |
305 | - FROM dept_info | |
306 | - WHERE dept_id = #{deptId} | |
307 | - UNION ALL | |
308 | - SELECT di.dept_id | |
309 | - , di.up_dept_id | |
310 | - FROM dept_info di | |
311 | - JOIN DeptHierarchy dh | |
312 | - ON di.up_dept_id = dh.dept_id | |
313 | - ) | |
314 | - DELETE FROM dept_mbr_info WHERE dept_id IN (SELECT dept_id FROM DeptHierarchy); | |
315 | - </delete> | |
316 | - | |
317 | - <!-- | |
318 | - 작성자 : takensoft | |
319 | - 작성일 : 2024.04.26 | |
320 | - 내 용 : 부서에 등록된 사용자 제거 | |
321 | - --> | |
322 | - <delete id="deleteDeptMbr" parameterType="Map"> | |
323 | - DELETE | |
324 | - FROM dept_mbr_info | |
325 | - WHERE 1 = 1 | |
326 | - AND mbr_id = #{mbrId} | |
327 | - </delete> | |
328 | - | |
329 | - <!-- | |
330 | - 작성자 : 박정하 | |
331 | - 작성일 : 2024.05.10 | |
332 | - 내 용 : 부서 정보 수정 (hierachyVO 사용) | |
333 | - --> | |
334 | - <update id="deptUpdateByHierachy" parameterType="DeptVO"> | |
335 | - UPDATE dept_info | |
336 | - SET up_dept_id = #{upDeptId} | |
337 | - , dept_grd = #{deptGrd} | |
338 | - , dept_sn = #{deptSn} | |
339 | - , mdfr = #{mdfr} | |
340 | - , mdfcn_dt = NOW() | |
341 | - WHERE dept_id = #{deptId} | |
342 | - </update> | |
343 | -</mapper>(파일 끝에 줄바꿈 문자 없음) |
+++ src/main/java/com/takensoft/cms/loginPolicy/dao/LoginPolicyDAO.java
... | ... | @@ -0,0 +1,36 @@ |
1 | +package com.takensoft.cms.loginPolicy.dao; | |
2 | + | |
3 | +import com.takensoft.cms.accesCtrl.vo.AccesCtrlVO; | |
4 | +import com.takensoft.cms.loginPolicy.vo.LoginPolicyVO; | |
5 | +import com.takensoft.common.Pagination; | |
6 | +import org.egovframe.rte.psl.dataaccess.mapper.Mapper; | |
7 | + | |
8 | +import java.util.List; | |
9 | + | |
10 | +/** | |
11 | + * @author 김혜민 | |
12 | + * @since 2025.03.22 | |
13 | + * @modification | |
14 | + * since | author | description | |
15 | + * 2025.03.22 | 김혜민 | 최초 등록 | |
16 | + * | |
17 | + * 중복로그인 허용 관련 DAO | |
18 | + */ | |
19 | +@Mapper("loginPolicyDAO") | |
20 | +public interface LoginPolicyDAO { | |
21 | + | |
22 | + /** | |
23 | + * @return Boolean - 중복로그인 여부 | |
24 | + * | |
25 | + * 중복로그인 조회 | |
26 | + */ | |
27 | + Boolean selectLatestPolicy(); | |
28 | + | |
29 | + /** | |
30 | + * @param loginPolicyVO - 중복로그인 정보 | |
31 | + * @return int - 중복로그인 저장 결과 | |
32 | + * | |
33 | + * 중복로그인 저장 | |
34 | + */ | |
35 | + int insertPolicy(LoginPolicyVO loginPolicyVO); | |
36 | +} |
+++ src/main/java/com/takensoft/cms/loginPolicy/service/LoginPolicyService.java
... | ... | @@ -0,0 +1,40 @@ |
1 | +package com.takensoft.cms.loginPolicy.service; | |
2 | + | |
3 | +import com.takensoft.cms.bbs.vo.BbsCnVO; | |
4 | +import com.takensoft.cms.loginPolicy.vo.LoginPolicyVO; | |
5 | +import com.takensoft.common.Pagination; | |
6 | +import org.springframework.dao.DataAccessException; | |
7 | +import org.springframework.web.multipart.MultipartFile; | |
8 | + | |
9 | +import java.util.HashMap; | |
10 | +import java.util.List; | |
11 | +import java.util.Map; | |
12 | + | |
13 | +/** | |
14 | + * @author 김혜민 | |
15 | + * @since 2024.05.21 | |
16 | + * @modification | |
17 | + * since | author | description | |
18 | + * 2024.05.21 | 김혜민 | 최초 등록 | |
19 | + * | |
20 | + * 중복로그인 관련 인터페이스 | |
21 | + */ | |
22 | +public interface LoginPolicyService { | |
23 | + | |
24 | + /** | |
25 | + * | |
26 | + * @return LoginPolicyVO - 중복로그인 여부 조회 | |
27 | + * | |
28 | + * 중복로그인 조회 | |
29 | + */ | |
30 | + public boolean getPolicy(); | |
31 | + /** | |
32 | + * @param loginPolicyVO - 접근 제어 정보 | |
33 | + * @return ResponseEntity - 접근 제어 수정 결과를 포함하는 응답 | |
34 | + * | |
35 | + * 중복로그인 수정 | |
36 | + */ | |
37 | + public int insertPolicy(LoginPolicyVO loginPolicyVO); | |
38 | + | |
39 | + | |
40 | +}(파일 끝에 줄바꿈 문자 없음) |
+++ src/main/java/com/takensoft/cms/loginPolicy/service/impl/LoginPolicyServiceImpl.java
... | ... | @@ -0,0 +1,72 @@ |
1 | +package com.takensoft.cms.loginPolicy.service.impl; | |
2 | + | |
3 | +import com.takensoft.cms.bbs.dao.BbsCnDAO; | |
4 | +import com.takensoft.cms.bbs.dao.BbsMngDAO; | |
5 | +import com.takensoft.cms.bbs.dao.WordMngDAO; | |
6 | +import com.takensoft.cms.bbs.service.BbsCnService; | |
7 | +import com.takensoft.cms.bbs.vo.BbsCnVO; | |
8 | +import com.takensoft.cms.bbs.vo.BbsMngVO; | |
9 | +import com.takensoft.cms.loginPolicy.dao.LoginPolicyDAO; | |
10 | +import com.takensoft.cms.loginPolicy.service.LoginPolicyService; | |
11 | +import com.takensoft.cms.loginPolicy.vo.LoginPolicyVO; | |
12 | +import com.takensoft.common.Pagination; | |
13 | +import com.takensoft.common.exception.*; | |
14 | +import com.takensoft.common.file.dao.FileDAO; | |
15 | +import com.takensoft.common.file.service.FileMngService; | |
16 | +import com.takensoft.common.file.vo.FileMngVO; | |
17 | +import com.takensoft.common.idgen.service.IdgenService; | |
18 | +import com.takensoft.common.util.JWTUtil; | |
19 | +import lombok.RequiredArgsConstructor; | |
20 | +import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl; | |
21 | +import org.springframework.beans.factory.annotation.Value; | |
22 | +import org.springframework.dao.DataAccessException; | |
23 | +import org.springframework.stereotype.Service; | |
24 | +import org.springframework.transaction.annotation.Transactional; | |
25 | +import org.springframework.web.multipart.MultipartFile; | |
26 | + | |
27 | +import java.util.HashMap; | |
28 | +import java.util.List; | |
29 | +import java.util.Map; | |
30 | + | |
31 | +/** | |
32 | + * @author 김혜민 | |
33 | + * @since 2024.05.21 | |
34 | + * @modification | |
35 | + * since | author | description | |
36 | + * 2024.05.21 | 김혜민 | 최초 등록 | |
37 | + * | |
38 | + * EgovAbstractServiceImpl : 전자정부 상속 | |
39 | + * LoginPolicyService : 중복 로그인 관련 인터페이스 상속 | |
40 | + * | |
41 | + * 중복 로그인 관련 인터페이스 구현체 | |
42 | + */ | |
43 | +@Service("loginPolicyService") | |
44 | +@RequiredArgsConstructor | |
45 | +public class LoginPolicyServiceImpl extends EgovAbstractServiceImpl implements LoginPolicyService { | |
46 | + | |
47 | + private final LoginPolicyDAO loginPolicyDAO; | |
48 | + private final IdgenService loginPolicyIdgn; | |
49 | + | |
50 | + /** | |
51 | + * @return Boolean - 중복로그인 여부 | |
52 | + * | |
53 | + * 중복로그인 조회 | |
54 | + */ | |
55 | + @Override | |
56 | + public boolean getPolicy() { | |
57 | + return loginPolicyDAO.selectLatestPolicy(); | |
58 | + } | |
59 | + | |
60 | + /** | |
61 | + * @param loginPolicyVO - 중복로그인 정보 | |
62 | + * @return int - 중복로그인 저장 결과 | |
63 | + * | |
64 | + * 중복로그인 저장 | |
65 | + */ | |
66 | + @Override | |
67 | + public int insertPolicy(LoginPolicyVO loginPolicyVO) { | |
68 | + loginPolicyVO.setId(loginPolicyIdgn.getNextStringId()); // ID 자동 생성 | |
69 | + return loginPolicyDAO.insertPolicy(loginPolicyVO); | |
70 | + } | |
71 | + | |
72 | +}(파일 끝에 줄바꿈 문자 없음) |
+++ src/main/java/com/takensoft/cms/loginPolicy/vo/LoginPolicyVO.java
... | ... | @@ -0,0 +1,26 @@ |
1 | +package com.takensoft.cms.loginPolicy.vo; | |
2 | + | |
3 | +import lombok.AllArgsConstructor; | |
4 | +import lombok.Getter; | |
5 | +import lombok.NoArgsConstructor; | |
6 | +import lombok.Setter; | |
7 | + | |
8 | +/** | |
9 | + * @author : 김혜민 | |
10 | + * @since : 2025.03.22 | |
11 | + * @modification | |
12 | + * since | author | description | |
13 | + * 2025.03.22 | 김혜민 | 최초 등록 | |
14 | + * | |
15 | + * 중복로그인 허용 관련 VO | |
16 | + */ | |
17 | +@Getter | |
18 | +@Setter | |
19 | +@NoArgsConstructor | |
20 | +@AllArgsConstructor | |
21 | +public class LoginPolicyVO { | |
22 | + | |
23 | + private String id; // 중복로그인 ID | |
24 | + private boolean allowMultipleLogin; // 중복 로그인 허용 여부 | |
25 | + private String mbrId; // 수정한 관리자 ID | |
26 | +}(파일 끝에 줄바꿈 문자 없음) |
+++ src/main/java/com/takensoft/cms/loginPolicy/web/LoginPolicyController.java
... | ... | @@ -0,0 +1,85 @@ |
1 | +package com.takensoft.cms.loginPolicy.web; | |
2 | + | |
3 | +import com.takensoft.cms.loginPolicy.service.LoginPolicyService; | |
4 | +import com.takensoft.cms.loginPolicy.vo.LoginPolicyVO; | |
5 | +import com.takensoft.common.message.MessageCode; | |
6 | +import com.takensoft.common.util.JWTUtil; | |
7 | +import com.takensoft.common.util.ResponseUtil; | |
8 | +import jakarta.servlet.http.HttpServletRequest; | |
9 | +import lombok.RequiredArgsConstructor; | |
10 | +import lombok.extern.slf4j.Slf4j; | |
11 | +import org.springframework.dao.DuplicateKeyException; | |
12 | +import org.springframework.http.ResponseEntity; | |
13 | +import org.springframework.web.bind.annotation.*; | |
14 | + | |
15 | +import java.util.Map; | |
16 | + | |
17 | +/** | |
18 | + * @author 김혜민 | |
19 | + * @since 2025.03.22 | |
20 | + * @modification | |
21 | + * since | author | description | |
22 | + * 2025.03.22 | 김혜민 | 최초 등록 | |
23 | + * | |
24 | + * 중복로그인 허용 관련 컨트롤러 | |
25 | + */ | |
26 | +@RestController | |
27 | +@RequiredArgsConstructor | |
28 | +@Slf4j | |
29 | +@RequestMapping(value = "/admin/allowMultipleLogin") | |
30 | +public class LoginPolicyController { | |
31 | + | |
32 | + private final LoginPolicyService loginPolicyService; | |
33 | + private final ResponseUtil resUtil; | |
34 | + private final JWTUtil jwtUtil; | |
35 | + | |
36 | + /** | |
37 | + * | |
38 | + * @return ResponseEntity - 접근 제어 목록 조회 결과를 포함하는 응답 | |
39 | + * | |
40 | + * 중복로그인 조회 | |
41 | + */ | |
42 | + @GetMapping("/login-policy") | |
43 | + public ResponseEntity<?> getPolicy() { | |
44 | + Boolean isAllowed = loginPolicyService.getPolicy(); | |
45 | + return resUtil.successRes(isAllowed, MessageCode.COMMON_SUCCESS); | |
46 | + } | |
47 | + | |
48 | + /** | |
49 | + * @param params - 접근 제어 정보 | |
50 | + * @return ResponseEntity - 접근 제어 수정 결과를 포함하는 응답 | |
51 | + * | |
52 | + * 중복로그인 수정 | |
53 | + */ | |
54 | + @PostMapping("/login-policy") | |
55 | + public ResponseEntity<?> updatePolicy(@RequestBody Map<String, Object> params, HttpServletRequest request) { | |
56 | + try { | |
57 | + boolean allow = (Boolean) params.get("allowMultipleLogin"); | |
58 | + | |
59 | + String token = request.getHeader("Authorization"); | |
60 | + String adminId = (String) jwtUtil.getClaim(token, "mbrId"); | |
61 | + | |
62 | + if (adminId == null || adminId.isBlank()) { | |
63 | + return resUtil.errorRes(MessageCode.COMMON_BAD_REQUEST); | |
64 | + } | |
65 | + | |
66 | + LoginPolicyVO loginPolicyVO = new LoginPolicyVO(); | |
67 | + loginPolicyVO.setAllowMultipleLogin(allow); | |
68 | + loginPolicyVO.setMbrId(adminId); | |
69 | + | |
70 | + int result = loginPolicyService.insertPolicy(loginPolicyVO); | |
71 | + | |
72 | + if (result > 0) { | |
73 | + return resUtil.successRes(result, MessageCode.COMMON_SUCCESS); | |
74 | + } else { | |
75 | + return resUtil.errorRes(MessageCode.COMMON_INSERT_FAIL); // 저장 실패 | |
76 | + } | |
77 | + | |
78 | + } catch (DuplicateKeyException e) { | |
79 | + return resUtil.errorRes(MessageCode.COMMON_DUPLICATION_DATA); // 중복 저장 | |
80 | + } catch (Exception e) { | |
81 | + return resUtil.errorRes(MessageCode.COMMON_UNKNOWN_ERROR); // 기타 예외 | |
82 | + } | |
83 | + } | |
84 | + | |
85 | +} |
--- src/main/java/com/takensoft/common/config/RedisConfig.java
+++ src/main/java/com/takensoft/common/config/RedisConfig.java
... | ... | @@ -1,5 +1,6 @@ |
1 | 1 |
package com.takensoft.common.config; |
2 | 2 |
|
3 |
+import com.takensoft.cms.loginPolicy.dao.LoginPolicyDAO; |
|
3 | 4 |
import org.springframework.beans.factory.annotation.Value; |
4 | 5 |
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
5 | 6 |
import org.springframework.context.annotation.Bean; |
... | ... | @@ -27,8 +28,11 @@ |
27 | 28 |
@Value("${redis.port}") |
28 | 29 |
private int redisPort; |
29 | 30 |
|
30 |
- @Value("${config.allow-multiple-logins}") // 기본값 false (중복 로그인 비허용) |
|
31 |
- private boolean allowMultipleLogin; |
|
31 |
+ private final LoginPolicyDAO loginPolicyDAO; |
|
32 |
+ |
|
33 |
+ public RedisConfig(LoginPolicyDAO loginPolicyDAO) { |
|
34 |
+ this.loginPolicyDAO = loginPolicyDAO; |
|
35 |
+ } |
|
32 | 36 |
|
33 | 37 |
@Bean |
34 | 38 |
@ConditionalOnProperty(name = "config.allow-multiple-logins", havingValue = "false", matchIfMissing = true) //redis 사용 안 할 경우 빈 등록x |
... | ... | @@ -51,15 +55,8 @@ |
51 | 55 |
* 중복 로그인 허용 여부를 반환하는 메서드 |
52 | 56 |
*/ |
53 | 57 |
public boolean isAllowMultipleLogin() { |
54 |
- return allowMultipleLogin; |
|
55 |
- } |
|
56 |
- /** |
|
57 |
- * @return allowMultipleLogin - 중복로그인 허용/비허용 반환 |
|
58 |
- * |
|
59 |
- * 관리자가 설정을 변경할 수 있도록 Setter 추가 |
|
60 |
- */ |
|
61 |
- public void setAllowMultipleLogin(boolean allowMultipleLogin) { |
|
62 |
- this.allowMultipleLogin = allowMultipleLogin; |
|
58 |
+ Boolean result = loginPolicyDAO.selectLatestPolicy(); |
|
59 |
+ return Boolean.TRUE.equals(result); |
|
63 | 60 |
} |
64 | 61 |
|
65 | 62 |
} |
--- src/main/java/com/takensoft/common/filter/JWTFilter.java
+++ src/main/java/com/takensoft/common/filter/JWTFilter.java
... | ... | @@ -4,6 +4,7 @@ |
4 | 4 |
import com.takensoft.cms.mber.vo.MberVO; |
5 | 5 |
import com.takensoft.common.config.AppConfig; |
6 | 6 |
import com.takensoft.common.config.RedisConfig; |
7 |
+import com.takensoft.common.exception.FilterExceptionHandler; |
|
7 | 8 |
import com.takensoft.common.util.ErrorResponse; |
8 | 9 |
import com.takensoft.common.util.JWTUtil; |
9 | 10 |
import io.jsonwebtoken.ExpiredJwtException; |
... | ... | @@ -37,6 +38,7 @@ |
37 | 38 |
*/ |
38 | 39 |
public class JWTFilter extends OncePerRequestFilter { |
39 | 40 |
|
41 |
+ private static final String AUTHORIZATION_HEADER = "Authorization"; |
|
40 | 42 |
private final JWTUtil jwtUtil; |
41 | 43 |
private final AppConfig appConfig; |
42 | 44 |
private final RedisConfig redisConfig; |
... | ... | @@ -65,7 +67,7 @@ |
65 | 67 |
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { |
66 | 68 |
try { |
67 | 69 |
// 헤더에서 access에 대한 토큰을 꺼냄 |
68 |
- String accessToken = request.getHeader("Authorization"); |
|
70 |
+ String accessToken = request.getHeader(AUTHORIZATION_HEADER); |
|
69 | 71 |
// 토큰이 없다면 다음 필터로 넘김 |
70 | 72 |
if(accessToken == null) { |
71 | 73 |
filterChain.doFilter(request, response); |
... | ... | @@ -73,8 +75,19 @@ |
73 | 75 |
} |
74 | 76 |
|
75 | 77 |
// 토큰 만료 여부 검증 |
76 |
- if( (Boolean) jwtUtil.getClaim(accessToken, "isExpired")) { |
|
77 |
- throw new JwtException("Token expired"); |
|
78 |
+ if ((Boolean) jwtUtil.getClaim(accessToken, "isExpired")) { |
|
79 |
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE); |
|
80 |
+ response.setStatus(HttpStatus.UNAUTHORIZED.value()); |
|
81 |
+ |
|
82 |
+ ErrorResponse errorResponse = new ErrorResponse(); |
|
83 |
+ errorResponse.setMessage("Token expired"); |
|
84 |
+ errorResponse.setPath(request.getRequestURI()); |
|
85 |
+ errorResponse.setError(HttpStatus.UNAUTHORIZED.getReasonPhrase()); |
|
86 |
+ errorResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); |
|
87 |
+ errorResponse.setTimestamp(LocalDateTime.now()); |
|
88 |
+ |
|
89 |
+ response.getOutputStream().write(appConfig.getObjectMapper().writeValueAsBytes(errorResponse)); |
|
90 |
+ return; |
|
78 | 91 |
} |
79 | 92 |
// 토큰에서 페이로드 확인[ 발급시 명시 ] |
80 | 93 |
String category = (String) jwtUtil.getClaim(accessToken, "category"); |
... | ... | @@ -99,7 +112,7 @@ |
99 | 112 |
response.setStatus(HttpStatus.UNAUTHORIZED.value()); |
100 | 113 |
|
101 | 114 |
ErrorResponse errorResponse = new ErrorResponse(); |
102 |
- errorResponse.setMessage("다른 기기에서 로그인되었습니다."); |
|
115 |
+ errorResponse.setMessage("Token expired"); |
|
103 | 116 |
errorResponse.setPath(request.getRequestURI()); |
104 | 117 |
errorResponse.setError(HttpStatus.UNAUTHORIZED.getReasonPhrase()); |
105 | 118 |
errorResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); |
... | ... | @@ -116,24 +129,14 @@ |
116 | 129 |
SecurityContextHolder.getContext().setAuthentication(authToken); |
117 | 130 |
// 다음 필터로 이동 |
118 | 131 |
filterChain.doFilter(request, response); |
119 |
- } catch(JwtException | InsufficientAuthenticationException e) { |
|
120 |
- // 토큰 검증 실패 시 클라이언트에게 에러 응답을 위한 객체 생성 |
|
121 |
- ErrorResponse errorResponse = new ErrorResponse(); |
|
122 |
- // 에러 응답 메시지 설정 |
|
123 |
- if(e instanceof ExpiredJwtException) { |
|
124 |
- errorResponse.setMessage("Token expired"); |
|
125 |
- } else { |
|
126 |
- errorResponse.setMessage(e.getMessage()); |
|
127 |
- } |
|
128 |
- errorResponse.setPath(request.getRequestURI()); // 오류 경로 설정 |
|
129 |
- errorResponse.setError(HttpStatus.UNAUTHORIZED.getReasonPhrase()); // 오류 원인 설정 |
|
130 |
- errorResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); // 상태코드 설정 |
|
131 |
- errorResponse.setTimestamp(LocalDateTime.now()); // 응답 시간 설정 |
|
132 |
- |
|
133 |
- // 응답 헤더 설정 및 json 응답 전송 |
|
134 |
- response.setContentType(MediaType.APPLICATION_JSON_VALUE); |
|
135 |
- response.setStatus(HttpStatus.UNAUTHORIZED.value()); |
|
136 |
- response.getOutputStream().write(appConfig.getObjectMapper().writeValueAsBytes(errorResponse)); |
|
132 |
+ } catch (ExpiredJwtException e) { |
|
133 |
+ FilterExceptionHandler.jwtError(response, e); |
|
134 |
+ } catch (JwtException e) { |
|
135 |
+ FilterExceptionHandler.jwtError(response, e); |
|
136 |
+ } catch (InsufficientAuthenticationException e) { |
|
137 |
+ FilterExceptionHandler.jwtError(response, e); |
|
138 |
+ } catch (Exception e) { |
|
139 |
+ FilterExceptionHandler.jwtError(response, e); |
|
137 | 140 |
} |
138 | 141 |
} |
139 | 142 |
}(파일 끝에 줄바꿈 문자 없음) |
--- src/main/java/com/takensoft/common/idgen/context/ContextIdgen.java
+++ src/main/java/com/takensoft/common/idgen/context/ContextIdgen.java
... | ... | @@ -3,7 +3,15 @@ |
3 | 3 |
import com.takensoft.common.idgen.service.IdgenService; |
4 | 4 |
import org.springframework.context.annotation.Bean; |
5 | 5 |
import org.springframework.context.annotation.Configuration; |
6 |
- |
|
6 |
+/** |
|
7 |
+ * @author takensoft |
|
8 |
+ * @since 2025.01.22 |
|
9 |
+ * @modification |
|
10 |
+ * since | author | description |
|
11 |
+ * 2025.01.22 | takensoft | 최초 등록 |
|
12 |
+ * |
|
13 |
+ * 고유 아이디 생성 클래스 |
|
14 |
+ */ |
|
7 | 15 |
@Configuration |
8 | 16 |
public class ContextIdgen { |
9 | 17 |
|
... | ... | @@ -149,4 +157,15 @@ |
149 | 157 |
idgenServiceImpl.setTblNm("POPUP_MNG_ID"); |
150 | 158 |
return idgenServiceImpl; |
151 | 159 |
} |
160 |
+ |
|
161 |
+ // 중복 로그인 정책 이력 ID |
|
162 |
+ @Bean(name = "loginPolicyIdgn") |
|
163 |
+ public IdgenService loginPolicyIdgn() { |
|
164 |
+ IdgenService idgenServiceImpl = new IdgenService(); |
|
165 |
+ idgenServiceImpl.setCipers(15); // 총 자릿수 |
|
166 |
+ idgenServiceImpl.setFillChar('0'); // 0으로 채움 |
|
167 |
+ idgenServiceImpl.setPrefix("LOGIN_POL_"); // 접두사 |
|
168 |
+ idgenServiceImpl.setTblNm("LOGIN_POLICY_ID"); // 시퀀스 테이블명 |
|
169 |
+ return idgenServiceImpl; |
|
170 |
+ } |
|
152 | 171 |
}(파일 끝에 줄바꿈 문자 없음) |
--- src/main/java/com/takensoft/common/idgen/vo/IdgenVO.java
+++ src/main/java/com/takensoft/common/idgen/vo/IdgenVO.java
... | ... | @@ -22,6 +22,6 @@ |
22 | 22 |
@Setter |
23 | 23 |
public class IdgenVO implements Serializable { |
24 | 24 |
|
25 |
- private String tblNm; // 테이블명 |
|
25 |
+ private String tblNm; // 테이블명 |
|
26 | 26 |
private int aftrId; // ID값 |
27 | 27 |
} |
--- src/main/java/com/takensoft/common/util/JWTUtil.java
+++ src/main/java/com/takensoft/common/util/JWTUtil.java
... | ... | @@ -3,7 +3,7 @@ |
3 | 3 |
import com.takensoft.cms.mber.vo.MberAuthorVO; |
4 | 4 |
import com.takensoft.cms.mber.vo.MberVO; |
5 | 5 |
import io.jsonwebtoken.Claims; |
6 |
-import io.jsonwebtoken.JwtException; |
|
6 |
+import io.jsonwebtoken.ExpiredJwtException; |
|
7 | 7 |
import io.jsonwebtoken.Jwts; |
8 | 8 |
import org.springframework.beans.factory.annotation.Value; |
9 | 9 |
import org.springframework.security.core.Authentication; |
... | ... | @@ -100,11 +100,19 @@ |
100 | 100 |
* 클레임 조회 |
101 | 101 |
*/ |
102 | 102 |
public Object getClaim(String tkn, String knd) { |
103 |
- Claims claims = Jwts.parser() |
|
104 |
- .verifyWith(JWT_SECRET_KEY) |
|
105 |
- .build() |
|
106 |
- .parseSignedClaims(tkn) |
|
107 |
- .getPayload(); |
|
103 |
+ Claims claims; |
|
104 |
+ try { |
|
105 |
+ claims = Jwts.parser() |
|
106 |
+ .clockSkewSeconds(60) |
|
107 |
+ .verifyWith(JWT_SECRET_KEY) |
|
108 |
+ .build() |
|
109 |
+ .parseSignedClaims(tkn) |
|
110 |
+ .getPayload(); |
|
111 |
+ } catch (ExpiredJwtException e) { |
|
112 |
+ // 만료된 토큰이라도 claims 꺼내기 가능 |
|
113 |
+ claims = e.getClaims(); |
|
114 |
+ } |
|
115 |
+ |
|
108 | 116 |
switch (knd) { |
109 | 117 |
case "category": |
110 | 118 |
return claims.get("category", String.class); |
... | ... | @@ -115,24 +123,20 @@ |
115 | 123 |
case "mbrNm": |
116 | 124 |
return claims.get("mbrNm", String.class); |
117 | 125 |
case "roles": |
118 |
- // roles 클레임에서 사용자 권한 정보를 파싱 |
|
119 | 126 |
List<Map<String, Object>> roles = claims.get("roles", List.class); |
120 | 127 |
List<MberAuthorVO> authorList = new ArrayList<>(); |
121 |
- if(roles != null && !roles.isEmpty()) { |
|
122 |
- for(Map<String, Object> role : roles) { |
|
128 |
+ if (roles != null && !roles.isEmpty()) { |
|
129 |
+ for (Map<String, Object> role : roles) { |
|
123 | 130 |
MberAuthorVO userAuthor = new MberAuthorVO(role.get("authority").toString()); |
124 | 131 |
authorList.add(userAuthor); |
125 | 132 |
} |
126 | 133 |
} |
127 | 134 |
return authorList; |
128 | 135 |
case "isExpired": |
129 |
- // 토큰 만료 여부 반환 |
|
130 | 136 |
return claims.getExpiration().before(new Date()); |
131 | 137 |
case "expired": |
132 |
- // 토큰 만료 시간 반환 |
|
133 | 138 |
return claims.getExpiration(); |
134 | 139 |
default: |
135 |
- // 유효하지 않는 knd 처리 |
|
136 | 140 |
throw new IllegalArgumentException("Invalid knd : " + knd); |
137 | 141 |
} |
138 | 142 |
} |
+++ src/main/resources/mybatis/mapper/loginPolicy/loginPolicy-SQL.xml
... | ... | @@ -0,0 +1,31 @@ |
1 | +<?xml version="1.0" encoding="UTF-8"?> | |
2 | +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |
3 | +<!-- | |
4 | + 작성자 : takensoft | |
5 | + 작성일 : 2025.03.22 | |
6 | + 내 용 : 중복로그인 관련 | |
7 | +--> | |
8 | +<mapper namespace="com.takensoft.cms.logionPolicy.dao.LogionPolicyDAO"> | |
9 | + | |
10 | + <select id="selectLatestPolicy" resultType="boolean"> | |
11 | + SELECT allow_multiple_login | |
12 | + FROM login_policy_history | |
13 | + ORDER BY updated_at DESC | |
14 | + LIMIT 1 | |
15 | + </select> | |
16 | + | |
17 | + <!-- 새로운 설정 삽입 --> | |
18 | + <insert id="insertPolicy" parameterType="LoginPolicyVO"> | |
19 | + INSERT INTO login_policy_history ( | |
20 | + allow_multiple_login, | |
21 | + updated_by, | |
22 | + updated_at | |
23 | + ) | |
24 | + VALUES ( | |
25 | + #{allowMultipleLogin}, | |
26 | + #{updatedBy}, | |
27 | + NOW() | |
28 | + ) | |
29 | + </insert> | |
30 | + | |
31 | +</mapper>(파일 끝에 줄바꿈 문자 없음) |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?