OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준이다.
위키백과
웹에서 이런 저런 서비스를 돌아다니다 보면 위 사진처럼 카카오, 구글, 애플 등의 외부 소셜 계정을 통해 로그인 및 회원가입이 가능한 서비스들을 만날 수 있다. 로그인뿐 아니라 연동하는 외부 어플리케이션에서 구글, 애플 등이 제공하는 기능들 또한 사용이 가능하다. Google 로그인 시 Google Calendar를 받아와 출력할 수 있듯 말이다.
이 모든 과정은 OAuth 프로토콜을 통해 이루어진다.
즉 Inflearn(제3자 어플리케이션)에 구글 계정을 통해 로그인 시 Inflearn은 구글의 특정 자원(회원 정보, 회원의 캘린더 등)을 통해 사용자를 인증하고 구글에서 제공하는 기능들을 사용할 수 있는 권한을 인가받게 된다.
Resource Server(Google)에서 정보를 가져오는 외부 어플리케이션 (Inflearn)
Client에서 요구하는 정보를 가지고 있는 서버 (Google)
Resource Server(Google)이 제공하는 정보를 통해 Client(Inflearn)에 로그인하는 유저
위 설정을 모두 마치면
해당 url로 접속 시 카카오 로그인 화면 출력
AuthController.java
...
@ApiOperation(value = "카카오 로그인 url", notes = "카카오 로그인 url을 반환")
@GetMapping("/login/adult")
public String kakaoLoginURL() {
String REST_API_KEY = "--";
String REDIRECT_URI = "http://k7d206.p.ssafy.io/api/kakao/login";
String kakaoLoginURL = String.format("kauth.kakao.com/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code", REST_API_KEY, REDIRECT_URI);
return kakaoLoginURL;
}
...
1. 인가 code를 통해 카카오 OAuth Token 발급 및 Access Token 추출
2. 추출한 Access Token을 통해 유저 정보 요청
3. 유저 정보를 기반으로 회원가입 & 로그인 처리
KakaoController.java
@Api("KakaoController")
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/kakao")
public class KakaoController {
private final KakaoService kakaoService;
private final ChildService childService;
@ApiOperation(value = "카카오 로그인 인가 code 발급", notes = "사용자가 카카오 로그인 완료시 인가 code 넘어옴")
@GetMapping("/login")
public void kakaoCallBack(@RequestParam String code) {
// 인가 code를 통해 카카오 OAuth Token 발급 및 Access Token 추출
log.info("인가 code를 통해 카카오 OAuth Token 발급");
String accessToken = kakaoService.getKakaoAccessToken(code);
// 추출한 Access Token을 통해 유저 정보 요청
log.info("추출한 Access Token을 통해 유저 정보 요청");
ParentRegisterDto parentRegisterDto = kakaoService.getKakaoProfile(accessToken);
// 유저 정보를 기반으로 회원가입 & 로그인 처리
log.info("유저 정보를 기반으로 회원가입 & 로그인 처리");
Parent parent = childService.registerParent(parentRegisterDto);
}
}
KakaoService.java
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class KakaoService {
// 카카오 액세스 토큰 발급
public String getKakaoAccessToken(String code) {
String REST_API_KEY = "--";
String REDIRECT_URI = "http://localhost:8881/api/kakao/login";
// POST방식으로 key=value 데이터 요청
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
// HttpBody 오브젝트 생성
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", REST_API_KEY);
params.add("redirect_uri", REDIRECT_URI);
params.add("code", code);
// HttpHeader와 HttpBody를 하나의 오브젝트로 담는다
HttpEntity<MultiValueMap<String,String>> kakaoTokenRequest = new HttpEntity<>(params, headers);
// 실제요청
ResponseEntity<String> response = restTemplate.exchange(
"https://kauth.kakao.com/oauth/token",
HttpMethod.POST,
kakaoTokenRequest,
String.class
);
// 응답 정보 출력
System.out.println("response : ");
System.out.println(response);
// JSON -> 액세스 토큰 파싱
String tokenJson = response.getBody();
JSONObject rjson = new JSONObject(tokenJson);
return rjson.getString("access_token");
}
// 카카오 액세스 토큰을 사용해 유저 정보 요청
public ParentRegisterDto getKakaoProfile(String accessToken) {
///유저정보 요청
RestTemplate restTemplate = new RestTemplate();
//HttpHeader
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + accessToken);
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
//HttpHeader와 HttpBody 담기
HttpEntity<MultiValueMap<String, String>> kakaoProfileRequest = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange("https://kapi.kakao.com/v2/user/me", HttpMethod.POST, kakaoProfileRequest, String.class);
// 응답 JSON에서 필요한 정보들을 추출 후 DTO에 담기
JSONObject body = new JSONObject(response.getBody());
Long id = body.getLong("id");
String email = body.getJSONObject("kakao_account").getString("email");
String nickname = body.getJSONObject("properties").getString("nickname");
System.out.println(email);
System.out.println(nickname);
// DTO 반환
return new ParentRegisterDto(email, nickname, Role.ROLE_PARENT);
}
}
ParentRegisterDto.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ParentRegisterDto {
private String email;
private String nickname;
private Role role;
public Parent toEntity() {
return Parent.builder()
.email(this.getEmail())
.nickname(this.getNickname())
.role(Role.ROLE_PARENT)
.build();
}
}
ChildService.java
// 카카오 유저 정보로 회원 확인 후 회원가입 & 로그인
@Transactional
public Parent registerParent(ParentRegisterDto parentRegisterDto) {
Parent parent = parentRepository.findByNickname(parentRegisterDto.getNickname()).orElse(null);
if (parent == null) {
parentRepository.save(parentRegisterDto.toEntity());
}
return null;
}