Skip to content

Commit b00672c

Browse files
committed
[FIX] google auth 수정 및 헤더에 오던 토큰 응답을 body로 변경 - front가 유연한 리다이렉트 하도록 책임 변경
1 parent b92824d commit b00672c

File tree

9 files changed

+92
-92
lines changed

9 files changed

+92
-92
lines changed

src/main/java/com/example/ai_tutor/domain/auth/application/AuthService.java

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import com.example.ai_tutor.domain.auth.dto.AuthRes;
88
import com.example.ai_tutor.domain.auth.dto.RefreshTokenReq;
99
import com.example.ai_tutor.domain.auth.dto.TokenMapping;
10-
import com.example.ai_tutor.domain.professor.domain.Professor;
1110
import com.example.ai_tutor.domain.professor.domain.repository.ProfessorRepository;
1211
import com.example.ai_tutor.domain.user.domain.User;
1312
import com.example.ai_tutor.domain.user.domain.repository.UserRepository;
@@ -23,7 +22,6 @@
2322
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
2423
import org.springframework.security.core.Authentication;
2524
import org.springframework.security.core.context.SecurityContextHolder;
26-
import org.springframework.security.crypto.password.PasswordEncoder;
2725
import org.springframework.stereotype.Service;
2826
import org.springframework.transaction.annotation.Transactional;
2927

@@ -34,7 +32,7 @@
3432
@Transactional(readOnly = true)
3533
public class AuthService {
3634

37-
private final CustomTokenProviderService customTokenProviderService;
35+
private final JwtUtil jwtUtil;
3836
private final AuthenticationManager authenticationManager;
3937

4038
private final TokenRepository tokenRepository;
@@ -49,17 +47,17 @@ public ResponseEntity<?> refresh(RefreshTokenReq tokenRefreshRequest){
4947

5048
Token token = tokenRepository.findByRefreshToken(tokenRefreshRequest.getRefreshToken())
5149
.orElseThrow(InvalidTokenException::new);
52-
Authentication authentication = customTokenProviderService.getAuthenticationByEmail(token.getUserEmail());
50+
Authentication authentication = jwtUtil.getAuthenticationByEmail(token.getUserEmail());
5351

5452
//refresh token 정보 값을 업데이트 한다.
5553
//시간 유효성 확인
5654
TokenMapping tokenMapping;
5755

58-
Long expirationTime = customTokenProviderService.getExpiration(tokenRefreshRequest.getRefreshToken());
56+
Long expirationTime = jwtUtil.getExpiration(tokenRefreshRequest.getRefreshToken());
5957
if(expirationTime > 0){
60-
tokenMapping = customTokenProviderService.refreshToken(authentication, token.getRefreshToken());
58+
tokenMapping = jwtUtil.refreshToken(authentication, token.getRefreshToken());
6159
}else{
62-
tokenMapping = customTokenProviderService.createToken(authentication);
60+
tokenMapping = jwtUtil.createToken(authentication);
6361
}
6462

6563
Token updateToken = token.updateRefreshToken(tokenMapping.getRefreshToken());
@@ -95,15 +93,15 @@ public ResponseEntity<?> signOut(UserPrincipal userPrincipal){
9593
private boolean valid(String refreshToken){
9694

9795
//1. 토큰 형식 물리적 검증
98-
boolean validateCheck = customTokenProviderService.validateToken(refreshToken);
96+
boolean validateCheck = jwtUtil.validateToken(refreshToken);
9997
DefaultAssert.isTrue(validateCheck, "Token 검증에 실패하였습니다.");
10098

10199
//2. refresh token 값을 불러온다.
102100
Optional<Token> token = tokenRepository.findByRefreshToken(refreshToken);
103101
DefaultAssert.isTrue(token.isPresent(), "탈퇴 처리된 회원입니다.");
104102

105103
//3. email 값을 통해 인증값을 불러온다
106-
Authentication authentication = customTokenProviderService.getAuthenticationByEmail(token.get().getUserEmail());
104+
Authentication authentication = jwtUtil.getAuthenticationByEmail(token.get().getUserEmail());
107105
DefaultAssert.isTrue(token.get().getUserEmail().equals(authentication.getName()), "사용자 인증에 실패하였습니다.");
108106

109107
return true;
@@ -122,7 +120,7 @@ public ResponseEntity<?> signIn(SignInReq signInReq) {
122120
);
123121
SecurityContextHolder.getContext().setAuthentication(authentication);
124122

125-
TokenMapping tokenMapping = customTokenProviderService.createToken(authentication);
123+
TokenMapping tokenMapping = jwtUtil.createToken(authentication);
126124
Token token = Token.builder()
127125
.refreshToken(tokenMapping.getRefreshToken())
128126
.userEmail(tokenMapping.getUserEmail())

src/main/java/com/example/ai_tutor/domain/auth/application/CustomDefaultOAuth2UserService.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
public class CustomDefaultOAuth2UserService extends DefaultOAuth2UserService {
2828

2929
private final UserRepository userRepository;
30-
private PasswordEncoder passwordEncoder;
3130

3231
@Override
3332
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
@@ -59,22 +58,20 @@ private OAuth2User processOAuth2User(OAuth2UserRequest oAuth2UserRequest, OAuth2
5958
log.info("New user registered: {}", user);
6059
}
6160

62-
log.debug("OAuth2 user processed: {}", user);
61+
log.info("OAuth2 user processed: {}", user);
6362
return UserPrincipal.create(user, oAuth2User.getAttributes());
6463
}
6564

6665
private User registerNewUser(OAuth2UserRequest oAuth2UserRequest, OAuth2UserInfo oAuth2UserInfo) {
6766
User user = User.builder()
6867
.name(oAuth2UserInfo.getName())
6968
.email(oAuth2UserInfo.getEmail())
70-
.password(passwordEncoder.encode(oAuth2UserInfo.getId()))
69+
.password("oauth-only")
7170
.provider(Provider.valueOf(oAuth2UserRequest.getClientRegistration().getRegistrationId()))
7271
.providerId(oAuth2UserInfo.getId())
7372
.build();
7473

75-
User savedUser = userRepository.save(user);
76-
log.debug("New user registered: {}", savedUser);
77-
return savedUser;
74+
return userRepository.save(user);
7875
}
7976

8077
private User updateExistingUser(User user, OAuth2UserInfo oAuth2UserInfo) {

src/main/java/com/example/ai_tutor/domain/auth/application/CustomTokenProviderService.java renamed to src/main/java/com/example/ai_tutor/domain/auth/application/JwtUtil.java

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import io.jsonwebtoken.security.Keys;
99
import lombok.extern.slf4j.Slf4j;
1010
import org.springframework.beans.factory.annotation.Autowired;
11-
import org.springframework.beans.factory.annotation.Value;
1211
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
1312
import org.springframework.security.core.userdetails.UserDetails;
1413
import org.springframework.security.core.Authentication;
@@ -19,48 +18,55 @@
1918

2019
@Slf4j
2120
@Service
22-
public class CustomTokenProviderService {
21+
public class JwtUtil {
2322

2423
@Autowired
2524
private OAuth2Config oAuth2Config;
2625

2726
@Autowired
2827
private CustomUserDetailsService customUserDetailsService;
2928

30-
public TokenMapping refreshToken(Authentication authentication, String refreshToken) {
29+
30+
31+
public TokenMapping createToken(Authentication authentication) {
3132
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
33+
3234
Date now = new Date();
3335

3436
Date accessTokenExpiresIn = new Date(now.getTime() + oAuth2Config.getAuth().getAccessTokenExpirationMsec());
37+
Date refreshTokenExpiresIn = new Date(now.getTime() + oAuth2Config.getAuth().getRefreshTokenExpirationMsec());
3538

3639
String secretKey = oAuth2Config.getAuth().getTokenSecret();
40+
3741
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
3842
Key key = Keys.hmacShaKeyFor(keyBytes);
3943

4044
String accessToken = Jwts.builder()
41-
.setSubject(Long.toString(userPrincipal.getId()))
45+
.setSubject(userPrincipal.getEmail())
4246
.setIssuedAt(new Date())
4347
.setExpiration(accessTokenExpiresIn)
4448
.signWith(key, SignatureAlgorithm.HS512)
4549
.compact();
4650

51+
String refreshToken = Jwts.builder()
52+
.setExpiration(refreshTokenExpiresIn)
53+
.signWith(key, SignatureAlgorithm.HS512)
54+
.compact();
55+
4756
return TokenMapping.builder()
4857
.userEmail(userPrincipal.getEmail())
4958
.accessToken(accessToken)
5059
.refreshToken(refreshToken)
5160
.build();
5261
}
5362

54-
public TokenMapping createToken(Authentication authentication) {
63+
public TokenMapping refreshToken(Authentication authentication, String refreshToken) {
5564
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
56-
5765
Date now = new Date();
5866

5967
Date accessTokenExpiresIn = new Date(now.getTime() + oAuth2Config.getAuth().getAccessTokenExpirationMsec());
60-
Date refreshTokenExpiresIn = new Date(now.getTime() + oAuth2Config.getAuth().getRefreshTokenExpirationMsec());
6168

6269
String secretKey = oAuth2Config.getAuth().getTokenSecret();
63-
6470
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
6571
Key key = Keys.hmacShaKeyFor(keyBytes);
6672

@@ -71,11 +77,6 @@ public TokenMapping createToken(Authentication authentication) {
7177
.signWith(key, SignatureAlgorithm.HS512)
7278
.compact();
7379

74-
String refreshToken = Jwts.builder()
75-
.setExpiration(refreshTokenExpiresIn)
76-
.signWith(key, SignatureAlgorithm.HS512)
77-
.compact();
78-
7980
return TokenMapping.builder()
8081
.userEmail(userPrincipal.getEmail())
8182
.accessToken(accessToken)

src/main/java/com/example/ai_tutor/global/config/security/SecurityConfig.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@
66
import com.example.ai_tutor.global.config.security.handler.CustomSimpleUrlAuthenticationFailureHandler;
77
import com.example.ai_tutor.global.config.security.handler.CustomSimpleUrlAuthenticationSuccessHandler;
88
import com.example.ai_tutor.global.config.security.token.CustomAuthenticationEntryPoint;
9-
import com.example.ai_tutor.global.config.security.token.CustomOncePerRequestFilter;
9+
import com.example.ai_tutor.global.config.security.token.JwtAuthenticationFilter;
1010
import lombok.RequiredArgsConstructor;
1111
import org.springframework.context.annotation.Bean;
1212
import org.springframework.context.annotation.Configuration;
1313
import org.springframework.http.HttpMethod;
1414
import org.springframework.security.authentication.AuthenticationManager;
1515
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
16-
import org.springframework.security.config.Customizer;
1716
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
1817
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
1918
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -30,8 +29,6 @@
3029
import java.util.Arrays;
3130
import java.util.List;
3231

33-
import static org.springframework.security.config.Customizer.withDefaults;
34-
3532
@RequiredArgsConstructor
3633
@Configuration
3734
@EnableWebSecurity
@@ -49,8 +46,8 @@ public PasswordEncoder passwordEncoder() {
4946
}
5047

5148
@Bean
52-
public CustomOncePerRequestFilter customOncePerRequestFilter() {
53-
return new CustomOncePerRequestFilter();
49+
public JwtAuthenticationFilter customOncePerRequestFilter() {
50+
return new JwtAuthenticationFilter();
5451
}
5552

5653
@Bean

src/main/java/com/example/ai_tutor/global/config/security/handler/CustomSimpleUrlAuthenticationFailureHandler.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,39 @@
77
import jakarta.servlet.http.HttpServletRequest;
88
import jakarta.servlet.http.HttpServletResponse;
99
import lombok.RequiredArgsConstructor;
10+
import lombok.extern.slf4j.Slf4j;
1011
import org.springframework.security.core.AuthenticationException;
1112
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
1213
import org.springframework.stereotype.Component;
1314
import org.springframework.web.util.UriComponentsBuilder;
1415

1516
import java.io.IOException;
17+
import java.util.Arrays;
1618

1719
import static com.example.ai_tutor.domain.auth.domain.repository.CustomAuthorizationRequestRepository.REDIRECT_URI_PARAM_COOKIE_NAME;
1820

1921
@RequiredArgsConstructor
2022
@Component
23+
@Slf4j
2124
public class CustomSimpleUrlAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
2225
private final CustomAuthorizationRequestRepository customAuthorizationRequestRepository;
2326

2427
@Override
2528
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
29+
log.error("OAuth2 로그인 실패: {}", exception.getMessage());
30+
31+
log.info("Request URI: {}", request.getRequestURI());
32+
log.info("Query String: {}", request.getQueryString());
33+
log.info("Cookies: {}", Arrays.toString(request.getCookies()));
34+
2635
String targetUrl = CustomCookie.getCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME)
2736
.map(Cookie::getValue)
2837
.orElse(("/"));
2938

3039
targetUrl = UriComponentsBuilder.fromUriString(targetUrl)
31-
.queryParam("error", exception.getLocalizedMessage())
32-
.build().toUriString();
33-
40+
.queryParam("error", "invalid_request")
41+
.build()
42+
.toUriString();
3443
customAuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
3544

3645
getRedirectStrategy().sendRedirect(request, response, targetUrl);
Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.example.ai_tutor.global.config.security.handler;
22

3-
import com.example.ai_tutor.domain.auth.application.CustomTokenProviderService;
3+
import com.example.ai_tutor.domain.auth.application.JwtUtil;
44
import com.example.ai_tutor.domain.auth.domain.Token;
55
import com.example.ai_tutor.domain.auth.domain.repository.CustomAuthorizationRequestRepository;
66
import com.example.ai_tutor.domain.auth.domain.repository.TokenRepository;
@@ -13,6 +13,7 @@
1313
import jakarta.servlet.http.HttpServletRequest;
1414
import jakarta.servlet.http.HttpServletResponse;
1515
import lombok.RequiredArgsConstructor;
16+
import lombok.extern.slf4j.Slf4j;
1617
import org.springframework.security.core.Authentication;
1718
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
1819
import org.springframework.stereotype.Component;
@@ -24,65 +25,66 @@
2425

2526
import static com.example.ai_tutor.domain.auth.domain.repository.CustomAuthorizationRequestRepository.REDIRECT_URI_PARAM_COOKIE_NAME;
2627

28+
@Slf4j
2729
@RequiredArgsConstructor
2830
@Component
2931
public class CustomSimpleUrlAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
3032

31-
private final CustomTokenProviderService customTokenProviderService;
32-
private final OAuth2Config oAuth2Config;
33+
private final JwtUtil jwtUtil; // JWT 생성 유틸
3334
private final TokenRepository tokenRepository;
3435
private final CustomAuthorizationRequestRepository customAuthorizationRequestRepository;
3536

3637
@Override
37-
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
38-
DefaultAssert.isAuthentication(!response.isCommitted());
38+
public void onAuthenticationSuccess(HttpServletRequest request,
39+
HttpServletResponse response,
40+
Authentication authentication) throws IOException, ServletException {
3941

40-
String targetUrl = determineTargetUrl(request, response, authentication);
42+
// 이미 응답이 전송된 상태인지 확인
43+
if (response.isCommitted()) {
44+
log.warn("Response already committed.");
45+
return;
46+
}
4147

42-
TokenMapping token = customTokenProviderService.createToken(authentication);
43-
CustomCookie.addCookie(response, "Authorization", "Bearer_" + token.getAccessToken(), (int) oAuth2Config.getAuth().getAccessTokenExpirationMsec());
44-
CustomCookie.addCookie(response, "Refresh_Token", "Bearer_" + token.getRefreshToken(), (int) oAuth2Config.getAuth().getRefreshTokenExpirationMsec());
48+
// 1) JWT 토큰 생성
49+
TokenMapping tokenMapping = jwtUtil.createToken(authentication);
4550

46-
clearAuthenticationAttributes(request, response);
47-
getRedirectStrategy().sendRedirect(request, response, targetUrl);
48-
}
49-
50-
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
51-
Optional<String> redirectUri = CustomCookie.getCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME).map(Cookie::getValue);
52-
53-
DefaultAssert.isAuthentication( !(redirectUri.isPresent() && !isAuthorizedRedirectUri(redirectUri.get())) );
54-
55-
String targetUrl = redirectUri.orElse(getDefaultTargetUrl());
56-
57-
TokenMapping tokenMapping = customTokenProviderService.createToken(authentication);
51+
// 2) RefreshToken 저장 (DB or Redis etc.)
5852
Token token = Token.builder()
5953
.userEmail(tokenMapping.getUserEmail())
6054
.refreshToken(tokenMapping.getRefreshToken())
6155
.build();
6256
tokenRepository.save(token);
6357

64-
return UriComponentsBuilder.fromUriString(targetUrl)
65-
.queryParam("token", tokenMapping.getAccessToken())
66-
.build().toUriString();
67-
}
58+
// 3) JSON으로 응답
59+
response.setContentType("application/json;charset=UTF-8");
60+
response.setCharacterEncoding("UTF-8");
6861

69-
protected void clearAuthenticationAttributes(HttpServletRequest request, HttpServletResponse response) {
70-
super.clearAuthenticationAttributes(request);
62+
String jsonResponse = String.format(
63+
"{\"accessToken\":\"%s\",\"refreshToken\":\"%s\"}",
64+
tokenMapping.getAccessToken(),
65+
tokenMapping.getRefreshToken()
66+
);
67+
68+
// 4) OAuth2 인증 과정에서 사용된 쿠키/세션 정리
69+
clearAuthenticationAttributes(request, response);
7170
customAuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
71+
72+
// 5) 최종 응답
73+
response.getWriter().write(jsonResponse);
74+
response.getWriter().flush();
75+
76+
log.info("OAuth2 로그인 성공. JWT 토큰 발급 후 JSON으로 응답 완료.");
7277
}
7378

74-
private boolean isAuthorizedRedirectUri(String uri) {
75-
URI clientRedirectUri = URI.create(uri);
76-
77-
return oAuth2Config.getOauth2().getAuthorizedRedirectUris()
78-
.stream()
79-
.anyMatch(authorizedRedirectUri -> {
80-
URI authorizedURI = URI.create(authorizedRedirectUri);
81-
if(authorizedURI.getHost().equalsIgnoreCase(clientRedirectUri.getHost())
82-
&& authorizedURI.getPort() == clientRedirectUri.getPort()) {
83-
return true;
84-
}
85-
return false;
86-
});
79+
/**
80+
* 원래 SimpleUrlAuthenticationSuccessHandler에는
81+
* clearAuthenticationAttributes(HttpServletRequest request) 만 있으므로,
82+
* 필요시 우리가 (request, response) 시그니처의 메서드로 확장.
83+
* 내부에서는 부모 메서드(super) 호출해서 세션 정리만 수행.
84+
*/
85+
protected void clearAuthenticationAttributes(HttpServletRequest request, HttpServletResponse response) {
86+
// 부모 클래스가 세션의 "SPRING_SECURITY_LAST_EXCEPTION" 제거해줌
87+
super.clearAuthenticationAttributes(request);
8788
}
8889
}
90+

0 commit comments

Comments
 (0)