

250528 김혜민 oauth2 일반회원 로그인 처리
@96e622e04f01747632fb07b7376bc02e4fadd381
--- src/main/java/com/takensoft/cms/mber/service/Impl/MberServiceImpl.java
+++ src/main/java/com/takensoft/cms/mber/service/Impl/MberServiceImpl.java
... | ... | @@ -133,13 +133,12 @@ |
133 | 133 |
if(joinDTO.getTelno() != null && !joinDTO.getTelno().equals("")) { |
134 | 134 |
joinDTO.setTelno(Secret.encrypt(joinDTO.getTelno())); |
135 | 135 |
} |
136 |
- |
|
137 |
- // 아이피 조회 및 등록 |
|
138 |
- if (req != null) { |
|
139 |
- joinDTO.setFrstRegIp(httpRequestUtil.getIp(req)); |
|
140 |
- } else { |
|
141 |
- joinDTO.setFrstRegIp("0.0.0.0"); // OAuth2의 경우 기본값 |
|
136 |
+ //멤버타입 없을시 default "S" 고정 |
|
137 |
+ if (joinDTO.getMbrType() == null || joinDTO.getMbrType().isEmpty()) { |
|
138 |
+ joinDTO.setMbrType("S"); |
|
142 | 139 |
} |
140 |
+ // 아이피 조회 및 등록 |
|
141 |
+ joinDTO.setFrstRegIp(httpRequestUtil.getIp(req)); |
|
143 | 142 |
|
144 | 143 |
// 등록된 토큰에서 사용자 정보 조회 |
145 | 144 |
String writer = joinDTO.getRgtr(); |
... | ... | @@ -344,13 +343,13 @@ |
344 | 343 |
*/ |
345 | 344 |
@Override |
346 | 345 |
@Transactional(rollbackFor = Exception.class) |
347 |
- public MberVO saveOAuthUser(MberVO user) { |
|
346 |
+ public MberVO saveOAuthUser(MberVO user, HttpServletRequest request) { |
|
348 | 347 |
try { |
349 | 348 |
// OAuth2 사용자를 JoinDTO로 변환하여 기존 검증된 로직 활용 |
350 | 349 |
JoinDTO oauthJoinDTO = createOAuthJoinDTO(user); |
351 | 350 |
|
352 | 351 |
// 기존 userJoin 메서드 활용 (검증된 로직) |
353 |
- HashMap<String, Object> result = userJoin(null, oauthJoinDTO); |
|
352 |
+ HashMap<String, Object> result = userJoin(request, oauthJoinDTO); |
|
354 | 353 |
|
355 | 354 |
// 생성된 회원 ID 설정 |
356 | 355 |
user.setMbrId(result.get("mbrId").toString()); |
--- src/main/java/com/takensoft/cms/mber/service/MberService.java
+++ src/main/java/com/takensoft/cms/mber/service/MberService.java
... | ... | @@ -86,7 +86,7 @@ |
86 | 86 |
* |
87 | 87 |
* OAuth2 사용자 저장 |
88 | 88 |
*/ |
89 |
- public MberVO saveOAuthUser(MberVO user); |
|
89 |
+ public MberVO saveOAuthUser(MberVO user, HttpServletRequest request); |
|
90 | 90 |
|
91 | 91 |
/** |
92 | 92 |
* @param user - OAuth2 사용자 정보 |
--- src/main/java/com/takensoft/common/config/SecurityConfig.java
+++ src/main/java/com/takensoft/common/config/SecurityConfig.java
... | ... | @@ -162,7 +162,7 @@ |
162 | 162 |
); |
163 | 163 |
|
164 | 164 |
http.authorizeHttpRequests((auth) -> auth |
165 |
- .requestMatchers("/", "/mbr/**", "/refresh/**", "/sys/**", "/editFileUpload/**", "/fileUpload/**", "/oauth2/**", "/login/oauth2/**", "/.well-known/**").permitAll() // 회원, 토큰, 시스템 제공, 파일, OAuth2 접근 모두 허용 |
|
165 |
+ .requestMatchers("/", "/mbr/**", "/refresh/**", "/sys/**", "/editFileUpload/**", "/fileUpload/**", "/oauth2/**", "/login/oauth2/**").permitAll() // 회원, 토큰, 시스템 제공, 파일, OAuth2 접근 모두 허용 |
|
166 | 166 |
.requestMatchers("/admin/**").hasRole("ADMIN") // 관리자 페이지는 ADMIN 권한을 가진 사용자만 접근 가능 |
167 | 167 |
.anyRequest().authenticated() // 그 외에는 로그인한 사용자만 접근 가능 |
168 | 168 |
); |
--- src/main/java/com/takensoft/common/oauth/handler/OAuth2AuthenticationFailureHandler.java
+++ src/main/java/com/takensoft/common/oauth/handler/OAuth2AuthenticationFailureHandler.java
... | ... | @@ -35,8 +35,6 @@ |
35 | 35 |
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, |
36 | 36 |
AuthenticationException exception) throws IOException, ServletException { |
37 | 37 |
|
38 |
- log.error("OAuth2 로그인 실패: {}", exception.getMessage()); |
|
39 |
- |
|
40 | 38 |
String errorMessage = mapErrorMessage(exception); |
41 | 39 |
String encodedMessage = URLEncoder.encode(errorMessage, StandardCharsets.UTF_8); |
42 | 40 |
|
--- src/main/java/com/takensoft/common/oauth/handler/OAuth2AuthenticationSuccessHandler.java
+++ src/main/java/com/takensoft/common/oauth/handler/OAuth2AuthenticationSuccessHandler.java
... | ... | @@ -1,176 +1,247 @@ |
1 | 1 |
package com.takensoft.common.oauth.handler; |
2 | 2 |
|
3 |
+import com.fasterxml.jackson.core.JsonProcessingException; |
|
4 |
+import com.fasterxml.jackson.databind.ObjectMapper; |
|
3 | 5 |
import com.takensoft.cms.loginPolicy.service.LoginModeService; |
4 | 6 |
import com.takensoft.cms.loginPolicy.service.LoginPolicyService; |
5 | 7 |
import com.takensoft.cms.mber.service.LgnHstryService; |
8 |
+import com.takensoft.cms.mber.service.MberService; |
|
6 | 9 |
import com.takensoft.cms.mber.vo.LgnHstryVO; |
10 |
+import com.takensoft.cms.mber.vo.MberAuthorVO; |
|
11 |
+import com.takensoft.cms.mber.vo.MberVO; |
|
12 |
+import com.takensoft.cms.token.service.RefreshTokenService; |
|
13 |
+import com.takensoft.cms.token.vo.RefreshTknVO; |
|
7 | 14 |
import com.takensoft.common.oauth.vo.CustomOAuth2UserVO; |
8 | 15 |
import com.takensoft.common.util.HttpRequestUtil; |
9 | 16 |
import com.takensoft.common.util.JWTUtil; |
10 | 17 |
import com.takensoft.common.util.SessionUtil; |
11 | 18 |
import jakarta.servlet.ServletException; |
19 |
+import jakarta.servlet.http.Cookie; |
|
12 | 20 |
import jakarta.servlet.http.HttpServletRequest; |
13 | 21 |
import jakarta.servlet.http.HttpServletResponse; |
14 | 22 |
import jakarta.servlet.http.HttpSession; |
15 | 23 |
import lombok.RequiredArgsConstructor; |
16 | 24 |
import lombok.extern.slf4j.Slf4j; |
17 | 25 |
import org.springframework.beans.factory.annotation.Value; |
18 |
-import org.springframework.context.ApplicationEventPublisher; |
|
26 |
+import org.springframework.context.ApplicationContext; |
|
19 | 27 |
import org.springframework.data.redis.core.RedisTemplate; |
20 | 28 |
import org.springframework.security.core.Authentication; |
21 | 29 |
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; |
22 | 30 |
import org.springframework.stereotype.Component; |
23 | 31 |
|
24 | 32 |
import java.io.IOException; |
33 |
+import java.net.URLEncoder; |
|
34 |
+import java.util.*; |
|
25 | 35 |
import java.util.concurrent.TimeUnit; |
26 | 36 |
|
27 |
-/** |
|
28 |
- * @author takensoft |
|
29 |
- * @since 2025.05.22 |
|
30 |
- * @modification |
|
31 |
- * since | author | description |
|
32 |
- * 2025.05.22 | takensoft | 최초 등록 |
|
33 |
- * |
|
34 |
- * OAuth2 로그인 성공 핸들러 |
|
35 |
- */ |
|
36 | 37 |
@Slf4j |
37 | 38 |
@Component |
38 | 39 |
@RequiredArgsConstructor |
39 | 40 |
public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { |
40 | 41 |
|
42 |
+ private final ApplicationContext applicationContext; |
|
43 |
+ private final JWTUtil jwtUtil; |
|
44 |
+ private final RefreshTokenService refreshTokenService; |
|
41 | 45 |
private final LgnHstryService lgnHstryService; |
42 | 46 |
private final HttpRequestUtil httpRequestUtil; |
43 | 47 |
private final LoginModeService loginModeService; |
44 | 48 |
private final LoginPolicyService loginPolicyService; |
45 | 49 |
private final SessionUtil sessionUtil; |
46 |
- private final JWTUtil jwtUtil; |
|
47 | 50 |
private final RedisTemplate<String, String> redisTemplate; |
48 |
- private final ApplicationEventPublisher eventPublisher; |
|
49 | 51 |
|
50 | 52 |
@Value("${jwt.accessTime}") |
51 |
- private long JWT_ACCESSTIME; |
|
53 |
+ private long jwtAccessTime; |
|
54 |
+ |
|
55 |
+ @Value("${jwt.refreshTime}") |
|
56 |
+ private long jwtRefreshTime; |
|
57 |
+ |
|
58 |
+ @Value("${cookie.time}") |
|
59 |
+ private int cookieTime; |
|
52 | 60 |
|
53 | 61 |
@Value("${front.url}") |
54 |
- private String FRONT_URL; |
|
62 |
+ private String frontUrl; |
|
55 | 63 |
|
56 | 64 |
@Override |
57 |
- public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, |
|
58 |
- Authentication authentication) throws IOException, ServletException { |
|
65 |
+ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { |
|
59 | 66 |
|
60 | 67 |
CustomOAuth2UserVO oAuth2User = (CustomOAuth2UserVO) authentication.getPrincipal(); |
61 | 68 |
|
62 | 69 |
try { |
63 |
- log.info("OAuth2 로그인 성공 - Provider: {}, Email: {}", |
|
64 |
- oAuth2User.getProvider(), oAuth2User.getEmail()); |
|
65 | 70 |
|
66 |
- // 1. 비동기로 사용자 정보 저장/업데이트 처리 |
|
67 |
- eventPublisher.publishEvent(new OAuth2UserSaveEvent(oAuth2User)); |
|
71 |
+ // MberService를 ApplicationContext에서 가져옴 |
|
72 |
+ MberService mberService = applicationContext.getBean(MberService.class); |
|
68 | 73 |
|
69 |
- // 2. 로그인 이력 저장 |
|
70 |
- saveLoginHistory(request, oAuth2User); |
|
74 |
+ // OAuth2 사용자 정보로 MberVO 생성 또는 조회 |
|
75 |
+ MberVO mber = processOAuth2User(oAuth2User, mberService, request); |
|
71 | 76 |
|
72 |
- // 3. 로그인 모드에 따른 토큰/세션 처리 |
|
77 |
+ // 로그인 이력 저장 |
|
78 |
+ saveLoginHistory(request, mber); |
|
79 |
+ |
|
80 |
+ // 로그인 모드 확인 |
|
73 | 81 |
String loginMode = loginModeService.getLoginMode(); |
74 |
- String tempUserId = createTempUserId(oAuth2User); |
|
75 |
- processLoginByMode(request, response, oAuth2User, tempUserId, loginMode); |
|
76 | 82 |
|
77 |
- // 4. 캐시 방지 헤더 설정 |
|
78 |
- response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); |
|
79 |
- response.setHeader("Pragma", "no-cache"); |
|
80 |
- response.setHeader("Expires", "0"); |
|
83 |
+ // 로그인 모드에 따른 처리 |
|
84 |
+ if ("S".equals(loginMode)) { |
|
85 |
+ handleSessionMode(request, response, mber); |
|
86 |
+ } else { |
|
87 |
+ handleJwtMode(request, response, mber); |
|
88 |
+ } |
|
81 | 89 |
|
82 |
- // 5. 프론트엔드 로그인 페이지로 리다이렉트 (OAuth 성공 파라미터 + 타임스탬프) |
|
83 |
- long timestamp = System.currentTimeMillis(); |
|
84 |
- String redirectUrl = FRONT_URL + "/login.page?oauth_success=true&t=" + timestamp; |
|
90 |
+ // 프론트엔드로 리다이렉트 |
|
91 |
+ String redirectUrl = frontUrl + "/login.page?oauth_success=true&loginMode=" + loginMode; |
|
92 |
+ |
|
85 | 93 |
getRedirectStrategy().sendRedirect(request, response, redirectUrl); |
86 | 94 |
|
87 | 95 |
} catch (Exception e) { |
88 |
- log.error("OAuth2 로그인 처리 중 오류 발생", e); |
|
89 |
- String errorUrl = FRONT_URL + "/login.page?error=oauth2_processing_failed"; |
|
90 |
- getRedirectStrategy().sendRedirect(request, response, errorUrl); |
|
96 |
+ e.printStackTrace(); |
|
97 |
+ handleOAuth2Error(response, e); |
|
91 | 98 |
} |
92 | 99 |
} |
93 | 100 |
|
94 | 101 |
/** |
95 |
- * 로그인 이력 저장 |
|
102 |
+ * JWT 모드 처리 - OAuth용 access token 쿠키 추가 |
|
96 | 103 |
*/ |
97 |
- private void saveLoginHistory(HttpServletRequest request, CustomOAuth2UserVO oAuth2User) { |
|
104 |
+ private void handleJwtMode(HttpServletRequest request, HttpServletResponse response, MberVO mber) throws IOException { |
|
98 | 105 |
try { |
99 |
- LgnHstryVO lgnHstryVO = new LgnHstryVO(); |
|
100 |
- lgnHstryVO.setLgnId(oAuth2User.getEmail()); |
|
101 |
- lgnHstryVO.setLgnType("1"); // 일반 사용자 |
|
102 |
- lgnHstryVO.setCntnIp(httpRequestUtil.getIp(request)); |
|
103 |
- lgnHstryVO.setCntnOperSys(httpRequestUtil.getOS(httpRequestUtil.getUserAgent(request))); |
|
104 |
- lgnHstryVO.setDeviceNm(httpRequestUtil.getDevice(httpRequestUtil.getUserAgent(request))); |
|
105 |
- lgnHstryVO.setBrwsrNm(httpRequestUtil.getBrowser(httpRequestUtil.getUserAgent(request))); |
|
106 |
+ // JWT 토큰 생성 |
|
107 |
+ String accessToken = jwtUtil.createJwt("Authorization", |
|
108 |
+ mber.getMbrId(), |
|
109 |
+ mber.getLgnId(), |
|
110 |
+ mber.getMbrNm(), |
|
111 |
+ (List) mber.getAuthorities(), |
|
112 |
+ jwtAccessTime); |
|
106 | 113 |
|
107 |
- lgnHstryService.LgnHstrySave(lgnHstryVO); |
|
114 |
+ String refreshToken = jwtUtil.createJwt("refresh", |
|
115 |
+ mber.getMbrId(), |
|
116 |
+ mber.getLgnId(), |
|
117 |
+ mber.getMbrNm(), |
|
118 |
+ (List) mber.getAuthorities(), |
|
119 |
+ jwtRefreshTime); |
|
120 |
+ // Refresh 토큰 처리 |
|
121 |
+ RefreshTknVO refresh = new RefreshTknVO(); |
|
122 |
+ refresh.setMbrId(mber.getMbrId()); |
|
123 |
+ |
|
124 |
+ if (refreshTokenService.findByCheckRefresh(request, refresh)) { |
|
125 |
+ refreshTokenService.delete(request, refresh); |
|
126 |
+ } |
|
127 |
+ |
|
128 |
+ refresh.setToken(refreshToken); |
|
129 |
+ |
|
130 |
+ // 헤더와 쿠키 설정 |
|
131 |
+ response.setHeader("Authorization", accessToken); |
|
132 |
+ // OAuth 전용 access token 쿠키 생성 |
|
133 |
+ Cookie oauthAccessCookie = new Cookie("oauth_access_token", accessToken); |
|
134 |
+ oauthAccessCookie.setPath("/"); |
|
135 |
+ oauthAccessCookie.setMaxAge(300); // 5분 후 자동 삭제 |
|
136 |
+ oauthAccessCookie.setHttpOnly(false); // 프론트에서 접근 가능하도록 |
|
137 |
+ |
|
138 |
+ response.addCookie(oauthAccessCookie); |
|
139 |
+ |
|
140 |
+ // Refresh 쿠키 생성 |
|
141 |
+ Cookie refreshCookie = jwtUtil.createCookie("refresh", refreshToken, cookieTime); |
|
142 |
+ response.addCookie(refreshCookie); |
|
143 |
+ |
|
144 |
+ response.setHeader("login-type", "J"); |
|
145 |
+ |
|
146 |
+ // 중복 로그인 비허용 처리 |
|
147 |
+ if (!loginPolicyService.getPolicy()) { |
|
148 |
+ redisTemplate.delete("jwt:" + mber.getMbrId()); |
|
149 |
+ redisTemplate.opsForValue().set("jwt:" + mber.getMbrId(), accessToken, jwtAccessTime, TimeUnit.MILLISECONDS); |
|
150 |
+ } |
|
151 |
+ |
|
152 |
+ // Refresh 토큰 저장 |
|
153 |
+ refreshTokenService.saveRefreshToken(request, response, refresh, jwtRefreshTime); |
|
154 |
+ |
|
108 | 155 |
} catch (Exception e) { |
109 |
- log.error("OAuth2 로그인 이력 저장 실패", e); |
|
156 |
+ e.printStackTrace(); |
|
157 |
+ throw e; |
|
110 | 158 |
} |
111 |
- } |
|
112 |
- |
|
113 |
- /** |
|
114 |
- * 임시 사용자 ID 생성 |
|
115 |
- */ |
|
116 |
- private String createTempUserId(CustomOAuth2UserVO oAuth2User) { |
|
117 |
- return oAuth2User.getProvider() + "_" + oAuth2User.getId(); |
|
118 |
- } |
|
119 |
- |
|
120 |
- /** |
|
121 |
- * 로그인 모드에 따른 처리 |
|
122 |
- */ |
|
123 |
- private void processLoginByMode(HttpServletRequest request, HttpServletResponse response, |
|
124 |
- CustomOAuth2UserVO oAuth2User, String tempUserId, String loginMode) |
|
125 |
- throws IOException { |
|
126 |
- |
|
127 |
- // JWT 토큰 생성 |
|
128 |
- String accessToken = jwtUtil.createJwt( |
|
129 |
- "Authorization", |
|
130 |
- tempUserId, |
|
131 |
- oAuth2User.getEmail(), |
|
132 |
- oAuth2User.getName(), |
|
133 |
- null, // roles는 DB 저장 후 갱신 예정 |
|
134 |
- JWT_ACCESSTIME |
|
135 |
- ); |
|
136 |
- |
|
137 |
- if ("S".equals(loginMode)) { |
|
138 |
- // 세션 모드 처리 |
|
139 |
- processSessionMode(request, tempUserId, accessToken, oAuth2User); |
|
140 |
- } else { |
|
141 |
- // JWT 모드 처리 (기본값) |
|
142 |
- processJwtMode(response, tempUserId, accessToken); |
|
143 |
- } |
|
144 |
- |
|
145 |
- response.setHeader("login-type", loginMode); |
|
146 | 159 |
} |
147 | 160 |
|
148 | 161 |
/** |
149 | 162 |
* 세션 모드 처리 |
150 | 163 |
*/ |
151 |
- private void processSessionMode(HttpServletRequest request, String tempUserId, |
|
152 |
- String accessToken, CustomOAuth2UserVO oAuth2User) { |
|
153 |
- HttpSession session = request.getSession(true); |
|
154 |
- session.setAttribute("JWT_TOKEN", accessToken); |
|
155 |
- session.setAttribute("oauth2User", oAuth2User); |
|
164 |
+ private void handleSessionMode(HttpServletRequest request, HttpServletResponse response, MberVO mber) { |
|
165 |
+ log.info("세션 모드로 OAuth2 로그인 처리"); |
|
156 | 166 |
|
157 |
- // 중복 로그인 정책 확인 |
|
167 |
+ // 세션 생성 및 정보 저장 |
|
168 |
+ HttpSession session = request.getSession(true); |
|
169 |
+ |
|
170 |
+ // 세션에 사용자 정보 저장 (JWT 없이!) |
|
171 |
+ session.setAttribute("mbrId", mber.getMbrId()); |
|
172 |
+ session.setAttribute("mbrNm", mber.getMbrNm()); |
|
173 |
+ session.setAttribute("lgnId", mber.getLgnId()); |
|
174 |
+ session.setAttribute("roles", mber.getAuthorList()); |
|
175 |
+ session.setAttribute("loginType", "OAUTH2"); |
|
176 |
+ |
|
177 |
+ // 중복 로그인 비허용 처리 |
|
158 | 178 |
if (!loginPolicyService.getPolicy()) { |
159 |
- sessionUtil.registerSession(tempUserId, session); |
|
179 |
+ sessionUtil.registerSession(mber.getMbrId(), session); |
|
180 |
+ } |
|
181 |
+ |
|
182 |
+ response.setHeader("login-type", "S"); |
|
183 |
+ } |
|
184 |
+ |
|
185 |
+ /** |
|
186 |
+ * 로그인 이력 저장 |
|
187 |
+ */ |
|
188 |
+ private void saveLoginHistory(HttpServletRequest request, MberVO mber) { |
|
189 |
+ try { |
|
190 |
+ String userAgent = httpRequestUtil.getUserAgent(request); |
|
191 |
+ |
|
192 |
+ LgnHstryVO loginHistory = new LgnHstryVO(); |
|
193 |
+ loginHistory.setLgnId(mber.getLgnId()); |
|
194 |
+ loginHistory.setLgnType(mber.getAuthorities().stream() |
|
195 |
+ .anyMatch(r -> r.getAuthority().equals("ROLE_ADMIN")) ? "0" : "1"); |
|
196 |
+ loginHistory.setCntnIp(httpRequestUtil.getIp(request)); |
|
197 |
+ loginHistory.setCntnOperSys(httpRequestUtil.getOS(userAgent)); |
|
198 |
+ loginHistory.setDeviceNm(httpRequestUtil.getDevice(userAgent)); |
|
199 |
+ loginHistory.setBrwsrNm(httpRequestUtil.getBrowser(userAgent)); |
|
200 |
+ |
|
201 |
+ lgnHstryService.LgnHstrySave(loginHistory); |
|
202 |
+ } catch (Exception e) { |
|
203 |
+ log.error("로그인 이력 저장 실패", e); |
|
204 |
+ // 로그인 이력 저장 실패해도 로그인은 계속 진행 |
|
160 | 205 |
} |
161 | 206 |
} |
162 | 207 |
|
163 | 208 |
/** |
164 |
- * JWT 모드 처리 |
|
209 |
+ * OAuth2 사용자 처리 |
|
165 | 210 |
*/ |
166 |
- private void processJwtMode(HttpServletResponse response, String tempUserId, String accessToken) { |
|
167 |
- response.setHeader("Authorization", accessToken); |
|
211 |
+ private MberVO processOAuth2User(CustomOAuth2UserVO oAuth2User, MberService mberService, HttpServletRequest request) throws JsonProcessingException { |
|
212 |
+ String mbrType = convertProviderToMbrType(oAuth2User.getProvider()); |
|
213 |
+ MberVO existingUser = mberService.findByEmailAndProvider(oAuth2User.getEmail(), mbrType); |
|
214 |
+ if (existingUser != null) { |
|
215 |
+ existingUser.setMbrNm(oAuth2User.getName()); |
|
216 |
+ return mberService.updateOAuthUser(existingUser); |
|
217 |
+ } else { |
|
218 |
+ MberVO newUser = new MberVO(); |
|
219 |
+ newUser.setEml(oAuth2User.getEmail()); |
|
220 |
+ newUser.setLgnId(oAuth2User.getEmail().toLowerCase()); |
|
221 |
+ newUser.setMbrNm(oAuth2User.getName()); |
|
222 |
+ newUser.setNcnm(oAuth2User.getName()); |
|
223 |
+ newUser.setMbrType(mbrType); |
|
224 |
+ MberAuthorVO roleUser = new MberAuthorVO(); |
|
225 |
+ roleUser.setAuthrtCd("ROLE_USER"); |
|
226 |
+ newUser.setAuthorList(Collections.singletonList(roleUser)); |
|
168 | 227 |
|
169 |
- // 중복 로그인 정책 확인 |
|
170 |
- if (!loginPolicyService.getPolicy()) { |
|
171 |
- redisTemplate.delete("jwt:" + tempUserId); |
|
172 |
- redisTemplate.opsForValue().set("jwt:" + tempUserId, accessToken, |
|
173 |
- JWT_ACCESSTIME, TimeUnit.MILLISECONDS); |
|
228 |
+ return mberService.saveOAuthUser(newUser, request); |
|
174 | 229 |
} |
175 | 230 |
} |
231 |
+ |
|
232 |
+ private String convertProviderToMbrType(String provider) { |
|
233 |
+ return switch (provider.toLowerCase()) { |
|
234 |
+ case "kakao" -> "K"; |
|
235 |
+ case "naver" -> "N"; |
|
236 |
+ case "google" -> "G"; |
|
237 |
+ case "facebook" -> "F"; |
|
238 |
+ default -> "S"; |
|
239 |
+ }; |
|
240 |
+ } |
|
241 |
+ |
|
242 |
+ private void handleOAuth2Error(HttpServletResponse response, Exception e) throws IOException { |
|
243 |
+ String message = URLEncoder.encode("OAuth 로그인에 실패했습니다.", "UTF-8"); |
|
244 |
+ String errorUrl = String.format("%s/login.page?error=oauth2_failed&message=%s", frontUrl, message); |
|
245 |
+ getRedirectStrategy().sendRedirect(null, response, errorUrl); |
|
246 |
+ } |
|
176 | 247 |
}(파일 끝에 줄바꿈 문자 없음) |
--- src/main/java/com/takensoft/common/oauth/handler/OAuth2UserSaveEvent.java
... | ... | @@ -1,25 +0,0 @@ |
1 | -package com.takensoft.common.oauth.handler; | |
2 | - | |
3 | -import com.takensoft.common.oauth.vo.CustomOAuth2UserVO; | |
4 | -import lombok.Getter; | |
5 | -import org.springframework.context.ApplicationEvent; | |
6 | - | |
7 | -/** | |
8 | - * @author takensoft | |
9 | - * @since 2025.05.22 | |
10 | - * @modification | |
11 | - * since | author | description | |
12 | - * 2025.05.22 | takensoft | 최초 등록 | |
13 | - * | |
14 | - * OAuth2 사용자 저장 이벤트 | |
15 | - */ | |
16 | -@Getter | |
17 | -public class OAuth2UserSaveEvent extends ApplicationEvent { | |
18 | - | |
19 | - private final CustomOAuth2UserVO oAuth2User; | |
20 | - | |
21 | - public OAuth2UserSaveEvent(CustomOAuth2UserVO oAuth2User) { | |
22 | - super(oAuth2User); | |
23 | - this.oAuth2User = oAuth2User; | |
24 | - } | |
25 | -}(파일 끝에 줄바꿈 문자 없음) |
--- src/main/java/com/takensoft/common/oauth/handler/OAuth2UserSaveEventListener.java
... | ... | @@ -1,119 +0,0 @@ |
1 | -package com.takensoft.common.oauth.handler; | |
2 | - | |
3 | -import com.takensoft.cms.mber.service.MberService; | |
4 | -import com.takensoft.cms.mber.vo.MberAuthorVO; | |
5 | -import com.takensoft.cms.mber.vo.MberVO; | |
6 | -import com.takensoft.common.oauth.vo.CustomOAuth2UserVO; | |
7 | -import lombok.RequiredArgsConstructor; | |
8 | -import lombok.extern.slf4j.Slf4j; | |
9 | -import org.springframework.context.event.EventListener; | |
10 | -import org.springframework.scheduling.annotation.Async; | |
11 | -import org.springframework.stereotype.Component; | |
12 | - | |
13 | -import java.util.ArrayList; | |
14 | -import java.util.List; | |
15 | - | |
16 | -/** | |
17 | - * @author takensoft | |
18 | - * @since 2025.05.22 | |
19 | - * @modification | |
20 | - * since | author | description | |
21 | - * 2025.05.22 | takensoft | 최초 등록 | |
22 | - * | |
23 | - * OAuth2 사용자 저장 이벤트 리스너 | |
24 | - */ | |
25 | -@Component | |
26 | -@RequiredArgsConstructor | |
27 | -@Slf4j | |
28 | -public class OAuth2UserSaveEventListener { | |
29 | - | |
30 | - private final MberService mberService; | |
31 | - | |
32 | - @EventListener | |
33 | - @Async | |
34 | - public void handleOAuth2UserSaveEvent(OAuth2UserSaveEvent event) { | |
35 | - try { | |
36 | - CustomOAuth2UserVO oAuth2User = event.getOAuth2User(); | |
37 | - processOAuth2User(oAuth2User); | |
38 | - } catch (Exception e) { | |
39 | - log.error("OAuth2 사용자 저장 이벤트 처리 실패", e); | |
40 | - } | |
41 | - } | |
42 | - | |
43 | - /** | |
44 | - * OAuth2 사용자 처리 (저장 또는 업데이트) | |
45 | - */ | |
46 | - private void processOAuth2User(CustomOAuth2UserVO oAuth2User) { | |
47 | - String mbrType = convertProviderToMbrType(oAuth2User.getProvider()); | |
48 | - | |
49 | - // 이메일과 제공자로 기존 사용자 조회 | |
50 | - MberVO existingUser = mberService.findByEmailAndProvider(oAuth2User.getEmail(), mbrType); | |
51 | - | |
52 | - if (existingUser != null) { | |
53 | - updateExistingUser(existingUser, oAuth2User); | |
54 | - } else { | |
55 | - createNewUser(oAuth2User, mbrType); | |
56 | - } | |
57 | - } | |
58 | - | |
59 | - /** | |
60 | - * 기존 사용자 정보 업데이트 | |
61 | - */ | |
62 | - private void updateExistingUser(MberVO existingUser, CustomOAuth2UserVO oAuth2User) { | |
63 | - try { | |
64 | - existingUser.setMbrNm(oAuth2User.getName()); | |
65 | - // 프로필 이미지 URL이 있다면 업데이트 | |
66 | - if (oAuth2User.getImageUrl() != null) { | |
67 | - // 필요시 프로필 이미지 필드 추가 | |
68 | - } | |
69 | - | |
70 | - mberService.updateOAuthUser(existingUser); | |
71 | - log.info("기존 OAuth2 사용자 정보 업데이트 완료: {}", existingUser.getEml()); | |
72 | - } catch (Exception e) { | |
73 | - log.error("기존 OAuth2 사용자 업데이트 실패: {}", oAuth2User.getEmail(), e); | |
74 | - } | |
75 | - } | |
76 | - | |
77 | - /** | |
78 | - * 새 OAuth2 사용자 생성 | |
79 | - */ | |
80 | - private void createNewUser(CustomOAuth2UserVO oAuth2User, String mbrType) { | |
81 | - try { | |
82 | - MberVO newUser = new MberVO(); | |
83 | - newUser.setEml(oAuth2User.getEmail()); | |
84 | - newUser.setMbrNm(oAuth2User.getName()); | |
85 | - newUser.setNcnm(oAuth2User.getName()); | |
86 | - newUser.setMbrType(mbrType); | |
87 | - newUser.setAuthorList(createDefaultAuthorities()); | |
88 | - | |
89 | - mberService.saveOAuthUser(newUser); | |
90 | - log.info("새 OAuth2 사용자 생성 완료: {}", newUser.getEml()); | |
91 | - } catch (Exception e) { | |
92 | - log.error("새 OAuth2 사용자 생성 실패: {}", oAuth2User.getEmail(), e); | |
93 | - } | |
94 | - } | |
95 | - | |
96 | - /** | |
97 | - * 기본 권한 생성 | |
98 | - */ | |
99 | - private List<MberAuthorVO> createDefaultAuthorities() { | |
100 | - List<MberAuthorVO> authorities = new ArrayList<>(); | |
101 | - MberAuthorVO userRole = new MberAuthorVO(); | |
102 | - userRole.setAuthrtCd("ROLE_USER"); | |
103 | - authorities.add(userRole); | |
104 | - return authorities; | |
105 | - } | |
106 | - | |
107 | - /** | |
108 | - * OAuth2 제공자를 회원 타입으로 변환 | |
109 | - */ | |
110 | - private String convertProviderToMbrType(String provider) { | |
111 | - return switch (provider.toLowerCase()) { | |
112 | - case "kakao" -> "K"; | |
113 | - case "naver" -> "N"; | |
114 | - case "google" -> "G"; | |
115 | - case "facebook" -> "F"; | |
116 | - default -> "S"; // 일반 회원 | |
117 | - }; | |
118 | - } | |
119 | -}(파일 끝에 줄바꿈 문자 없음) |
--- src/main/java/com/takensoft/common/oauth/service/Impl/CustomOAuth2UserServiceImpl.java
+++ src/main/java/com/takensoft/common/oauth/service/Impl/CustomOAuth2UserServiceImpl.java
... | ... | @@ -29,8 +29,7 @@ |
29 | 29 |
@Service("customOAuth2UserService") |
30 | 30 |
@RequiredArgsConstructor |
31 | 31 |
@Slf4j |
32 |
-public class CustomOAuth2UserServiceImpl extends EgovAbstractServiceImpl |
|
33 |
- implements CustomOAuth2UserService, OAuth2UserService<OAuth2UserRequest, OAuth2User> { |
|
32 |
+public class CustomOAuth2UserServiceImpl extends EgovAbstractServiceImpl implements CustomOAuth2UserService, OAuth2UserService<OAuth2UserRequest, OAuth2User> { |
|
34 | 33 |
|
35 | 34 |
private final DefaultOAuth2UserService delegate = new DefaultOAuth2UserService(); |
36 | 35 |
|
... | ... | @@ -41,7 +40,7 @@ |
41 | 40 |
* @throws DataAccessException - db 관련 예외 발생 시 |
42 | 41 |
* @throws Exception - 그 외 예외 발생 시 |
43 | 42 |
* |
44 |
- * OAuth2 사용자 정보 로드 (최소 처리) |
|
43 |
+ * OAuth2 사용자 정보 로드 |
|
45 | 44 |
*/ |
46 | 45 |
@Override |
47 | 46 |
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { |
... | ... | @@ -49,10 +48,8 @@ |
49 | 48 |
OAuth2User oAuth2User = delegate.loadUser(userRequest); |
50 | 49 |
return processOAuth2User(userRequest, oAuth2User); |
51 | 50 |
} catch (DataAccessException dae) { |
52 |
- log.error("OAuth2 사용자 처리 중 DB 오류 발생", dae); |
|
53 | 51 |
throw new OAuth2AuthenticationException("OAuth2 사용자 처리 실패 - DB 오류"); |
54 | 52 |
} catch (Exception e) { |
55 |
- log.error("OAuth2 사용자 처리 중 오류 발생", e); |
|
56 | 53 |
throw new OAuth2AuthenticationException("OAuth2 사용자 처리 실패"); |
57 | 54 |
} |
58 | 55 |
} |
... | ... | @@ -64,7 +61,7 @@ |
64 | 61 |
* @throws DataAccessException - db 관련 예외 발생 시 |
65 | 62 |
* @throws Exception - 그 외 예외 발생 시 |
66 | 63 |
* |
67 |
- * OAuth2 사용자 정보 처리 (단순히 CustomOAuth2User 객체만 생성) |
|
64 |
+ * OAuth2 사용자 정보 처리 |
|
68 | 65 |
*/ |
69 | 66 |
private OAuth2User processOAuth2User(OAuth2UserRequest userRequest, OAuth2User oAuth2User) { |
70 | 67 |
try { |
--- src/main/java/com/takensoft/common/oauth/web/OAuth2Controller.java
+++ src/main/java/com/takensoft/common/oauth/web/OAuth2Controller.java
... | ... | @@ -1,11 +1,14 @@ |
1 | 1 |
package com.takensoft.common.oauth.web; |
2 | 2 |
|
3 |
+import com.takensoft.cms.loginPolicy.service.LoginModeService; |
|
3 | 4 |
import com.takensoft.cms.mber.service.MberService; |
4 | 5 |
import com.takensoft.cms.mber.vo.MberVO; |
5 | 6 |
import com.takensoft.common.message.MessageCode; |
6 | 7 |
import com.takensoft.common.service.VerificationService; |
7 | 8 |
import com.takensoft.common.util.HttpRequestUtil; |
9 |
+import com.takensoft.common.util.JWTUtil; |
|
8 | 10 |
import com.takensoft.common.util.ResponseUtil; |
11 |
+import jakarta.servlet.http.Cookie; |
|
9 | 12 |
import jakarta.servlet.http.HttpServletRequest; |
10 | 13 |
import jakarta.servlet.http.HttpServletResponse; |
11 | 14 |
import jakarta.servlet.http.HttpSession; |
... | ... | @@ -13,16 +16,13 @@ |
13 | 16 |
import lombok.extern.slf4j.Slf4j; |
14 | 17 |
import org.springframework.beans.factory.annotation.Value; |
15 | 18 |
import org.springframework.http.ResponseEntity; |
16 |
-import org.springframework.web.bind.annotation.GetMapping; |
|
17 |
-import org.springframework.web.bind.annotation.RequestMapping; |
|
18 |
-import org.springframework.web.bind.annotation.RequestParam; |
|
19 |
-import org.springframework.web.bind.annotation.RestController; |
|
19 |
+import org.springframework.web.bind.annotation.*; |
|
20 | 20 |
|
21 | 21 |
import java.io.IOException; |
22 | 22 |
import java.util.Arrays; |
23 |
+import java.util.Enumeration; |
|
23 | 24 |
import java.util.HashMap; |
24 | 25 |
import java.util.List; |
25 |
-import java.util.Map; |
|
26 | 26 |
|
27 | 27 |
/** |
28 | 28 |
* @author takensoft |
... | ... | @@ -31,6 +31,7 @@ |
31 | 31 |
* since | author | description |
32 | 32 |
* 2025.05.22 | takensoft | 최초 등록 |
33 | 33 |
* 2025.05.26 | takensoft | OAuth 리다이렉트 기능 추가 |
34 |
+ * 2025.05.28 | takensoft | 쿠키에서 OAuth 토큰 읽기 추가 |
|
34 | 35 |
* |
35 | 36 |
* OAuth2 관련 통합 컨트롤러 |
36 | 37 |
*/ |
... | ... | @@ -44,6 +45,8 @@ |
44 | 45 |
private final VerificationService verificationService; |
45 | 46 |
private final ResponseUtil resUtil; |
46 | 47 |
private final HttpRequestUtil httpRequestUtil; |
48 |
+ private final LoginModeService loginModeService; |
|
49 |
+ private final JWTUtil jwtUtil; |
|
47 | 50 |
|
48 | 51 |
@Value("${front.url}") |
49 | 52 |
private String FRONT_URL; |
... | ... | @@ -56,17 +59,10 @@ |
56 | 59 |
* 프론트엔드에서 provider 정보를 받아 검증 후 OAuth2 서버로 리다이렉트 |
57 | 60 |
*/ |
58 | 61 |
@GetMapping("/login") |
59 |
- public void redirectToOAuth(@RequestParam String provider, |
|
60 |
- HttpServletRequest request, |
|
61 |
- HttpServletResponse response) throws IOException { |
|
62 |
+ public void redirectToOAuth(@RequestParam String provider, HttpServletRequest request, HttpServletResponse response) throws IOException { |
|
62 | 63 |
|
63 | 64 |
String clientIP = httpRequestUtil.getIp(request); |
64 | 65 |
String userAgent = httpRequestUtil.getUserAgent(request); |
65 |
- |
|
66 |
- log.info("=== OAuth 로그인 시도 ==="); |
|
67 |
- log.info("Provider: {}", provider); |
|
68 |
- log.info("Client IP: {}", clientIP); |
|
69 |
- log.info("User Agent: {}", userAgent); |
|
70 | 66 |
|
71 | 67 |
try { |
72 | 68 |
// 1. Provider 유효성 검증 |
... | ... | @@ -83,20 +79,15 @@ |
83 | 79 |
|
84 | 80 |
// 4. OAuth2 Authorization Server로 리다이렉트 |
85 | 81 |
String redirectUrl = "/oauth2/authorization/" + provider; |
86 |
- log.info("OAuth 리다이렉트 URL: {}", redirectUrl); |
|
87 | 82 |
|
88 |
- // 리다이렉트 상태 코드를 명시적으로 설정 |
|
89 | 83 |
response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); |
90 | 84 |
response.sendRedirect(redirectUrl); |
91 | 85 |
|
92 | 86 |
} catch (IllegalArgumentException e) { |
93 |
- log.error("OAuth 로그인 실패 - 잘못된 Provider: {}", provider); |
|
94 | 87 |
handleError(response, "invalid_provider", e.getMessage()); |
95 | 88 |
} catch (SecurityException e) { |
96 |
- log.error("OAuth 로그인 실패 - 보안 검증 실패: {}", e.getMessage()); |
|
97 | 89 |
handleError(response, "security_check_failed", e.getMessage()); |
98 | 90 |
} catch (Exception e) { |
99 |
- log.error("OAuth 로그인 실패 - 시스템 오류", e); |
|
100 | 91 |
handleError(response, "system_error", "OAuth 로그인 중 오류가 발생했습니다."); |
101 | 92 |
} |
102 | 93 |
} |
... | ... | @@ -104,39 +95,107 @@ |
104 | 95 |
/** |
105 | 96 |
* OAuth2 로그인 후 사용자 정보 조회 |
106 | 97 |
*/ |
107 |
- @GetMapping(value = "/user-info") |
|
98 |
+ @PostMapping(value = "/getUserInfo.json") |
|
108 | 99 |
public ResponseEntity<?> getUserInfo(HttpServletRequest request) { |
109 | 100 |
try { |
110 |
- // 현재 로그인된 사용자 ID 추출 |
|
111 |
- String currentUserId = verificationService.getCurrentUserId(); |
|
101 |
+ // 로그인 모드 확인 |
|
102 |
+ String loginMode = loginModeService.getLoginMode(); |
|
112 | 103 |
|
113 |
- if (currentUserId == null || currentUserId.isEmpty()) { |
|
114 |
- // 세션에서 OAuth2 정보 확인 (세션 모드인 경우) |
|
104 |
+ // 현재 로그인한 사용자 ID 조회 |
|
105 |
+ String currentUserId = null; |
|
106 |
+ MberVO mberInfo = null; |
|
107 |
+ |
|
108 |
+ if ("S".equals(loginMode)) { |
|
109 |
+ // 세션 모드 - 세션에서 직접 조회 |
|
115 | 110 |
HttpSession session = request.getSession(false); |
116 |
- if (session != null && session.getAttribute("oauth2User") != null) { |
|
117 |
- return handleSessionOAuth2User(session); |
|
111 |
+ if (session != null) { |
|
112 |
+ currentUserId = (String) session.getAttribute("mbrId"); |
|
113 |
+ |
|
114 |
+ if (currentUserId != null) { |
|
115 |
+ // 세션에 저장된 정보로 응답 생성 |
|
116 |
+ HashMap<String, Object> result = new HashMap<>(); |
|
117 |
+ result.put("mbrId", session.getAttribute("mbrId")); |
|
118 |
+ result.put("mbrNm", session.getAttribute("mbrNm")); |
|
119 |
+ result.put("roles", session.getAttribute("roles")); |
|
120 |
+ |
|
121 |
+ return resUtil.successRes(result, MessageCode.COMMON_SUCCESS); |
|
122 |
+ } |
|
123 |
+ } else { |
|
124 |
+ return resUtil.errorRes(MessageCode.LOGIN_USER_NOT_FOUND); |
|
125 |
+ } |
|
126 |
+ } else { |
|
127 |
+ // JWT 모드 - 토큰에서 조회 |
|
128 |
+ String authHeader = request.getHeader("Authorization"); |
|
129 |
+ System.out.println("Authorization Header: " + authHeader); |
|
130 |
+ |
|
131 |
+ String token = null; |
|
132 |
+ |
|
133 |
+ if (authHeader != null && !authHeader.isEmpty()) { |
|
134 |
+ // Authorization 헤더에서 토큰 추출 |
|
135 |
+ token = jwtUtil.extractToken(authHeader); |
|
136 |
+ } else { |
|
137 |
+ // Authorization 헤더가 없으면 OAuth 전용 쿠키에서 확인 |
|
138 |
+ token = getTokenFromOAuthCookie(request); |
|
118 | 139 |
} |
119 | 140 |
|
120 |
- return resUtil.errorRes(MessageCode.LOGIN_USER_NOT_FOUND); |
|
141 |
+ if (token == null || token.isEmpty()) { |
|
142 |
+ return resUtil.errorRes(MessageCode.LOGIN_USER_NOT_FOUND); |
|
143 |
+ } |
|
144 |
+ |
|
145 |
+ try { |
|
146 |
+ currentUserId = (String) jwtUtil.getClaim(token, "mbrId"); |
|
147 |
+ |
|
148 |
+ // JWT 모드에서는 DB에서 최신 정보 조회 |
|
149 |
+ HashMap<String, Object> params = new HashMap<>(); |
|
150 |
+ params.put("mbrId", currentUserId); |
|
151 |
+ mberInfo = mberService.findByMbr(params); |
|
152 |
+ |
|
153 |
+ if (mberInfo == null) { |
|
154 |
+ return resUtil.errorRes(MessageCode.LOGIN_USER_NOT_FOUND); |
|
155 |
+ } |
|
156 |
+ |
|
157 |
+ // 응답 데이터 구성 |
|
158 |
+ HashMap<String, Object> result = new HashMap<>(); |
|
159 |
+ result.put("mbrId", mberInfo.getMbrId()); |
|
160 |
+ result.put("mbrNm", mberInfo.getMbrNm()); |
|
161 |
+ result.put("roles", mberInfo.getAuthorList()); |
|
162 |
+ |
|
163 |
+ // 토큰도 함께 전달 |
|
164 |
+ if (authHeader != null && !authHeader.isEmpty()) { |
|
165 |
+ result.put("token", authHeader); |
|
166 |
+ } else { |
|
167 |
+ // 쿠키에서 가져온 토큰이면 Bearer 형태로 반환 |
|
168 |
+ String oauthToken = getTokenFromOAuthCookie(request); |
|
169 |
+ if (oauthToken != null) { |
|
170 |
+ result.put("token", "Bearer " + oauthToken); |
|
171 |
+ } |
|
172 |
+ } |
|
173 |
+ return resUtil.successRes(result, MessageCode.COMMON_SUCCESS); |
|
174 |
+ |
|
175 |
+ } catch (IllegalArgumentException e) { |
|
176 |
+ return resUtil.errorRes(MessageCode.LOGIN_USER_NOT_FOUND); |
|
177 |
+ } |
|
121 | 178 |
} |
122 |
- |
|
123 |
- // DB에서 사용자 정보 조회 |
|
124 |
- HashMap<String, Object> params = new HashMap<>(); |
|
125 |
- params.put("mbrId", currentUserId); |
|
126 |
- MberVO userInfo = mberService.findByMbr(params); |
|
127 |
- |
|
128 |
- if (userInfo == null) { |
|
129 |
- return resUtil.errorRes(MessageCode.LOGIN_USER_NOT_FOUND); |
|
130 |
- } |
|
131 |
- |
|
132 |
- // 응답 데이터 구성 |
|
133 |
- HashMap<String, Object> response = createUserResponse(userInfo); |
|
134 |
- return resUtil.successRes(response, MessageCode.COMMON_SUCCESS); |
|
179 |
+ // 여기까지 왔다면 사용자를 찾을 수 없음 |
|
180 |
+ return resUtil.errorRes(MessageCode.LOGIN_USER_NOT_FOUND); |
|
135 | 181 |
|
136 | 182 |
} catch (Exception e) { |
137 |
- log.error("사용자 정보 조회 실패", e); |
|
138 | 183 |
return resUtil.errorRes(MessageCode.COMMON_UNKNOWN_ERROR); |
139 | 184 |
} |
185 |
+ } |
|
186 |
+ |
|
187 |
+ /** |
|
188 |
+ * OAuth 전용 쿠키에서 access token 추출 |
|
189 |
+ */ |
|
190 |
+ private String getTokenFromOAuthCookie(HttpServletRequest request) { |
|
191 |
+ if (request.getCookies() != null) { |
|
192 |
+ for (Cookie cookie : request.getCookies()) { |
|
193 |
+ if ("oauth_access_token".equals(cookie.getName())) { |
|
194 |
+ return cookie.getValue(); |
|
195 |
+ } |
|
196 |
+ } |
|
197 |
+ } |
|
198 |
+ return null; |
|
140 | 199 |
} |
141 | 200 |
|
142 | 201 |
/** |
... | ... | @@ -144,7 +203,6 @@ |
144 | 203 |
*/ |
145 | 204 |
@GetMapping("/providers") |
146 | 205 |
public ResponseEntity<?> getSupportedProviders() { |
147 |
- log.info("지원하는 OAuth 제공자 목록 조회"); |
|
148 | 206 |
return resUtil.successRes(SUPPORTED_PROVIDERS, MessageCode.COMMON_SUCCESS); |
149 | 207 |
} |
150 | 208 |
|
... | ... | @@ -159,22 +217,13 @@ |
159 | 217 |
if (!SUPPORTED_PROVIDERS.contains(provider.toLowerCase())) { |
160 | 218 |
throw new IllegalArgumentException("지원하지 않는 OAuth 제공자입니다: " + provider); |
161 | 219 |
} |
162 |
- |
|
163 |
- // TODO: 동적 설정으로 특정 제공자 비활성화 체크 |
|
164 |
- // if (!oauthConfigService.isProviderEnabled(provider)) { |
|
165 |
- // throw new IllegalArgumentException(provider + " 로그인이 일시 중단되었습니다."); |
|
166 |
- // } |
|
167 | 220 |
} |
168 | 221 |
|
169 | 222 |
/** |
170 | 223 |
* 보안 검증 (필요시 확장) |
171 | 224 |
*/ |
172 | 225 |
private void validateSecurity(HttpServletRequest request) { |
173 |
- // TODO: 필요시 보안 검증 로직 추가 |
|
174 |
- // 예: IP 화이트리스트, Rate Limiting, 사용자 상태 체크 등 |
|
175 |
- |
|
176 | 226 |
String clientIP = httpRequestUtil.getIp(request); |
177 |
- |
|
178 | 227 |
// 예시: 로컬 개발 환경이 아닌 경우 추가 검증 |
179 | 228 |
if (!"127.0.0.1".equals(clientIP) && !"::1".equals(clientIP)) { |
180 | 229 |
// 운영 환경 보안 검증 로직 |
... | ... | @@ -186,50 +235,7 @@ |
186 | 235 |
*/ |
187 | 236 |
private void handleError(HttpServletResponse response, String errorCode, String errorMessage) throws IOException { |
188 | 237 |
String encodedMessage = java.net.URLEncoder.encode(errorMessage, "UTF-8"); |
189 |
- String errorUrl = String.format("%s/login?error=%s&message=%s", FRONT_URL, errorCode, encodedMessage); |
|
190 |
- |
|
191 |
- log.info("에러 리다이렉트 URL: {}", errorUrl); |
|
238 |
+ String errorUrl = String.format("%s/login.page?error=%s&message=%s", FRONT_URL, errorCode, encodedMessage); |
|
192 | 239 |
response.sendRedirect(errorUrl); |
193 |
- } |
|
194 |
- |
|
195 |
- /** |
|
196 |
- * 세션의 OAuth2 사용자 정보 처리 |
|
197 |
- */ |
|
198 |
- private ResponseEntity<?> handleSessionOAuth2User(HttpSession session) { |
|
199 |
- try { |
|
200 |
- // 세션에서 OAuth2 사용자 정보 추출 |
|
201 |
- // 이는 DB 저장이 완료되기 전의 임시 상태 |
|
202 |
- HashMap<String, Object> tempResponse = new HashMap<>(); |
|
203 |
- tempResponse.put("mbrId", "TEMP_OAUTH2"); |
|
204 |
- tempResponse.put("mbrNm", "OAuth2 User"); |
|
205 |
- tempResponse.put("roles", new String[]{"ROLE_USER"}); |
|
206 |
- tempResponse.put("isTemporary", true); |
|
207 |
- |
|
208 |
- return resUtil.successRes(tempResponse, MessageCode.COMMON_SUCCESS); |
|
209 |
- } catch (Exception e) { |
|
210 |
- log.error("세션 OAuth2 사용자 정보 처리 실패", e); |
|
211 |
- return resUtil.errorRes(MessageCode.COMMON_UNKNOWN_ERROR); |
|
212 |
- } |
|
213 |
- } |
|
214 |
- |
|
215 |
- /** |
|
216 |
- * 사용자 응답 데이터 생성 |
|
217 |
- */ |
|
218 |
- private HashMap<String, Object> createUserResponse(MberVO userInfo) { |
|
219 |
- HashMap<String, Object> response = new HashMap<>(); |
|
220 |
- response.put("mbrId", userInfo.getMbrId()); |
|
221 |
- response.put("mbrNm", userInfo.getMbrNm()); |
|
222 |
- response.put("eml", userInfo.getEml()); |
|
223 |
- response.put("ncnm", userInfo.getNcnm()); |
|
224 |
- response.put("mbrType", userInfo.getMbrType()); |
|
225 |
- |
|
226 |
- // 권한 정보 변환 |
|
227 |
- String[] roles = userInfo.getAuthorList().stream() |
|
228 |
- .map(auth -> auth.getAuthrtCd()) |
|
229 |
- .toArray(String[]::new); |
|
230 |
- response.put("roles", roles); |
|
231 |
- response.put("isTemporary", false); |
|
232 |
- |
|
233 |
- return response; |
|
234 | 240 |
} |
235 | 241 |
}(파일 끝에 줄바꿈 문자 없음) |
--- src/main/java/com/takensoft/common/util/JWTUtil.java
+++ src/main/java/com/takensoft/common/util/JWTUtil.java
... | ... | @@ -81,16 +81,6 @@ |
81 | 81 |
cookie.setHttpOnly(true); // front에서 script로 접근 방지 |
82 | 82 |
return cookie; |
83 | 83 |
} |
84 |
- // 로그인 사용자 아이디 조회 |
|
85 |
- public String getWriter() { |
|
86 |
- String mbrId = null; |
|
87 |
- Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); |
|
88 |
- if(authentication != null && authentication.isAuthenticated()) { |
|
89 |
- Object principal = authentication.getPrincipal(); |
|
90 |
- if(principal instanceof UserDetails) mbrId = ((MberVO) authentication.getPrincipal()).getMbrId(); |
|
91 |
- } |
|
92 |
- return mbrId; |
|
93 |
- } |
|
94 | 84 |
|
95 | 85 |
/** |
96 | 86 |
* @param tkn JWT 토큰 문자열 |
... | ... | @@ -103,6 +93,13 @@ |
103 | 93 |
public Object getClaim(String tkn, String knd) { |
104 | 94 |
Claims claims; |
105 | 95 |
try { |
96 |
+ // 토큰 값 검증 |
|
97 |
+ if (tkn == null || tkn.trim().isEmpty()) { |
|
98 |
+ throw new IllegalArgumentException("Token is null or empty"); |
|
99 |
+ }else{ |
|
100 |
+ tkn = extractToken(tkn); |
|
101 |
+ } |
|
102 |
+ |
|
106 | 103 |
claims = Jwts.parser() |
107 | 104 |
.verifyWith(JWT_SECRET_KEY) |
108 | 105 |
.build() |
... | ... | @@ -144,4 +141,17 @@ |
144 | 141 |
} |
145 | 142 |
} |
146 | 143 |
|
144 |
+ /** |
|
145 |
+ * @param authHeader JWT 토큰 문자열 |
|
146 |
+ * @return 실제 토큰 부분만 추출 |
|
147 |
+ * |
|
148 |
+ * Bearer 토큰에서 실제 토큰 부분만 추출하는 메서드 |
|
149 |
+ */ |
|
150 |
+ public String extractToken(String authHeader) { |
|
151 |
+ if (authHeader != null && authHeader.startsWith("Bearer ")) { |
|
152 |
+ return authHeader.substring(7); |
|
153 |
+ } |
|
154 |
+ return authHeader; // Bearer가 없으면 그대로 반환 |
|
155 |
+ } |
|
156 |
+ |
|
147 | 157 |
} |
--- src/main/resources/mybatis/mapper/mber/mber-SQL.xml
+++ src/main/resources/mybatis/mapper/mber/mber-SQL.xml
... | ... | @@ -149,7 +149,7 @@ |
149 | 149 |
, #{smsRcptnAgreYn} |
150 | 150 |
, #{emlRcptnAgreYn} |
151 | 151 |
, #{prvcRlsYn} |
152 |
- , 'S' |
|
152 |
+ , #{mbrType} |
|
153 | 153 |
, NOW() |
154 | 154 |
, #{frstRegIp} |
155 | 155 |
, '1' |
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?