|
2 | 2 |
|
3 | 3 | import static org.websoso.WSSServer.exception.error.CustomAuthError.INVALID_TOKEN; |
4 | 4 |
|
| 5 | +import io.jsonwebtoken.Claims; |
| 6 | +import java.security.PublicKey; |
| 7 | +import java.util.Map; |
5 | 8 | import lombok.RequiredArgsConstructor; |
6 | 9 | import org.springframework.stereotype.Service; |
7 | 10 | import org.springframework.transaction.annotation.Transactional; |
8 | 11 | import org.websoso.WSSServer.config.jwt.CustomAuthenticationToken; |
9 | 12 | import org.websoso.WSSServer.config.jwt.JWTUtil; |
10 | 13 | import org.websoso.WSSServer.config.jwt.JwtProvider; |
11 | 14 | import org.websoso.WSSServer.config.jwt.JwtValidationType; |
| 15 | +import org.websoso.WSSServer.dto.auth.ApplePublicKeys; |
| 16 | +import org.websoso.WSSServer.dto.auth.AppleTokenResponse; |
| 17 | +import org.websoso.WSSServer.dto.auth.AuthResponse; |
12 | 18 | import org.websoso.WSSServer.dto.auth.LogoutRequest; |
13 | 19 | import org.websoso.WSSServer.dto.auth.ReissueResponse; |
14 | 20 | import org.websoso.WSSServer.dto.user.LoginResponse; |
15 | 21 | import org.websoso.WSSServer.exception.exception.CustomAuthException; |
| 22 | +import org.websoso.WSSServer.oauth2.domain.UserAppleToken; |
| 23 | +import org.websoso.WSSServer.oauth2.dto.KakaoUserInfo; |
| 24 | +import org.websoso.WSSServer.oauth2.service.AppleClient; |
| 25 | +import org.websoso.WSSServer.oauth2.service.AppleKeyGenerator; |
| 26 | +import org.websoso.WSSServer.oauth2.service.AppleService; |
16 | 27 | import org.websoso.WSSServer.oauth2.service.KakaoService; |
17 | | -import org.websoso.WSSServer.repository.RefreshTokenRepository; |
18 | | -import org.websoso.WSSServer.user.domain.RefreshToken; |
| 28 | +import org.websoso.WSSServer.oauth2.repository.RefreshTokenRepository; |
| 29 | +import org.websoso.WSSServer.oauth2.domain.RefreshToken; |
| 30 | +import org.websoso.WSSServer.oauth2.service.TokenService; |
19 | 31 | import org.websoso.WSSServer.user.domain.User; |
20 | | -import org.websoso.WSSServer.user.repository.UserDeviceRepository; |
| 32 | +import org.websoso.WSSServer.notification.repository.UserDeviceRepository; |
21 | 33 | import org.websoso.WSSServer.user.service.UserService; |
22 | 34 |
|
23 | 35 | @Service |
24 | 36 | @RequiredArgsConstructor |
25 | 37 | public class AuthApplication { |
| 38 | + |
| 39 | + private final TokenService tokenService; |
26 | 40 | private final JwtProvider jwtProvider; |
27 | 41 | private final JWTUtil jwtUtil; |
28 | 42 | private final RefreshTokenRepository refreshTokenRepository; |
29 | 43 | private final UserDeviceRepository userDeviceRepository; |
30 | 44 | private final UserService userService; |
31 | 45 | private final KakaoService kakaoService; |
| 46 | + private final AppleService appleService; |
| 47 | + private final AppleClient appleClient; |
32 | 48 | private static final String KAKAO_PREFIX = "kakao"; |
33 | 49 | private static final String APPLE_PREFIX = "apple"; |
| 50 | + private final AppleKeyGenerator appleKeyGenerator; |
34 | 51 |
|
| 52 | + @Transactional |
35 | 53 | public ReissueResponse reissue(String refreshToken) { |
36 | | - RefreshToken storedRefreshToken = refreshTokenRepository.findByRefreshToken(refreshToken) |
37 | | - .orElseThrow(() -> new CustomAuthException(INVALID_TOKEN, "given token is invalid token for reissue")); |
38 | | - |
| 54 | + // 1. 토큰 유효성 검증 |
39 | 55 | if (jwtUtil.validateJWT(refreshToken) != JwtValidationType.VALID_REFRESH) { |
40 | 56 | throw new CustomAuthException(INVALID_TOKEN, "given token is invalid token for reissue"); |
41 | 57 | } |
42 | 58 |
|
| 59 | + // 2. 저장된 토큰 조회 |
| 60 | + RefreshToken storedRefreshToken = tokenService.findRefreshTokenOrThrow(refreshToken); |
| 61 | + |
| 62 | + // 3. 새로운 토큰 생성 |
43 | 63 | Long userId = jwtUtil.getUserIdFromJwt(refreshToken); |
44 | 64 | CustomAuthenticationToken customAuthenticationToken = new CustomAuthenticationToken(userId, null, null); |
| 65 | + |
45 | 66 | String newAccessToken = jwtProvider.generateAccessToken(customAuthenticationToken); |
46 | 67 | String newRefreshToken = jwtProvider.generateRefreshToken(customAuthenticationToken); |
47 | 68 |
|
48 | | - refreshTokenRepository.delete(storedRefreshToken); |
49 | | - refreshTokenRepository.save(new RefreshToken(newRefreshToken, userId)); |
| 69 | + // 4. 리프레시 토큰 교체 |
| 70 | + tokenService.rotateRefreshToken(storedRefreshToken, newRefreshToken, userId); |
50 | 71 |
|
51 | 72 | return ReissueResponse.of(newAccessToken, newRefreshToken); |
52 | 73 | } |
53 | 74 |
|
| 75 | + @Transactional |
| 76 | + public AuthResponse loginKakao(String kakaoAccessToken) { |
| 77 | + // 1. 카카오 로그인 인증 |
| 78 | + KakaoUserInfo kakaoUserInfo = kakaoService.getUserInfo(kakaoAccessToken); |
| 79 | + |
| 80 | + // 2. 사용자 정보 불러오기 / 생성 |
| 81 | + User user = userService.getOrCreateKakaoUser(kakaoUserInfo); |
| 82 | + |
| 83 | + // 3. Access / Refresh Token 생성 |
| 84 | + CustomAuthenticationToken customAuthenticationToken = CustomAuthenticationToken.create(user.getUserId()); |
| 85 | + String accessToken = jwtProvider.generateAccessToken(customAuthenticationToken); |
| 86 | + String refreshToken = jwtProvider.generateRefreshToken(customAuthenticationToken); |
| 87 | + |
| 88 | + // 4. Refresh Token 저장 |
| 89 | + tokenService.saveRefreshToken(user, refreshToken); |
| 90 | + |
| 91 | + boolean isRegister = !user.isTemporaryNickname(); |
| 92 | + return AuthResponse.of(accessToken, refreshToken, isRegister); |
| 93 | + } |
| 94 | + |
| 95 | + @Transactional |
| 96 | + public AuthResponse loginApple(String authorizationCode, String appleToken) { |
| 97 | + // 1. 공개키 가져오기 & 헤더 파싱 |
| 98 | + ApplePublicKeys applePublicKeys = appleClient.getApplePublicKeys(); |
| 99 | + Map<String, String> headers = jwtProvider.parseAppleTokenHeader(appleToken); |
| 100 | + |
| 101 | + // 2. 공개키 생성 및 검증 (서명 확인) |
| 102 | + PublicKey publicKey = appleKeyGenerator.generatePublicKey(headers, applePublicKeys); |
| 103 | + Claims claims = jwtProvider.extractClaims(appleToken, publicKey); |
| 104 | + |
| 105 | + // 3. 애플 서버에서 Refresh Token 받아오기 |
| 106 | + String clientSecret = appleKeyGenerator.createClientSecret(); |
| 107 | + AppleTokenResponse appleTokenResponse = appleClient.requestAppleToken(authorizationCode, clientSecret); |
| 108 | + |
| 109 | + // 4. 유저 정보 추출 |
| 110 | + String email = claims.get("email", String.class); |
| 111 | + String userIdentifier = claims.get("sub", String.class); |
| 112 | + String customSocialId = APPLE_PREFIX + "_" + userIdentifier; |
| 113 | + String defaultNickname = APPLE_PREFIX.charAt(0) + "*" + userIdentifier.substring(7, 15); |
| 114 | + |
| 115 | + // 5. 유저 처리 |
| 116 | + User user = userService.getOrCreateAppleUser(customSocialId, email, defaultNickname); |
| 117 | + |
| 118 | + // 6. 애플 Refresh Token 저장 |
| 119 | + appleService.upsertRefreshToken(user, appleTokenResponse.getRefreshToken()); |
| 120 | + |
| 121 | + // 7. Access / Refresh Token 생성 |
| 122 | + CustomAuthenticationToken customAuthenticationToken = CustomAuthenticationToken.create(user.getUserId()); |
| 123 | + String accessToken = jwtProvider.generateAccessToken(customAuthenticationToken); |
| 124 | + String refreshToken = jwtProvider.generateRefreshToken(customAuthenticationToken); |
| 125 | + |
| 126 | + // 8. Refresh Token 저장 |
| 127 | + tokenService.saveRefreshToken(user, refreshToken); |
| 128 | + |
| 129 | + boolean isRegister = !user.isTemporaryNickname(); |
| 130 | + |
| 131 | + return AuthResponse.of(accessToken, refreshToken, isRegister); |
| 132 | + } |
| 133 | + |
54 | 134 | // TODO: getUserOrException -> existUserOrException 변경 |
55 | 135 | @Transactional(readOnly = true) |
56 | 136 | public LoginResponse login(Long userId) { |
|
0 commit comments