Skip to content

Commit

Permalink
Merge pull request #84 from DevKor-github/main
Browse files Browse the repository at this point in the history
[Merge] main to deploy
  • Loading branch information
jjjh02 authored Feb 8, 2025
2 parents df899e9 + 06c1915 commit 9f00f4d
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 89 deletions.
1 change: 1 addition & 0 deletions ontime-back/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ services:

volumes:
- /home/ubuntu/OnTime-back/ontime-back/src/main/resources/application.properties:/app/src/main/resources/application.properties
- /home/ubuntu/OnTime-back/ontime-back/src/main/resources/key/:/app/resources/key/
depends_on:
- mysql # mysql 서비스가 실행된 이후에 backend를 실행

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import devkor.ontime_back.global.oauth.apple.AppleLoginFilter;
import devkor.ontime_back.global.oauth.apple.AppleLoginService;
import devkor.ontime_back.global.oauth.apple.ApplePublicKeyGenerator;
import devkor.ontime_back.global.oauth.google.GoogleLoginService;
import devkor.ontime_back.global.oauth.kakao.KakaoLoginFilter;
import devkor.ontime_back.global.oauth.google.GoogleLoginFilter;
import devkor.ontime_back.repository.UserRepository;
Expand Down Expand Up @@ -50,6 +51,7 @@ public class SecurityConfig {
private final UserRepository userRepository;
private final ObjectMapper objectMapper;
private final AppleLoginService appleLoginService;
private final GoogleLoginService googleLoginService;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
Expand All @@ -73,7 +75,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
)
.addFilterBefore(new KakaoLoginFilter("/oauth2/kakao/registerOrLogin", jwtTokenProvider, userRepository),
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new GoogleLoginFilter("/oauth2/google/registerOrLogin", jwtTokenProvider, userRepository),
.addFilterBefore(new GoogleLoginFilter("/oauth2/google/registerOrLogin", googleLoginService, userRepository),
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new AppleLoginFilter("/oauth2/apple/registerOrLogin", appleLoginService, userRepository),
UsernamePasswordAuthenticationFilter.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
@Getter
public class OAuthGoogleRequestDto {
private String accessToken;
private String refreshToken;
}
6 changes: 6 additions & 0 deletions ontime-back/src/main/java/devkor/ontime_back/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ public class User {
@Setter
private String firebaseToken;

private String socialLoginToken;

@OneToOne(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL)
private UserSetting userSetting;

Expand Down Expand Up @@ -98,6 +100,10 @@ public void resetPunctualityScore() {
this.latenessCountAfterReset = 0;
}

public void updateSocialLoginToken(String refreshToken) {
this.socialLoginToken = refreshToken;
}

//여유 시간 업데이트
public void setSpareTime(Integer newSpareTime) { this.spareTime = newSpareTime; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo
String responseBody = String.format(
"{ \"status\": \"success\", \"code\": \"200\", \"message\": \"%s\", \"data\": { " +
"\"userId\": %d, \"email\": \"%s\", \"name\": \"%s\", " +
"\"spareTime\": \"%s\", \"note\": \"%s\", \"punctualityScore\": %f, \"role\": \"%s\" } }",
"\"spareTime\": %d, \"note\": \"%s\", \"punctualityScore\": %f, \"role\": \"%s\" } }",
msg, user.getId(), user.getEmail(), user.getName(),
user.getSpareTime(), user.getNote(), user.getPunctualityScore(), user.getRole().name()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public Authentication handleLogin(User user, HttpServletResponse response) throw
String refreshToken = jwtTokenProvider.createRefreshToken();

jwtTokenProvider.updateRefreshToken(user.getEmail(), refreshToken);
jwtTokenProvider.sendAccessToken(response, accessToken);
jwtTokenProvider.sendAccessAndRefreshToken(response, accessToken, refreshToken);

response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
Expand All @@ -83,7 +83,7 @@ public Authentication handleLogin(User user, HttpServletResponse response) throw
String responseBody = String.format(
"{ \"status\": \"success\", \"code\": \"200\", \"message\": \"%s\", \"data\": { " +
"\"userId\": %d, \"email\": \"%s\", \"name\": \"%s\", " +
"\"spareTime\": \"%s\", \"note\": \"%s\", \"punctualityScore\": %f, \"role\": \"%s\" } }",
"\"spareTime\": %d, \"note\": \"%s\", \"punctualityScore\": %f, \"role\": \"%s\" } }",
msg, user.getId(), user.getEmail(), user.getName(),
user.getSpareTime(), user.getNote(), user.getPunctualityScore(), user.getRole().name()
);
Expand Down Expand Up @@ -140,8 +140,6 @@ public Claims verifyIdentityToken(String identityToken) throws
throw new IllegalArgumentException("Invalid JWT: Issuer mismatch. Expected: " + issuer);
}
// aud 확인
log.info("clientId: {}", clientId);
log.info("tokenClaims.getAudience(): {}", tokenClaims.getAudience());
if (!clientId.equals(tokenClaims.getAudience())) {
throw new IllegalArgumentException("Invalid JWT: Audience mismatch. Expected: " + clientId);
}
Expand Down Expand Up @@ -172,7 +170,6 @@ public AppleTokenResponseDto getAppleAccessTokenAndRefreshToken(String authCode)
APPLE_TOKEN_URL, HttpMethod.POST, requestEntity, JsonNode.class);

JsonNode response = responseEntity.getBody();
log.info("Apple Token Response: {}", response.toString());

ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.treeToValue(response, AppleTokenResponseDto.class);
Expand Down Expand Up @@ -210,8 +207,6 @@ private String generateClientSecret() throws Exception {
public boolean appleLoginRevoked(String appleRefreshToken) throws Exception {
log.info("checkAppleLoginRevoked");
String clientSecret = generateClientSecret();
log.info("client_id: {}", clientId);
log.info("client_secret: {}", clientSecret);
String revokeUrl = "https://appleid.apple.com/auth/revoke";

HttpHeaders headers = new HttpHeaders();
Expand All @@ -230,10 +225,8 @@ public boolean appleLoginRevoked(String appleRefreshToken) throws Exception {
try {
ResponseEntity<String> response = restTemplate.exchange(
revokeUrl, HttpMethod.POST, requestEntity, String.class);
log.info("response.getStatusCode(): {}", response.getStatusCode());
return response.getStatusCode() != HttpStatus.OK; // -> 토큰이 아직 유효함
} catch (HttpClientErrorException e) {
log.info("e: {}", e);
return true; // 요청 실패 -> 이미 철회된 refreshToken
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,13 @@

@Slf4j
public class GoogleLoginFilter extends AbstractAuthenticationProcessingFilter {
private final JwtTokenProvider jwtTokenProvider;
private final UserRepository userRepository;
private static final String GOOGLE_USER_INFO_URL = "https://www.googleapis.com/userinfo/v2/me";
private final GoogleLoginService googleLoginService;


public GoogleLoginFilter(String defaultFilterProcessesUrl, JwtTokenProvider jwtTokenProvider, UserRepository userRepository) {
public GoogleLoginFilter(String defaultFilterProcessesUrl, GoogleLoginService googleLoginService, UserRepository userRepository) {
super(defaultFilterProcessesUrl);
this.jwtTokenProvider = jwtTokenProvider;
this.googleLoginService = googleLoginService;
this.userRepository = userRepository;
}

Expand All @@ -46,91 +45,21 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ
throws AuthenticationException, IOException, ServletException {
ObjectMapper objectMapper = new ObjectMapper();
OAuthGoogleRequestDto oAuthGoogleRequestDto = objectMapper.readValue(request.getInputStream(), OAuthGoogleRequestDto.class);
OAuthGoogleUserDto oAuthGoogleUserInfo = getUserInfoFromAccessToken(oAuthGoogleRequestDto.getAccessToken());
OAuthGoogleUserDto oAuthGoogleUserInfo = googleLoginService.getUserInfoFromAccessToken(oAuthGoogleRequestDto.getAccessToken());

Optional<User> existingUser = userRepository.findBySocialTypeAndSocialId(SocialType.GOOGLE, oAuthGoogleUserInfo.getSub());


if (existingUser.isPresent()) {
return handleLogin(existingUser.get(), response);
return googleLoginService.handleLogin(oAuthGoogleRequestDto, existingUser.get(), response);
} else {
return handleRegister(oAuthGoogleUserInfo, response);
return googleLoginService.handleRegister(oAuthGoogleRequestDto, oAuthGoogleUserInfo, response);
}
}

public OAuthGoogleUserDto getUserInfoFromAccessToken(String accessToken) {
RestTemplate restTemplate = new RestTemplate();

HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + accessToken);

HttpEntity<String> entity = new HttpEntity<>(headers);

ResponseEntity<OAuthGoogleUserDto> response = restTemplate.exchange(
GOOGLE_USER_INFO_URL,
org.springframework.http.HttpMethod.GET,
entity,
OAuthGoogleUserDto.class
);

return response.getBody();
}

private Authentication handleLogin(User user, HttpServletResponse response) throws IOException {

String accessToken = jwtTokenProvider.createAccessToken(user.getEmail(), user.getId());
String refreshToken = jwtTokenProvider.createRefreshToken();

jwtTokenProvider.updateRefreshToken(user.getEmail(), refreshToken);
jwtTokenProvider.sendAccessToken(response, accessToken);

response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");

String msg = user.getRole().name().equals("GUEST") ? "유저의 ROLE이 GUEST이므로 온보딩API를 호출해 온보딩을 진행해야합니다." : "로그인에 성공하였습니다.";
// JSON 응답 생성
String responseBody = String.format(
"{ \"status\": \"success\", \"code\": \"200\", \"message\": \"%s\", \"data\": { " +
"\"userId\": %d, \"email\": \"%s\", \"name\": \"%s\", " +
"\"spareTime\": \"%s\", \"note\": \"%s\", \"punctualityScore\": %f, \"role\": \"%s\" } }",
msg, user.getId(), user.getEmail(), user.getName(),
user.getSpareTime(), user.getNote(), user.getPunctualityScore(), user.getRole().name()
);

response.getWriter().write(responseBody);
response.getWriter().flush();

return new UsernamePasswordAuthenticationToken(user, null, Collections.singletonList(new SimpleGrantedAuthority(user.getRole().name())));
}

private Authentication handleRegister(OAuthGoogleUserDto oAuthGoogleUserDto, HttpServletResponse response) throws IOException {
User newUser = User.builder()
.socialType(SocialType.GOOGLE)
.socialId(oAuthGoogleUserDto.getSub())
.email(oAuthGoogleUserDto.getEmail())
.name(oAuthGoogleUserDto.getName())
.imageUrl(oAuthGoogleUserDto.getPicture())
.role(Role.GUEST)
.build();

User savedUser = userRepository.save(newUser);

String accessToken = jwtTokenProvider.createAccessToken(newUser.getEmail(), newUser.getId());
jwtTokenProvider.sendAccessToken(response, accessToken);

response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");

String responseBody = String.format(
"{\"message\": \"%s\", \"role\": \"%s\"}",
"회원가입이 완료되었습니다. ROLE이 GUEST이므로 온보딩이 필요합니다.",
savedUser.getRole().name()
);

response.getWriter().write(responseBody);
response.getWriter().flush();

return new UsernamePasswordAuthenticationToken(newUser, null, Collections.singletonList(new SimpleGrantedAuthority(newUser.getRole().name())));
}

// 인증 성공 처리
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package devkor.ontime_back.global.oauth.google;

import devkor.ontime_back.dto.OAuthGoogleRequestDto;
import devkor.ontime_back.dto.OAuthGoogleUserDto;
import devkor.ontime_back.entity.Role;
import devkor.ontime_back.entity.SocialType;
import devkor.ontime_back.entity.User;
import devkor.ontime_back.global.jwt.JwtTokenProvider;
import devkor.ontime_back.repository.UserRepository;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.Collections;

@Slf4j
@Service
@RequiredArgsConstructor
public class GoogleLoginService {

private final JwtTokenProvider jwtTokenProvider;
private final UserRepository userRepository;
private static final String GOOGLE_USER_INFO_URL = "https://www.googleapis.com/userinfo/v2/me";


public OAuthGoogleUserDto getUserInfoFromAccessToken(String accessToken) {
RestTemplate restTemplate = new RestTemplate();

HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + accessToken);

HttpEntity<String> entity = new HttpEntity<>(headers);

ResponseEntity<OAuthGoogleUserDto> response = restTemplate.exchange(
GOOGLE_USER_INFO_URL,
org.springframework.http.HttpMethod.GET,
entity,
OAuthGoogleUserDto.class
);

return response.getBody();
}
public Authentication handleLogin(OAuthGoogleRequestDto oAuthGoogleRequestDto, User user, HttpServletResponse response) throws IOException {
user.updateSocialLoginToken(oAuthGoogleRequestDto.getRefreshToken());

String accessToken = jwtTokenProvider.createAccessToken(user.getEmail(), user.getId());
String refreshToken = jwtTokenProvider.createRefreshToken();

jwtTokenProvider.updateRefreshToken(user.getEmail(), refreshToken);
jwtTokenProvider.sendAccessAndRefreshToken(response, accessToken, refreshToken);

response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");

String msg = user.getRole().name().equals("GUEST") ? "유저의 ROLE이 GUEST이므로 온보딩API를 호출해 온보딩을 진행해야합니다." : "로그인에 성공하였습니다.";
// JSON 응답 생성
String responseBody = String.format(
"{ \"status\": \"success\", \"code\": \"200\", \"message\": \"%s\", \"data\": { " +
"\"userId\": %d, \"email\": \"%s\", \"name\": \"%s\", " +
"\"spareTime\": %d, \"note\": \"%s\", \"punctualityScore\": %f, \"role\": \"%s\" } }",
msg, user.getId(), user.getEmail(), user.getName(),
user.getSpareTime(), user.getNote(), user.getPunctualityScore(), user.getRole().name()
);

response.getWriter().write(responseBody);
response.getWriter().flush();

return new UsernamePasswordAuthenticationToken(user, null, Collections.singletonList(new SimpleGrantedAuthority(user.getRole().name())));
}

public Authentication handleRegister(OAuthGoogleRequestDto oAuthGoogleRequestDto, OAuthGoogleUserDto oAuthGoogleUserDto, HttpServletResponse response) throws IOException {
User newUser = User.builder()
.socialType(SocialType.GOOGLE)
.socialId(oAuthGoogleUserDto.getSub())
.email(oAuthGoogleUserDto.getEmail())
.name(oAuthGoogleUserDto.getName())
.imageUrl(oAuthGoogleUserDto.getPicture())
.role(Role.GUEST)
.socialLoginToken(oAuthGoogleRequestDto.getRefreshToken())
.build();

User savedUser = userRepository.save(newUser);

String accessToken = jwtTokenProvider.createAccessToken(newUser.getEmail(), newUser.getId());
jwtTokenProvider.sendAccessToken(response, accessToken);

response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");

String responseBody = String.format(
"{\"message\": \"%s\", \"role\": \"%s\"}",
"회원가입이 완료되었습니다. ROLE이 GUEST이므로 온보딩이 필요합니다.",
savedUser.getRole().name()
);

response.getWriter().write(responseBody);
response.getWriter().flush();

return new UsernamePasswordAuthenticationToken(newUser, null, Collections.singletonList(new SimpleGrantedAuthority(newUser.getRole().name())));
}


}

0 comments on commit 9f00f4d

Please sign in to comment.