-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathOAuthController.java
More file actions
153 lines (129 loc) · 6.48 KB
/
OAuthController.java
File metadata and controls
153 lines (129 loc) · 6.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package umc.codeplay.controller;
import java.util.List;
import java.util.Map;
import org.springframework.http.*;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.view.RedirectView;
import lombok.RequiredArgsConstructor;
import io.swagger.v3.oas.annotations.Hidden;
import umc.codeplay.apiPayLoad.ApiResponse;
import umc.codeplay.apiPayLoad.code.status.ErrorStatus;
import umc.codeplay.apiPayLoad.exception.handler.GeneralHandler;
import umc.codeplay.config.properties.BaseOAuthProperties;
import umc.codeplay.config.properties.GoogleOAuthProperties;
import umc.codeplay.config.properties.KakaoOAuthProperties;
import umc.codeplay.domain.Member;
import umc.codeplay.domain.enums.SocialStatus;
import umc.codeplay.dto.MemberResponseDTO;
import umc.codeplay.jwt.JwtUtil;
import umc.codeplay.service.MemberService;
@RestController
@RequestMapping("/oauth")
@RequiredArgsConstructor
@Validated
public class OAuthController {
private final JwtUtil jwtUtil;
private final RestTemplate restTemplate = new RestTemplate();
private final GoogleOAuthProperties googleOAuthProperties;
private final KakaoOAuthProperties kakaoOAuthProperties;
private final MemberService memberService;
@GetMapping("/authorize/{provider}")
public RedirectView redirectToOAuth(@PathVariable("provider") String provider) {
// CSRF 방어용 state, PKCE(code_challenge)..는 굳이
BaseOAuthProperties properties =
switch (provider) {
case "google" -> googleOAuthProperties;
case "kakao" -> kakaoOAuthProperties;
default -> throw new GeneralHandler(ErrorStatus.INVALID_OAUTH_PROVIDER);
};
String url = properties.getUrl();
RedirectView redirectView = new RedirectView();
redirectView.setUrl(url);
return redirectView;
}
@Hidden
@GetMapping("/callback/{provider}")
public ApiResponse<MemberResponseDTO.LoginResultDTO> OAuthCallback(
@RequestParam("code") String code, @PathVariable("provider") String provider) {
BaseOAuthProperties properties =
switch (provider) {
case "google" -> googleOAuthProperties;
case "kakao" -> kakaoOAuthProperties;
default -> throw new GeneralHandler(ErrorStatus.INVALID_OAUTH_PROVIDER);
};
// (1) 받은 code 로 구글 토큰 엔드포인트에 Access/ID Token 교환
Map<String, Object> tokenResponse = requestOAuthToken(code, properties);
// (2) 받아온 Access Token(or ID Token)을 통해 사용자 정보 가져오기
// String idToken = (String) tokenResponse.get("id_token"); // OIDC
String accessToken = (String) tokenResponse.get("access_token");
Map<String, Object> userInfo = requestOAuthUserInfo(accessToken, properties);
String email = null;
String name = null;
switch (provider) {
case "google" -> {
// (3-a) 구글 UserInfo Endpoint 로 이메일, 프로필 등 조회
email = (String) userInfo.get("email");
name = (String) userInfo.get("name");
}
case "kakao" -> {
// (3-b) 카카오 UserInfo Endpoint 로 이메일, 프로필 등 조회
Map<String, Object> kakaoAccount =
(Map<String, Object>) userInfo.get("kakao_account");
Map<String, Object> kakaoProperties =
(Map<String, Object>) userInfo.get("properties");
email = (String) kakaoAccount.get("email");
name = (String) kakaoProperties.get("nickname");
}
}
// (4) 우리 DB에서 회원 조회 or 생성
Member member =
memberService.findOrCreateOAuthMember(
email, name, SocialStatus.valueOf(provider.toUpperCase()));
// (5) JWTUtil 이용해서 Access/Refresh 토큰 발급
var authorities = List.of(new SimpleGrantedAuthority("ROLE_" + member.getRole().name()));
String serviceAccessToken = jwtUtil.generateToken(email, authorities);
String serviceRefreshToken = jwtUtil.generateRefreshToken(email, authorities);
// (6) 최종적으로 JWT(액세스/리프레시)를 프론트에 응답
return ApiResponse.onSuccess(
MemberResponseDTO.LoginResultDTO.builder()
.email(email)
.token(serviceAccessToken)
.refreshToken(serviceRefreshToken)
.build());
}
private Map<String, Object> requestOAuthToken(String code, BaseOAuthProperties properties) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", properties.getClientId());
params.add("client_secret", properties.getClientSecret());
params.add("redirect_uri", properties.getRedirectUri());
params.add("code", code);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
ResponseEntity<Map> response =
restTemplate.postForEntity(properties.getTokenUri(), request, Map.class);
if (response.getStatusCode() == HttpStatus.OK) {
return response.getBody();
}
throw new GeneralHandler(ErrorStatus.OAUTH_TOKEN_REQUEST_FAILED);
}
private Map<String, Object> requestOAuthUserInfo(
String accessToken, BaseOAuthProperties properties) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + accessToken);
HttpEntity<Void> request = new HttpEntity<>(headers);
ResponseEntity<Map> response =
restTemplate.exchange(
properties.getUserInfoUri(), HttpMethod.GET, request, Map.class);
if (response.getStatusCode() == HttpStatus.OK) {
return response.getBody();
}
throw new GeneralHandler(ErrorStatus.OAUTH_USERINFO_REQUEST_FAILED);
}
}