๐ ๋ฐฐ๊ฒฝ
๊ธฐ์กด์ ์์ฒ๋ผ application.yml ํ์ผ์ kakao ์์ ๋ก๊ทธ์ธ ์ค์ ์ ํด๋จ์๋๋ฐ,
google ์์ ๋ก๊ทธ์ธ๋ ๊ตฌํํ๊ธฐ ์ํด์
//oauth2
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
build.gradle ์ ์์ ๊ฐ์ด ์ถ๊ฐ๋ฅผ ํด์ค ํ์ ๋ค์ ๋๋ฆฌ๋๊น ๊ฐ ์ข ์๋ฌ๋ค์ด ๋ฐ์ํ๋ค.
Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration'
Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration'
Error creating bean with name 'OAuth2AuthorizedClientManager'
Failed to instantiate [org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager]: Factory method 'getAuthorizedClientManager' threw exception with message
Provider ID must be specified for client registration 'kakao'
๐ฑ ๊ฐ์
1. ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ: ์ง์ ๊ตฌํ
- ์ง์ API ํธ์ถ: RestTemplate์ด๋ WebClient๋ก Kakao์ ๋ก๊ทธ์ธ API์ ์์ฒญ์ ๋ณด๋ด๊ณ , ์๋ต์ผ๋ก ๋ฐ์ access token์ ์ฌ์ฉํด ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฐฉ์.
- ์๋์ผ๋ก ์ฒ๋ฆฌ: ์์ฒญ๊ณผ ์๋ต์ ์ง์ ๋ค๋ฃจ๊ณ , ๋ก๊ทธ์ธ ํ ์ฌ์ฉ์ ์ ๋ณด์ ์ธ์ฆ ์ฒ๋ฆฌ๋ ๋ชจ๋ ์๋์ผ๋ก ๊ตฌํํ์ ๊ฐ๋ฅ์ฑ์ด ํผ.
๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ ํ๋ฆ์ ์ค์ค๋ก ์ปจํธ๋กคํ ์ ์๋ ์ฅ์ ์ด ์์ง๋ง, ๋งค๋ฒ ์๋ก์ด ์์ ๋ก๊ทธ์ธ์ ์ถ๊ฐํ ๋ ๊ฐ์ ๊ณผ์ ์ ๋ฐ๋ณตํด์ผ ํด์ ๋ฒ๊ฑฐ๋ก์ธ ์ ์์.
2. ์ฌ์ฉํ์ ๋์ ์ด์ : ์๋ํ์ ๊ฐํธํจ
- ์๋ ๋ฆฌ๋ค์ด๋ ํธ: ๋ก๊ทธ์ธ ๋ฒํผ์ ๋๋ฅด๋ฉด Spring์ด ์๋์ผ๋ก Kakao์ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธํ๊ณ , ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ ํ ๋ค์ ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ๋์์ค๋๋ก ์ค์ ํ ์ ์์์.
- ํ ํฐ ๋ฐ ์ฌ์ฉ์ ์ ๋ณด ์๋ ๊ด๋ฆฌ: Spring์ด Kakao์ access token๊ณผ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์๋์ผ๋ก ๊ฐ์ ธ์ค๊ณ ๊ด๋ฆฌํด ์ฃผ๊ธฐ ๋๋ฌธ์, ๋ก๊ทธ์ธ ํ ์ถ๊ฐ์ ์ธ ์ฒ๋ฆฌ๊ฐ ๊ฑฐ์ ํ์ ์์.
- ๋ค๋ฅธ ์์ ๋ก๊ทธ์ธ๊ณผ ํ์ฅ์ฑ: Kakao ์ธ์ ๋ค๋ฅธ ์์ ๋ก๊ทธ์ธ์ ์ถ๊ฐํ ๋๋ ์ค์ ๋ง ํ๋ฉด ๋๊ณ , ์ฝ๋๋ฅผ ๊ฑฐ์ ์์ ํ ํ์๊ฐ ์์.
์ฆ, spring-boot-starter-oauth2-client๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๋๊ฐ ๋จ์ํด์ง๊ณ ๋ฐ๋ณต๋๋ ๋ถ๋ถ์ ์ค์ผ ์ ์์ด์ ํ์ฅ์ฑ๊ณผ ์ ์ง๋ณด์ ์ธก๋ฉด์์๋ ๋ ํธ๋ฆฌํจ.
๊ฒฐ๋ก
์์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ์ ํ๋๋ ๋ด๊ฐ ์ํฉ์ ๋ง๊ฒ ์ ํํ๋ ๊ฒ์ด๊ณ , ๊ธฐ์กด์ kakao ์์ ๋ก๊ทธ์ธ ํ๋๋ง ๊ตฌํ๋์ด์์๊ธฐ ๋๋ฌธ์ ์ง์ ํ๊ฒฝ์ ๊ตฌํํ๋ ๊ฒ์ด๋ค.
ํ์ง๋ง ๊ตฌ๊ธ๊ณผ ๋ค์ด๋ฒ๊น์ง ์์ ๋ก๊ทธ์ธ์ ๊ตฌํํ๋ ค๋ ์ง๊ธ์ ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์ข์๋ณด์๋ค.
๐ฑ ์ฌ์ฉ๋ฒ
security:
oauth2:
client:
registration:
kakao:
client-id: ${KAKAO_CLIENT_ID}
redirect-uri: "http://localhost:8080/ssaktium/signin/kakao"
authorization-grant-type: authorization_code
scope: profile_nickname, account_email, birthyear
google:
client-id: ${GOOGLE_CLIENT_ID}
client-secret: ${GOOGLE_CLIENT_SECRET}
redirect-uri: "http://localhost:8080/ssaktium/signin/google"
authorization-grant-type: authorization_code
scope: email, profile, https://www.googleapis.com/auth/user.birthday.read
naver:
client-id: ${NAVER_CLIENT_ID}
client-secret: ${NAVER_CLIENT_SECRET}
redirect-uri: "http://localhost:8080/ssaktium/signin-naver"
authorization-grant-type: authorization_code
scope: name, email, birthyear
provider:
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize
token-uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user-name-attribute: id
google:
authorization-uri: https://accounts.google.com/o/oauth2/auth
token-uri: https://oauth2.googleapis.com/token
user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo
user-name-attribute: sub
naver:
authorization-uri: https://nid.naver.com/oauth2.0/authorize
token-uri: https://nid.naver.com/oauth2.0/token
user-info-uri: https://openapi.naver.com/v1/nid/me
user-name-attribute: response.id
ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ์์ฒ๋ผ ์์์ ๋ง์ถฐ์ ์์ฑํด์ผํ๋ค.
Authorization Code Grant Type (์น์ธ ์ฝ๋ ๋ฐฉ์)
OAuth2์๋ ์ฌ๋ฌ ์น์ธ ๋ฐฉ์์ด ์๋๋ฐ, Authorization Code ๋ฐฉ์์ ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ๋ฐฉ์์ผ๋ก, ๋ณด์์ด ๊ฐํ๋ ์ธ์ฆ ํ๋ก์ฐ๋ค. ์ด ๋ฐฉ์์ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ (์: ๋น์ ์ ์น ์ ํ๋ฆฌ์ผ์ด์ )์ด ์ธ๋ถ ์ธ์ฆ ์ ๊ณต์(์: Kakao, Google)๋ฅผ ํตํด ์ฌ์ฉ์๋ก๋ถํฐ ์น์ธ ์ฝ๋(Authorization Code)๋ฅผ ๋ฐ์, ์ด ์ฝ๋๋ฅผ ํตํด ์ก์ธ์ค ํ ํฐ์ ์ป์ด์ค๋ ๊ตฌ์กฐ.
CustomOauthController
// ์์
๋ก๊ทธ์ธ
@GetMapping("/ssaktium/signin/{provider}")
public String socialLogin(
@PathVariable("provider") String provider,
@RequestParam("code") String code, HttpServletResponse response) throws JsonProcessingException {
String token = customOauthService.socialLogin(provider, code, response);
Cookie cookie = new Cookie(JwtUtil.AUTHORIZATION_HEADER, token.substring(7));
cookie.setPath("/");
provider Param ์ ๋ฐ์์ ์นด์นด์ค, ๋ค์ด๋ฒ, ๊ตฌ๊ธ ๋ก ์์ ๋ก๊ทธ์ธ์ ์งํ ํ์ ๋ ํ๋์ ์ปจํธ๋กค๋ฌ์์ ์งํ๋ ์ ์๋๋ก ์ค์ ํ๋ค.
CustomOauthInfoDto
@Getter
@NoArgsConstructor
public class CustomOauthInfoDto {
private String socialId;
private String userName;
private String email;
private String birthYear;
public CustomOauthInfoDto(String socialId, String userName, String email) {
this.socialId = socialId;
this.userName = userName;
this.email = email;
}
public CustomOauthInfoDto(String socialId, String userName, String email, String birthYear) {
this.socialId = socialId;
this.userName = userName;
this.email = email;
this.birthYear = birthYear;
}
public static CustomOauthInfoDto addGoogleId(String socialId, String userName, String email) {
return new CustomOauthInfoDto(socialId, userName, email);
}
}
์์ ๋ก๊ทธ์ธ ์ ์ ๊ณต๋ฐ๋ ํ์์ ๋ณด ๋ฐ์ดํฐ๋ฅผ socialId, userName, email, birthYear ์ด ํฌํจ๋ Dto ๋ก ๋ฐ์์ Service ์์ ๋ค๋ฃจ๊ธฐ ์ํด Dto ๋ฅผ ๋ง๋ค์ด์คฌ๋ค.
CustomOauthService
// ์์
๋ก๊ทธ์ธ ์๋น์ค๋ฅผ ํตํด ์ธ์ฆ์ ์ํํ๋ ๋ฉ์๋
public String socialLogin(String provider, String code, HttpServletResponse response) throws JsonProcessingException {
// 1. "์ธ๊ฐ ์ฝ๋"๋ก "์ก์ธ์ค ํ ํฐ" ์์ฒญ
String accessToken = getAccessToken(provider, code);
// 2. ํ ํฐ์ผ๋ก ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๊ธฐ
CustomOauthInfoDto UserInfo = fetchUserInfoFromProvider(accessToken, provider);
// 3. ์ ์ ์์ฑ
User user = registerUserIfNeeded(UserInfo, response, provider);
// 4. ํ ํฐ ๋ฐ๊ธ
String createToken = jwtUtil.createToken(user.getId(), user.getEmail(), user.getUserRole());
log.info(createToken);
// 5. ํ ํฐ ๋ฐํ
return createToken;
}
CustomOauthService
// AccessToken์ ๋ฐ๊ธ๋ฐ๊ธฐ ์ํ ๊ณตํต ๋ฉ์๋
private String getAccessToken(String provider, String code) {
String redirectUri = "http://localhost:8080/ssaktium/signin/" + provider;
String url;
String clientId;
String clientSecret = null; // ํ์ํ ๊ฒฝ์ฐ์๋ง ํ ๋น
// provider ์ ๋ฐ๋ฅธ URL, clientId, clientSecret ์ค์
switch (provider) {
case "kakao":
url = "https://kauth.kakao.com/oauth/token";
clientId = kakaoClientId;
break;
case "google":
url = "https://oauth2.googleapis.com/token";
clientId = googleClientId;
clientSecret = googleClientSecret;
break;
case "naver":
url = "https://nid.naver.com/oauth2.0/token";
clientId = naverClientId;
clientSecret = naverClientSecret;
break;
default:
throw new NotFoundUserException();
}
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", clientId);
params.add("redirect_uri", redirectUri);
params.add("code", code);
// clientSecret ์ด ์กด์ฌํ๋ ๊ฒฝ์ฐ์๋ง ์ถ๊ฐ
if (clientSecret != null) {
params.add("client_secret", clientSecret);
}
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
Map responseBody = restTemplate.postForObject(url, request, Map.class);
if (responseBody == null || !responseBody.containsKey("access_token")) {
log.error("Failed to fetch access token from {} provider. Response: {}", provider, responseBody);
throw new NotFoundUserException();
}
return (String) responseBody.get("access_token");
}
kakao, Google, Naver ๋ก ๋ถํฐ Access Token ์ ๋ฐ๊ธ ๋ฐ๊ธฐ ์ํด switch ๋ฌธ์ ์จ์ ๊ฐ ๊ฐ์ ์ผ์ด์ค์ ๋ง๊ฒ Token ์ ๋ฐ์์ค๋ ๋ก์ง์ ์งฐ๋ค.
CustomOauthService
// ์์
๋ก๊ทธ์ธํ ์ฌ์ฉ์ ์ ๋ณด๋ก ๋ก๊ทธ์ธ/ํ์๊ฐ์
์ ์ฒ๋ฆฌํ๋ ๋ฉ์๋
public User registerUserIfNeeded(CustomOauthInfoDto userInfo, HttpServletResponse response, String provider) {
// ๊ฐ์ ์ด๋ฉ์ผ์ด ์๋์ง ํ์ธ
String changedEmail = userInfo.getEmail() + "_" + provider;
User existingUser = userRepository.findByEmail(changedEmail).orElse(null);
if (existingUser == null) {
// ๊ธฐ์กด ์ฌ์ฉ์๊ฐ ์๋ ๊ฒฝ์ฐ ์ ๊ท ์ฌ์ฉ์๋ก ๋ฑ๋ก
String password = UUID.randomUUID().toString();
String encodedPassword = passwordEncoder.encode(password);
String email = userInfo.getEmail() + "_" + provider;
String birthYear = userInfo.getBirthYear();
String socialAccountId = userInfo.getSocialId();
existingUser = User.builder()
.email(email)
.userName(userInfo.getUserName())
.password(encodedPassword)
.birthYear(birthYear)
.userRole(UserRole.ROLE_USER)
.socialAccountId(socialAccountId)
.build();
userRepository.save(existingUser);
// JWT ์์ฑ ๋ฐ ํค๋ ์ถ๊ฐ
addJwtToResponse(existingUser, response);
}
return existingUser;
}
// JWT ํ ํฐ์ ์์ฑํ๊ณ , ์๋ต ํค๋์ ์ถ๊ฐํ๋ ๋ฉ์๋
private void addJwtToResponse(User user, HttpServletResponse response) {
String createToken = jwtUtil.createToken(user.getId(), user.getEmail(), user.getUserRole());
jwtUtil.addTokenToResponseHeader(createToken, response);
}
๊ธฐ์กด ์ ์ ๊ฐ ์๋์ง ๊ฒ์ฌํ๊ณ ์๋ค๋ฉด ๋ก๊ทธ์ธ, ์๋ค๋ฉด ์๋ก User ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ DB ์ ์ ์ฅ์ํค๋ ๋ก์ง์ด๊ณ , ์์ฑ๋ Jwt ํ ํฐ์ ํค๋์ ์ถ๊ฐํ๊ธฐ ์ํด addJwtToResponse ๋ฉ์๋๋ ์ถ๊ฐํด์คฌ๋ค.
CustomOauthService
// AccessToken ์ ์ฌ์ฉํ์ฌ ์์
์ ๊ณต์์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฉ์๋
private CustomOauthInfoDto fetchUserInfoFromProvider(String accessToken, String provider) throws JsonProcessingException {
return switch (provider) {
case "kakao" -> fetchKakaoUserInfo(accessToken);
case "google" -> fetchGoogleUserInfo(accessToken);
case "naver" -> fetchNaverUserInfo(accessToken);
default -> throw new NotFoundUserException();
};
}
๋ฐ๊ธ๋ Access Token ์ ๊ฐ ํ๋ซํผ์ ๋ง๊ฒ ๋ฉ์๋๋ก ์ ๋์ํจ๋ค.
CustomOauthService
//์นด์นด์ค ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฉ์๋
private CustomOauthInfoDto fetchKakaoUserInfo(String accessToken) throws JsonProcessingException {
String url = "https://kapi.kakao.com/v2/user/me";
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<String> request = new HttpEntity<>(headers);
String responseBody = restTemplate.exchange(url, HttpMethod.GET, request, String.class).getBody();
if (responseBody == null) {
log.error("Kakao API response body is null.");
throw new NotFoundUserException();
}
JsonNode jsonNode = new ObjectMapper().readTree(responseBody);
String socialId = jsonNode.get("id").asText();
String email = jsonNode.get("kakao_account").get("email").asText();
String birthYear = jsonNode.get("kakao_account")
.get("birthyear").asText();
String userName = jsonNode.get("properties").get("nickname").asText();
log.info("์นด์นด์ค ์ฌ์ฉ์ ์ ๋ณด: " + socialId + ", " + userName + ", " + birthYear + ", " + email);
return new CustomOauthInfoDto(socialId, userName, email, birthYear);
}
//๊ตฌ๊ธ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฉ์๋
private CustomOauthInfoDto fetchGoogleUserInfo(String accessToken) throws JsonProcessingException {
String url = "https://www.googleapis.com/oauth2/v3/userinfo";
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
HttpEntity<String> request = new HttpEntity<>(headers);
String responseBody = restTemplate.exchange(url, HttpMethod.GET, request, String.class).getBody();
if (responseBody == null) {
log.error("Google API response body is null.");
throw new NotFoundUserException();
}
JsonNode jsonNode = new ObjectMapper().readTree(responseBody);
log.info(responseBody);
String socialId = jsonNode.has("sub") ? jsonNode.get("sub").asText() : null;
String name = jsonNode.has("name") ? jsonNode.get("name").asText() : null; // null ์ฒดํฌ
String email = jsonNode.has("email") ? jsonNode.get("email").asText() : null; // null ์ฒดํฌ
return CustomOauthInfoDto.addGoogleId(socialId, name, email);
}
// ๋ค์ด๋ฒ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฉ์๋
private CustomOauthInfoDto fetchNaverUserInfo(String accessToken) throws JsonProcessingException {
String url = "https://openapi.naver.com/v1/nid/me";
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<String> request = new HttpEntity<>(headers);
String responseBody = restTemplate.exchange(url, HttpMethod.GET, request, String.class).getBody();
if (responseBody == null) {
log.error("Naver API response body is null.");
throw new NotFoundUserException();
}
JsonNode jsonNode = new ObjectMapper().readTree(responseBody);
JsonNode responseNode = jsonNode.get("response");
String socialId = responseNode.has("id") ? responseNode.get("id").asText() : null; // null ์ฒดํฌ
String email = responseNode.has("email") ? responseNode.get("email").asText() : null; // null ์ฒดํฌ
String userName = responseNode.has("name") ? responseNode.get("name").asText() : null; // null ์ฒดํฌ
String birthyear = responseNode.has("birthyear") ? responseNode.get("birthyear").asText() : null; // null ์ฒดํฌ
log.info("๋ค์ด๋ฒ ์ฌ์ฉ์ ์ ๋ณด: " + ", " + userName + ", " + birthyear + ", " + email);
return new CustomOauthInfoDto(socialId, userName, email, birthyear);
}
๊ฐ ํ๋ซํผ๋ง๋ค ๋ฐ์ดํฐ๊ฐ ์ ๋ฌ๋๋ ์์์ด ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ ๊ฐ ํ๋ซํผ์ ๋ง๋ ๋ฐฉ์์ผ๋ก ๊ฐ ๊ฐ Dto ๋ก ๋ณํํ๊ธฐ ์ํ ๋ฉ์๋๋ฅผ ๋ง๋ค์ด์คฌ๋ค.
์ ์ฒด ์ฝ๋
@Service
@RequiredArgsConstructor
@Slf4j
public class CustomOauthService {
private final UserRepository userRepository;
private final JwtUtil jwtUtil;
private final RestTemplate restTemplate;
private final PasswordEncoder passwordEncoder;
@Value("${spring.security.oauth2.client.registration.kakao.client-id}")
private String kakaoClientId;
@Value("${spring.security.oauth2.client.registration.google.client-id}")
private String googleClientId;
@Value("${spring.security.oauth2.client.registration.google.client-secret}")
private String googleClientSecret;
@Value("${spring.security.oauth2.client.registration.naver.client-id}")
private String naverClientId;
@Value("${spring.security.oauth2.client.registration.naver.client-secret}")
private String naverClientSecret;
// ์์
๋ก๊ทธ์ธ ์๋น์ค๋ฅผ ํตํด ์ธ์ฆ์ ์ํํ๋ ๋ฉ์๋
public String socialLogin(String provider, String code, HttpServletResponse response) throws JsonProcessingException {
// 1. "์ธ๊ฐ ์ฝ๋"๋ก "์ก์ธ์ค ํ ํฐ" ์์ฒญ
String accessToken = getAccessToken(provider, code);
// 2. ํ ํฐ์ผ๋ก ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๊ธฐ
CustomOauthInfoDto UserInfo = fetchUserInfoFromProvider(accessToken, provider);
// 3. ์ ์ ์์ฑ
User user = registerUserIfNeeded(UserInfo, response, provider);
// 4. ํ ํฐ ๋ฐ๊ธ
String createToken = jwtUtil.createToken(user.getId(), user.getEmail(), user.getUserRole());
log.info(createToken);
// 5. ํ ํฐ ๋ฐํ
return createToken;
}
// AccessToken์ ๋ฐ๊ธ๋ฐ๊ธฐ ์ํ ๊ณตํต ๋ฉ์๋
private String getAccessToken(String provider, String code) {
String redirectUri = "http://localhost:8080/ssaktium/signin/" + provider;
String url;
String clientId;
String clientSecret = null; // ํ์ํ ๊ฒฝ์ฐ์๋ง ํ ๋น
// provider ์ ๋ฐ๋ฅธ URL, clientId, clientSecret ์ค์
switch (provider) {
case "kakao":
url = "https://kauth.kakao.com/oauth/token";
clientId = kakaoClientId;
break;
case "google":
url = "https://oauth2.googleapis.com/token";
clientId = googleClientId;
clientSecret = googleClientSecret;
break;
case "naver":
url = "https://nid.naver.com/oauth2.0/token";
clientId = naverClientId;
clientSecret = naverClientSecret;
break;
default:
throw new NotFoundUserException();
}
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", clientId);
params.add("redirect_uri", redirectUri);
params.add("code", code);
// clientSecret ์ด ์กด์ฌํ๋ ๊ฒฝ์ฐ์๋ง ์ถ๊ฐ
if (clientSecret != null) {
params.add("client_secret", clientSecret);
}
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
Map responseBody = restTemplate.postForObject(url, request, Map.class);
if (responseBody == null || !responseBody.containsKey("access_token")) {
log.error("Failed to fetch access token from {} provider. Response: {}", provider, responseBody);
throw new NotFoundUserException();
}
return (String) responseBody.get("access_token");
}
// ์์
๋ก๊ทธ์ธํ ์ฌ์ฉ์ ์ ๋ณด๋ก ๋ก๊ทธ์ธ/ํ์๊ฐ์
์ ์ฒ๋ฆฌํ๋ ๋ฉ์๋
public User registerUserIfNeeded(CustomOauthInfoDto userInfo, HttpServletResponse response, String provider) {
// ๊ฐ์ ์ด๋ฉ์ผ์ด ์๋์ง ํ์ธ
String changedEmail = userInfo.getEmail() + "_" + provider;
User existingUser = userRepository.findByEmail(changedEmail).orElse(null);
if (existingUser == null) {
// ๊ธฐ์กด ์ฌ์ฉ์๊ฐ ์๋ ๊ฒฝ์ฐ ์ ๊ท ์ฌ์ฉ์๋ก ๋ฑ๋ก
String password = UUID.randomUUID().toString();
String encodedPassword = passwordEncoder.encode(password);
String email = userInfo.getEmail() + "_" + provider;
String birthYear = userInfo.getBirthYear();
String socialAccountId = userInfo.getSocialId();
existingUser = User.builder()
.email(email)
.userName(userInfo.getUserName())
.password(encodedPassword)
.birthYear(birthYear)
.userRole(UserRole.ROLE_USER)
.socialAccountId(socialAccountId)
.build();
userRepository.save(existingUser);
// JWT ์์ฑ ๋ฐ ํค๋ ์ถ๊ฐ
addJwtToResponse(existingUser, response);
}
return existingUser;
}
// JWT ํ ํฐ์ ์์ฑํ๊ณ , ์๋ต ํค๋์ ์ถ๊ฐํ๋ ๋ฉ์๋
private void addJwtToResponse(User user, HttpServletResponse response) {
String createToken = jwtUtil.createToken(user.getId(), user.getEmail(), user.getUserRole());
jwtUtil.addTokenToResponseHeader(createToken, response);
}
// AccessToken ์ ์ฌ์ฉํ์ฌ ์์
์ ๊ณต์์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฉ์๋
private CustomOauthInfoDto fetchUserInfoFromProvider(String accessToken, String provider) throws JsonProcessingException {
return switch (provider) {
case "kakao" -> fetchKakaoUserInfo(accessToken);
case "google" -> fetchGoogleUserInfo(accessToken);
case "naver" -> fetchNaverUserInfo(accessToken);
default -> throw new NotFoundUserException();
};
}
//์นด์นด์ค ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฉ์๋
private CustomOauthInfoDto fetchKakaoUserInfo(String accessToken) throws JsonProcessingException {
String url = "https://kapi.kakao.com/v2/user/me";
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<String> request = new HttpEntity<>(headers);
String responseBody = restTemplate.exchange(url, HttpMethod.GET, request, String.class).getBody();
if (responseBody == null) {
log.error("Kakao API response body is null.");
throw new NotFoundUserException();
}
JsonNode jsonNode = new ObjectMapper().readTree(responseBody);
String socialId = jsonNode.get("id").asText();
String email = jsonNode.get("kakao_account").get("email").asText();
String birthYear = jsonNode.get("kakao_account")
.get("birthyear").asText();
String userName = jsonNode.get("properties").get("nickname").asText();
log.info("์นด์นด์ค ์ฌ์ฉ์ ์ ๋ณด: " + socialId + ", " + userName + ", " + birthYear + ", " + email);
return new CustomOauthInfoDto(socialId, userName, email, birthYear);
}
//๊ตฌ๊ธ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฉ์๋
private CustomOauthInfoDto fetchGoogleUserInfo(String accessToken) throws JsonProcessingException {
String url = "https://www.googleapis.com/oauth2/v3/userinfo";
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
HttpEntity<String> request = new HttpEntity<>(headers);
String responseBody = restTemplate.exchange(url, HttpMethod.GET, request, String.class).getBody();
if (responseBody == null) {
log.error("Google API response body is null.");
throw new NotFoundUserException();
}
JsonNode jsonNode = new ObjectMapper().readTree(responseBody);
log.info(responseBody);
String socialId = jsonNode.has("sub") ? jsonNode.get("sub").asText() : null;
String name = jsonNode.has("name") ? jsonNode.get("name").asText() : null; // null ์ฒดํฌ
String email = jsonNode.has("email") ? jsonNode.get("email").asText() : null; // null ์ฒดํฌ
return CustomOauthInfoDto.addGoogleId(socialId, name, email);
}
// ๋ค์ด๋ฒ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฉ์๋
private CustomOauthInfoDto fetchNaverUserInfo(String accessToken) throws JsonProcessingException {
String url = "https://openapi.naver.com/v1/nid/me";
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<String> request = new HttpEntity<>(headers);
String responseBody = restTemplate.exchange(url, HttpMethod.GET, request, String.class).getBody();
if (responseBody == null) {
log.error("Naver API response body is null.");
throw new NotFoundUserException();
}
JsonNode jsonNode = new ObjectMapper().readTree(responseBody);
JsonNode responseNode = jsonNode.get("response");
String socialId = responseNode.has("id") ? responseNode.get("id").asText() : null; // null ์ฒดํฌ
String email = responseNode.has("email") ? responseNode.get("email").asText() : null; // null ์ฒดํฌ
String userName = responseNode.has("name") ? responseNode.get("name").asText() : null; // null ์ฒดํฌ
String birthyear = responseNode.has("birthyear") ? responseNode.get("birthyear").asText() : null; // null ์ฒดํฌ
log.info("๋ค์ด๋ฒ ์ฌ์ฉ์ ์ ๋ณด: " + ", " + userName + ", " + birthyear + ", " + email);
return new CustomOauthInfoDto(socialId, userName, email, birthyear);
}
}