OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준이다
카카오와 네이버 로그인 api를 사용해보도록 하겠다.
https://developers.kakao.com/
카카오 개발자에 들어가 앱을 등록한다.
플랫폼도 등록한다.
redirectURI 도 등록해준다!!
application.yml
social:
kakao:
client_id:
redirect: /member/social/login/kakao
url:
login: https://kauth.kakao.com/oauth/authorize
token: https://kauth.kakao.com/oauth/token
profile: https://kapi.kakao.com/v2/user/me
url:
base: http://localhost:8000
yml파일에 등록해준다.
이제 뭘해야하지...?
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api
공식문서를 보자. (한국말 최고...)
위 과정 비슷하게 네이버도 진행하삼요...
로그인 api를 사용하는 흐름을 알아보자.
짱 쉽다 공식문서 그대로 따라하면 된다.
https://kauth.kakao.com/oauth/authorize?client_id=받은 REST API 키&redirect_uri=설정한 uri&response_type=code
동의화면이 나오고 후 오류 화면이 나온다! 괜찮다 오류가 아니니깐 주소를 보면 code값이 무엇인지 보인다. 이게 인가코드이다.
이전에 받은 인가코드를 넣어준다.
그럼 토큰을 발행해준다
{
"token_type":"bearer",
"access_token":"{ACCESS_TOKEN}",
"expires_in":43199,
"refresh_token":"{REFRESH_TOKEN}",
"refresh_token_expires_in":25184000,
"scope":"account_email profile"
}
우리는 여기서 access_token을 이용해 로그인/회원가입을 할것이다.
@ApiOperation(value = "소셜 로그인", notes = " 소셜 회원 로그인을 한다.")
@PostMapping(value = "/signin/{provider}")
public SingleResult<String> signinByProvider(
@ApiParam(value = "서비스 제공자 provider", required = true, defaultValue = "kakao") @PathVariable String provider,
@ApiParam(value = "소셜 인가 코드", required = true) @RequestParam String code) {
User signedUser = null;
switch (provider) {
case "naver":
RetNaverAuth retNaverAuth=naverService.getNaverTokenInfo(code);
signedUser = userService.signupByNaver(retNaverAuth.getAccess_token(), provider);
break;
case "kakao":
RetKakaoAuth retKakaoAuth = kakaoService.getKakaoTokenInfo(code);
signedUser = userService.signupByKakao(retKakaoAuth.getAccess_token(), provider);
break;
}
return responseService.getSingleResult(
jwtTokenProvider.createToken(String.valueOf(signedUser.getEmail()), signedUser.getRoles()));
}
package kr.or.dining_together.member.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import com.google.gson.Gson;
import kr.or.dining_together.member.advice.exception.ComunicationException;
import kr.or.dining_together.member.vo.KakaoProfile;
import kr.or.dining_together.member.vo.RetKakaoAuth;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class KakaoService {
private final RestTemplate restTemplate;
private final Environment env;
private final Gson gson;
@Value("${spring.url.base}")
private String baseUrl;
@Value("${spring.social.kakao.client_id}")
private String kakaoClientId;
@Value("${spring.social.kakao.redirect}")
private String kakaoRedirect;
/**
* 카카오 플랫폼에서 사용자 정보를 요청한다.
**/
public KakaoProfile getKakaoProfile(String accessToken) {
// Set header : Content-type: application/x-www-form-urlencoded
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.set("Authorization", "Bearer " + accessToken);
// Set http entity
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(null, headers);
try {
// Request profile
ResponseEntity<String> response = restTemplate.postForEntity(
env.getProperty("spring.social.kakao.url.profile"), request, String.class);
if (response.getStatusCode() == HttpStatus.OK)
return gson.fromJson(response.getBody(), KakaoProfile.class);
} catch (Exception e) {
throw new ComunicationException();
}
throw new ComunicationException();
}
/**
* 카카오 플랫폼에서 사용자 토큰을 발급받는다.
**/
public RetKakaoAuth getKakaoTokenInfo(String code) {
// Set header : Content-type: application/x-www-form-urlencoded
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// Set parameter
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", kakaoClientId);
params.add("redirect_uri", "http://localhost8000/member/social/login/kakao");
params.add("code", code);
System.out.print(params.values().toString());
// Set http entity
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
ResponseEntity<String> response = restTemplate.postForEntity("https://kauth.kakao.com/oauth/token",
request, String.class);
// return response;
System.out.println(response.getStatusCode());
if (response.getStatusCode() == HttpStatus.OK) {
System.out.println(response.getBody());
return gson.fromJson(response.getBody(), RetKakaoAuth.class);
}
return null;
}
}
package kr.or.dining_together.member.service;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import com.google.gson.Gson;
import kr.or.dining_together.member.vo.NaverProfile;
import kr.or.dining_together.member.vo.RetNaverAuth;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class NaverService {
private final RestTemplate restTemplate;
private final Environment env;
private final Gson gson;
@Value("${spring.url.base}")
private String baseUrl;
@Value("${spring.social.naver.client_id}")
private String naverClientId;
@Value("${spring.social.naver.client_secret}")
private String naverClientSecret;
@Value("${spring.social.naver.redirect}")
private String naverRedirect;
@Value("${spring.social.naver.url.profile}")
private String naverProfile;
private static String get(String apiUrl, Map<String, String> requestHeaders) {
HttpURLConnection con = connect(apiUrl);
try {
con.setRequestMethod("GET");
for (Map.Entry<String, String> header : requestHeaders.entrySet()) {
con.setRequestProperty(header.getKey(), header.getValue());
}
int responseCode = con.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) { // 정상 호출
return readBody(con.getInputStream());
} else { // 에러 발생
return readBody(con.getErrorStream());
}
} catch (IOException e) {
throw new RuntimeException("API 요청과 응답 실패", e);
} finally {
con.disconnect();
}
}
private static HttpURLConnection connect(String apiUrl) {
try {
URL url = new URL(apiUrl);
return (HttpURLConnection)url.openConnection();
} catch (MalformedURLException e) {
throw new RuntimeException("API URL이 잘못되었습니다. : " + apiUrl, e);
} catch (IOException e) {
throw new RuntimeException("연결이 실패했습니다. : " + apiUrl, e);
}
}
private static String readBody(InputStream body) {
InputStreamReader streamReader = new InputStreamReader(body);
try (BufferedReader lineReader = new BufferedReader(streamReader)) {
StringBuilder responseBody = new StringBuilder();
String line;
while ((line = lineReader.readLine()) != null) {
responseBody.append(line);
}
return responseBody.toString();
} catch (IOException e) {
throw new RuntimeException("API 응답을 읽는데 실패했습니다.", e);
}
}
public NaverProfile getNaverProfile(String accessToken) {
String header = "Bearer " + accessToken; // Bearer 다음에 공백 추가
System.out.println(header);
Map<String, String> requestHeaders = new HashMap<>();
requestHeaders.put("Authorization", header);
String responseBody = get(naverProfile, requestHeaders);
System.out.println(responseBody);
return gson.fromJson(responseBody, NaverProfile.class);
}
public RetNaverAuth getNaverTokenInfo(String code) {
// Set header : Content-type: application/x-www-form-urlencoded
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// Set parameter
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", naverClientId);
params.add("client_secret", naverClientSecret);
params.add("redirect_uri", "http://localhost:8000/member/social/login/naver");
params.add("code", code);
params.add("state", "dining");
// Set http entity
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
ResponseEntity<String> response = restTemplate.postForEntity(env.getProperty("spring.social.naver.url.token"),
request, String.class);
System.out.println(response.getStatusCode());
if (response.getStatusCode() == HttpStatus.OK) {
System.out.println(response.getBody());
return gson.fromJson(response.getBody(), RetNaverAuth.class);
}
return null;
}
}
우선 엑세스토큰을 이용해 사용자 존재 유무를 파악한다.
없으면 -> 회원가입
있으면 -> 로그인을 진행한다.
public User signupByKakao(String accessToken, String provider) {
KakaoProfile profile = kakaoService.getKakaoProfile(accessToken);
KakaoProfile.Kakao_account kakaoAccount = profile.getKakao_account();
Optional<User> user = userRepository.findByEmailAndProvider(String.valueOf(kakaoAccount.getEmail()),
provider);
if (user.isPresent()) {
return user.get();
} else {
Customer kakaoUser = Customer.builder()
.email(String.valueOf(kakaoAccount.getEmail()))
.name(kakaoAccount.getEmail())
.gender(kakaoAccount.getGender())
.provider(provider)
.build();
return (User)userRepository.save(kakaoUser);
}
}
public User signupByNaver(String accessToken, String provider) {
System.out.print(accessToken);
NaverProfile naverProfile = naverService.getNaverProfile(accessToken);
NaverProfile.Response naverAccount = naverProfile.getResponse();
System.out.print(naverAccount.getEmail());
Optional<User> user = userRepository.findByEmailAndProvider(naverAccount.getEmail(), provider);
if (user.isPresent()) {
return user.get();
} else {
Customer naverUser = Customer.builder()
.email(naverAccount.getEmail())
.name(naverAccount.getEmail())
.provider(provider)
.build();
userRepository.save(naverUser);
return naverUser;
}
}
https://daddyprogrammer.org/post/1012/springboot2-rest-api-social-login-kakao/