

250327 김혜민 로그인방식 변경시 전체 로그아웃 반영
@023c598d7a547cec3e412ee252c5dee84ebb7a85
--- src/main/java/com/takensoft/cms/loginPolicy/vo/LoginModeVO.java
+++ src/main/java/com/takensoft/cms/loginPolicy/vo/LoginModeVO.java
... | ... | @@ -21,7 +21,7 @@ |
21 | 21 |
public class LoginModeVO { |
22 | 22 |
|
23 | 23 |
private String lgnModeId; // 로그인 방식 설정 ID |
24 |
- private String lgnMode; // 로그인 방식 (JWT / SESSION) |
|
25 |
- private String rgtrId; // 등록자 ID |
|
24 |
+ private String lgnMode; // 로그인 방식 (JWT / SESSION) J : S |
|
25 |
+ private String rgtr; // 등록자 ID |
|
26 | 26 |
private String regDt; // 등록일시 |
27 | 27 |
}(파일 끝에 줄바꿈 문자 없음) |
--- src/main/java/com/takensoft/cms/loginPolicy/vo/LoginPolicyVO.java
+++ src/main/java/com/takensoft/cms/loginPolicy/vo/LoginPolicyVO.java
... | ... | @@ -21,7 +21,7 @@ |
21 | 21 |
public class LoginPolicyVO { |
22 | 22 |
|
23 | 23 |
private String policyId; // 중복로그인 ID |
24 |
- private boolean allowMultipleLogin; // 중복 로그인 허용 여부 |
|
24 |
+ private String allowMultipleLogin; // 중복 로그인 허용 여부 |
|
25 | 25 |
private String rgtr; // 수정한 관리자 ID |
26 | 26 |
private String regDt; // 등록일시 |
27 | 27 |
}(파일 끝에 줄바꿈 문자 없음) |
--- src/main/java/com/takensoft/cms/loginPolicy/web/LoginPolicyController.java
+++ src/main/java/com/takensoft/cms/loginPolicy/web/LoginPolicyController.java
... | ... | @@ -4,17 +4,19 @@ |
4 | 4 |
import com.takensoft.cms.loginPolicy.service.LoginPolicyService; |
5 | 5 |
import com.takensoft.cms.loginPolicy.vo.LoginModeVO; |
6 | 6 |
import com.takensoft.cms.loginPolicy.vo.LoginPolicyVO; |
7 |
+import com.takensoft.cms.token.service.RefreshTokenService; |
|
7 | 8 |
import com.takensoft.common.message.MessageCode; |
8 | 9 |
import com.takensoft.common.util.JWTUtil; |
9 | 10 |
import com.takensoft.common.util.ResponseUtil; |
11 |
+import com.takensoft.common.util.SessionUtil; |
|
10 | 12 |
import jakarta.servlet.http.HttpServletRequest; |
11 | 13 |
import lombok.RequiredArgsConstructor; |
12 | 14 |
import lombok.extern.slf4j.Slf4j; |
13 | 15 |
import org.springframework.dao.DuplicateKeyException; |
16 |
+import org.springframework.data.redis.core.RedisTemplate; |
|
14 | 17 |
import org.springframework.http.ResponseEntity; |
15 | 18 |
import org.springframework.web.bind.annotation.*; |
16 | 19 |
|
17 |
-import java.util.Map; |
|
18 | 20 |
|
19 | 21 |
/** |
20 | 22 |
* @author 김혜민 |
... | ... | @@ -23,7 +25,7 @@ |
23 | 25 |
* since | author | description |
24 | 26 |
* 2025.03.22 | 김혜민 | 최초 등록 |
25 | 27 |
* |
26 |
- * 중복로그인 허용 관련 컨트롤러 |
|
28 |
+ * 로그인정책 관련 컨트롤러 |
|
27 | 29 |
*/ |
28 | 30 |
@RestController |
29 | 31 |
@RequiredArgsConstructor |
... | ... | @@ -35,6 +37,9 @@ |
35 | 37 |
private final LoginModeService loginModeService; |
36 | 38 |
private final ResponseUtil resUtil; |
37 | 39 |
private final JWTUtil jwtUtil; |
40 |
+ private final SessionUtil sessionUtil; |
|
41 |
+ private final RedisTemplate<String, String> redisTemplate; |
|
42 |
+ private final RefreshTokenService refreshTokenService; |
|
38 | 43 |
|
39 | 44 |
/** |
40 | 45 |
* |
... | ... | @@ -42,23 +47,21 @@ |
42 | 47 |
* |
43 | 48 |
* 중복로그인 조회 |
44 | 49 |
*/ |
45 |
- @GetMapping("/getLoginPolicy.json") |
|
50 |
+ @PostMapping(value ="/getLoginPolicy.json") |
|
46 | 51 |
public ResponseEntity<?> getLoginPolicy() { |
47 |
- Boolean isAllowed = loginPolicyService.getPolicy(); |
|
48 |
- return resUtil.successRes(isAllowed, MessageCode.COMMON_SUCCESS); |
|
52 |
+ Boolean result = loginPolicyService.getPolicy(); |
|
53 |
+ return resUtil.successRes(result, MessageCode.COMMON_SUCCESS); |
|
49 | 54 |
} |
50 | 55 |
|
51 | 56 |
/** |
52 |
- * @param params - 중복로그인 정보 |
|
57 |
+ * @param loginPolicyVO - 중복로그인 정보 |
|
53 | 58 |
* @return ResponseEntity - 중복로그인 결과를 포함하는 응답 |
54 | 59 |
* |
55 | 60 |
* 중복로그인 수정 |
56 | 61 |
*/ |
57 |
- @PostMapping("/saveLoginPolicy.json") |
|
58 |
- public ResponseEntity<?> saveLoginPolicy(@RequestBody Map<String, Object> params, HttpServletRequest request) { |
|
62 |
+ @PostMapping(value ="/saveLoginPolicy.json") |
|
63 |
+ public ResponseEntity<?> saveLoginPolicy(@RequestBody LoginPolicyVO loginPolicyVO, HttpServletRequest request) { |
|
59 | 64 |
try { |
60 |
- boolean allow = (Boolean) params.get("allowMultipleLogin"); |
|
61 |
- |
|
62 | 65 |
String token = request.getHeader("Authorization"); |
63 | 66 |
String mbrId = (String) jwtUtil.getClaim(token, "mbrId"); |
64 | 67 |
|
... | ... | @@ -66,8 +69,6 @@ |
66 | 69 |
return resUtil.errorRes(MessageCode.COMMON_BAD_REQUEST); |
67 | 70 |
} |
68 | 71 |
|
69 |
- LoginPolicyVO loginPolicyVO = new LoginPolicyVO(); |
|
70 |
- loginPolicyVO.setAllowMultipleLogin(allow); |
|
71 | 72 |
loginPolicyVO.setRgtr(mbrId); |
72 | 73 |
|
73 | 74 |
int result = loginPolicyService.insertPolicy(loginPolicyVO); |
... | ... | @@ -91,7 +92,7 @@ |
91 | 92 |
* |
92 | 93 |
* 로그인 방식 조회 |
93 | 94 |
*/ |
94 |
- @GetMapping("/getLoginMode.json") |
|
95 |
+ @PostMapping(value ="/getLoginMode.json") |
|
95 | 96 |
public Object getLoginMode() { |
96 | 97 |
String loginMode = loginModeService.getLoginMode(); |
97 | 98 |
return resUtil.successRes(loginMode, MessageCode.COMMON_SUCCESS); |
... | ... | @@ -103,24 +104,29 @@ |
103 | 104 |
* |
104 | 105 |
* 로그인 방식 저장 |
105 | 106 |
*/ |
106 |
- @PostMapping("/saveLoginMode.json") |
|
107 |
- public ResponseEntity<?> saveLoginMode(@RequestBody Map<String, Object> params, HttpServletRequest request) { |
|
107 |
+ @PostMapping(value ="/saveLoginMode.json") |
|
108 |
+ public ResponseEntity<?> saveLoginMode(@RequestBody LoginModeVO loginModeVO, HttpServletRequest request) { |
|
108 | 109 |
try { |
109 |
- String lgnMode = params.get("lgnMode").toString(); |
|
110 |
- |
|
111 | 110 |
String token = request.getHeader("Authorization"); |
112 | 111 |
String mbrId = (String) jwtUtil.getClaim(token, "mbrId"); |
113 | 112 |
|
114 | 113 |
if (mbrId == null || mbrId.isBlank()) { |
115 | 114 |
return resUtil.errorRes(MessageCode.COMMON_BAD_REQUEST); |
116 | 115 |
} |
117 |
- |
|
118 |
- LoginModeVO loginModeVO = new LoginModeVO(); |
|
119 |
- loginModeVO.setLgnMode(lgnMode); |
|
120 |
- loginModeVO.setRgtrId(mbrId); |
|
116 |
+ loginModeVO.setRgtr(mbrId); |
|
121 | 117 |
|
122 | 118 |
int result = loginModeService.insertLoginMode(loginModeVO); |
123 | 119 |
|
120 |
+ /* if (loginModeVO.getLgnMode().equals("J")) { |
|
121 |
+ // JWT 전체 로그아웃 |
|
122 |
+ Set<String> keys = redisTemplate.keys("jwt:*"); |
|
123 |
+ if (keys != null && !keys.isEmpty()) redisTemplate.delete(keys); |
|
124 |
+ refreshTokenService.deleteAll(); |
|
125 |
+ } else { |
|
126 |
+ // 세션 전체 로그아웃 |
|
127 |
+ sessionUtil.invalidateAllSessions(); |
|
128 |
+ }*/ |
|
129 |
+ |
|
124 | 130 |
if (result > 0) { |
125 | 131 |
return resUtil.successRes(result, MessageCode.COMMON_SUCCESS); |
126 | 132 |
} else { |
--- src/main/java/com/takensoft/cms/token/dao/RefreshTokenDAO.java
+++ src/main/java/com/takensoft/cms/token/dao/RefreshTokenDAO.java
... | ... | @@ -46,4 +46,12 @@ |
46 | 46 |
* refresh token 등록 여부 확인 |
47 | 47 |
*/ |
48 | 48 |
boolean findByCheckRefresh(RefreshTknVO refreshTknVO); |
49 |
+ |
|
50 |
+ /** |
|
51 |
+ * @return int - refresh token 전체 삭제 여부 |
|
52 |
+ * |
|
53 |
+ * refresh token 전체 삭제 |
|
54 |
+ */ |
|
55 |
+ int deleteAll(); |
|
56 |
+ |
|
49 | 57 |
} |
--- src/main/java/com/takensoft/cms/token/service/RefreshTokenService.java
+++ src/main/java/com/takensoft/cms/token/service/RefreshTokenService.java
... | ... | @@ -62,4 +62,11 @@ |
62 | 62 |
* refresh token 등록 여부 확인 |
63 | 63 |
*/ |
64 | 64 |
public boolean findByCheckRefresh(HttpServletRequest req, RefreshTknVO refreshTknVO); |
65 |
+ |
|
66 |
+ /** |
|
67 |
+ * @return int - refresh token 전체 삭제 여부 |
|
68 |
+ * |
|
69 |
+ * refresh token 전체 삭제 |
|
70 |
+ */ |
|
71 |
+ public int deleteAll(); |
|
65 | 72 |
} |
--- src/main/java/com/takensoft/cms/token/service/impl/RefreshTokenServiceImpl.java
+++ src/main/java/com/takensoft/cms/token/service/impl/RefreshTokenServiceImpl.java
... | ... | @@ -23,10 +23,7 @@ |
23 | 23 |
import jakarta.servlet.http.HttpServletRequest; |
24 | 24 |
import jakarta.servlet.http.HttpServletResponse; |
25 | 25 |
|
26 |
-import java.util.Date; |
|
27 |
-import java.util.HashMap; |
|
28 |
-import java.util.List; |
|
29 |
-import java.util.Map; |
|
26 |
+import java.util.*; |
|
30 | 27 |
|
31 | 28 |
/** |
32 | 29 |
* @author takensoft |
... | ... | @@ -265,4 +262,21 @@ |
265 | 262 |
} |
266 | 263 |
} |
267 | 264 |
|
265 |
+ /** |
|
266 |
+ * @return int - refresh token 전체 삭제 여부 |
|
267 |
+ * |
|
268 |
+ * refresh token 전체 삭제 |
|
269 |
+ */ |
|
270 |
+ @Override |
|
271 |
+ public int deleteAll() { |
|
272 |
+ // JWT 방식이면서 중복 로그인 비허용인 경우 redis도 정리 |
|
273 |
+ /*if (!loginPolicyService.getPolicy()) { |
|
274 |
+ Set<String> keys = redisTemplate.keys("jwt:*"); |
|
275 |
+ if (keys != null && !keys.isEmpty()) { |
|
276 |
+ redisTemplate.delete(keys); |
|
277 |
+ } |
|
278 |
+ }*/ |
|
279 |
+ return refreshTokenDAO.deleteAll(); // DB에서 리프레시 토큰 전부 삭제 |
|
280 |
+ } |
|
281 |
+ |
|
268 | 282 |
} |
--- src/main/java/com/takensoft/cms/token/web/RefreshTokenController.java
+++ src/main/java/com/takensoft/cms/token/web/RefreshTokenController.java
... | ... | @@ -61,20 +61,20 @@ |
61 | 61 |
if (auth != null && auth.getPrincipal() instanceof MberVO) { |
62 | 62 |
MberVO mber = (MberVO) auth.getPrincipal(); |
63 | 63 |
String mbrId = mber.getMbrId(); |
64 |
- String loginType = loginModeService.getLoginMode(); // "J" or "S" |
|
64 |
+ String loginType = loginModeService.getLoginMode(); // J or S |
|
65 | 65 |
|
66 |
- // ✅ Refresh 토큰 삭제 (DB) |
|
66 |
+ // Refresh 토큰 삭제 (DB) |
|
67 | 67 |
RefreshTknVO refresh = new RefreshTknVO(); |
68 | 68 |
refresh.setMbrId(mbrId); |
69 | 69 |
int result = refreshTokenService.delete(req, refresh); |
70 | 70 |
|
71 | 71 |
if ("S".equals(loginType)) { |
72 |
- // ✅ 세션 방식: 세션 만료 + SessionMap에서 제거 |
|
72 |
+ // 세션 방식: 세션 만료 + SessionMap에서 제거 |
|
73 | 73 |
HttpSession session = req.getSession(false); |
74 | 74 |
if (session != null) session.invalidate(); |
75 | 75 |
sessionUtil.removeSession(mbrId); |
76 | 76 |
} else { |
77 |
- // ✅ JWT 방식: Redis에서 삭제 |
|
77 |
+ // JWT 방식: Redis에서 삭제 |
|
78 | 78 |
if (!loginPolicyService.getPolicy()) { |
79 | 79 |
redisTemplate.delete("jwt:" + mbrId); |
80 | 80 |
} |
--- src/main/java/com/takensoft/common/config/SecurityConfig.java
+++ src/main/java/com/takensoft/common/config/SecurityConfig.java
... | ... | @@ -170,12 +170,12 @@ |
170 | 170 |
); |
171 | 171 |
|
172 | 172 |
// 로그인 방식에 따라 필터 적용 (JWT or 세션) |
173 |
- if ("S".equals(loginModeService.getLoginMode())) { |
|
173 |
+ /* if ("S".equals(loginModeService.getLoginMode())) { |
|
174 | 174 |
http.addFilterBefore(new SessionAuthFilter(jwtUtil, redisTemplate, loginPolicyService), LoginFilter.class); |
175 | 175 |
} else { |
176 |
- http.addFilterBefore(new JWTFilter(jwtUtil, appConfig, loginPolicyService, redisTemplate), LoginFilter.class); |
|
177 |
- } |
|
178 | 176 |
|
177 |
+ }*/ |
|
178 |
+ http.addFilterBefore(new JWTFilter(jwtUtil, appConfig, loginPolicyService, redisTemplate), LoginFilter.class); |
|
179 | 179 |
http.addFilterBefore(new AccesFilter(accesCtrlService, httpRequestUtil, appConfig), JWTFilter.class); // 아이피 검증 |
180 | 180 |
http.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, refreshTokenService, lgnHstryService, httpRequestUtil, |
181 | 181 |
loginModeService, loginPolicyService, sessionUtil, JWT_ACCESSTIME, JWT_REFRESHTIME, COOKIE_TIME, redisTemplate), UsernamePasswordAuthenticationFilter.class); // 로그인 필터 |
--- src/main/java/com/takensoft/common/filter/LoginFilter.java
+++ src/main/java/com/takensoft/common/filter/LoginFilter.java
... | ... | @@ -18,6 +18,7 @@ |
18 | 18 |
import lombok.SneakyThrows; |
19 | 19 |
import org.springframework.beans.factory.annotation.Value; |
20 | 20 |
import org.springframework.data.redis.core.RedisTemplate; |
21 |
+import org.springframework.http.HttpStatus; |
|
21 | 22 |
import org.springframework.security.authentication.AuthenticationManager; |
22 | 23 |
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
23 | 24 |
import org.springframework.security.core.Authentication; |
... | ... | @@ -30,7 +31,9 @@ |
30 | 31 |
import jakarta.servlet.http.HttpServletRequest; |
31 | 32 |
import jakarta.servlet.http.HttpServletResponse; |
32 | 33 |
import java.io.IOException; |
34 |
+import java.util.HashMap; |
|
33 | 35 |
import java.util.List; |
36 |
+import java.util.Map; |
|
34 | 37 |
import java.util.concurrent.TimeUnit; |
35 | 38 |
|
36 | 39 |
/** |
... | ... | @@ -168,7 +171,16 @@ |
168 | 171 |
if (!loginPolicyService.getPolicy()) { |
169 | 172 |
sessionUtil.registerSession(mber.getMbrId(), session); |
170 | 173 |
} |
174 |
+ Map<String, Object> result = new HashMap<>(); |
|
175 |
+ result.put("mbrId", mber.getMbrId()); |
|
176 |
+ result.put("mbrNm", mber.getMbrNm()); |
|
177 |
+ result.put("roles", mber.getAuthorList()); |
|
171 | 178 |
|
179 |
+ res.setContentType("application/json;charset=UTF-8"); |
|
180 |
+ res.setStatus(HttpStatus.OK.value()); |
|
181 |
+ |
|
182 |
+ ObjectMapper mapper = new ObjectMapper(); |
|
183 |
+ res.getOutputStream().write(mapper.writeValueAsBytes(result)); |
|
172 | 184 |
} else { |
173 | 185 |
res.setHeader("Authorization", accessToken); |
174 | 186 |
res.addCookie(jwtUtil.createCookie("refresh", refreshToken, COOKIE_TIME)); |
--- src/main/java/com/takensoft/common/util/SessionUtil.java
+++ src/main/java/com/takensoft/common/util/SessionUtil.java
... | ... | @@ -3,22 +3,22 @@ |
3 | 3 |
import jakarta.servlet.http.HttpSession; |
4 | 4 |
import org.springframework.stereotype.Component; |
5 | 5 |
|
6 |
-import java.util.HashMap; |
|
7 | 6 |
import java.util.Map; |
7 |
+import java.util.concurrent.ConcurrentHashMap; |
|
8 | 8 |
|
9 | 9 |
/** |
10 | 10 |
* @author : takensoft |
11 |
- * @since : 2025.01.22 |
|
11 |
+ * @since : 2025.03.21 |
|
12 | 12 |
* @modification |
13 | 13 |
* since | author | description |
14 |
- * 2025.01.22 | takensoft | 최초 등록 |
|
14 |
+ * 2025.03.21 | takensoft | 최초 등록 |
|
15 | 15 |
* |
16 |
- * 중복로그인, 로그인 방식 등의 유틸리티 |
|
16 |
+ * 세션 로그인 방식의 유틸리티 |
|
17 | 17 |
*/ |
18 | 18 |
@Component |
19 | 19 |
public class SessionUtil { |
20 | 20 |
|
21 |
- private final Map<String, HttpSession> sessionMap = new HashMap<>(); |
|
21 |
+ private final Map<String, HttpSession> sessionMap = new ConcurrentHashMap<>(); |
|
22 | 22 |
|
23 | 23 |
public synchronized void registerSession(String mbrId, HttpSession newSession) { |
24 | 24 |
// 기존 세션 있으면 강제 로그아웃 |
... | ... | @@ -28,11 +28,18 @@ |
28 | 28 |
} |
29 | 29 |
sessionMap.put(mbrId, newSession); |
30 | 30 |
} |
31 |
- |
|
31 |
+ //로그아웃처리 |
|
32 | 32 |
public void removeSession(String mbrId) { |
33 | 33 |
sessionMap.remove(mbrId); |
34 | 34 |
} |
35 |
- |
|
36 |
- |
|
35 |
+ //전체 로그아웃 처리 |
|
36 |
+ public void invalidateAllSessions() { |
|
37 |
+ for (HttpSession session : sessionMap.values()) { |
|
38 |
+ if (session != null) { |
|
39 |
+ session.invalidate(); |
|
40 |
+ } |
|
41 |
+ } |
|
42 |
+ sessionMap.clear(); // 전체 초기화 |
|
43 |
+ } |
|
37 | 44 |
|
38 | 45 |
} |
--- src/main/resources/mybatis/mapper/loginPolicy/loginMode-SQL.xml
+++ src/main/resources/mybatis/mapper/loginPolicy/loginMode-SQL.xml
... | ... | @@ -28,12 +28,12 @@ |
28 | 28 |
INSERT INTO lgn_mode_hstry ( |
29 | 29 |
lgn_mode_id, |
30 | 30 |
lgn_mode, |
31 |
- rgtr_id, |
|
31 |
+ rgtr, |
|
32 | 32 |
reg_dt |
33 | 33 |
) VALUES ( |
34 | 34 |
#{lgnModeId}, |
35 | 35 |
#{lgnMode}, |
36 |
- #{rgtrId}, |
|
36 |
+ #{rgtr}, |
|
37 | 37 |
NOW() |
38 | 38 |
) |
39 | 39 |
</insert> |
--- src/main/resources/mybatis/mapper/mber/refresh-SQL.xml
+++ src/main/resources/mybatis/mapper/mber/refresh-SQL.xml
... | ... | @@ -62,4 +62,12 @@ |
62 | 62 |
AND use_ip = #{useIp} |
63 | 63 |
) AS result |
64 | 64 |
</select> |
65 |
+ <!-- |
|
66 |
+ 작성자 : 김혜민 |
|
67 |
+ 작성일 : 2025.03.27 |
|
68 |
+ 내 용 : refresh token 전체 삭제 |
|
69 |
+ --> |
|
70 |
+ <delete id="deleteAll"> |
|
71 |
+ DELETE FROM mbr_refresh |
|
72 |
+ </delete> |
|
65 | 73 |
</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?