OAuth 2.0 (카카오 로그인)

정종일·2023년 3월 3일
0

Web

목록 보기
4/6
post-custom-banner

OAuth란?

OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준이다.
위키백과

웹에서 이런 저런 서비스를 돌아다니다 보면 위 사진처럼 카카오, 구글, 애플 등의 외부 소셜 계정을 통해 로그인 및 회원가입이 가능한 서비스들을 만날 수 있다. 로그인뿐 아니라 연동하는 외부 어플리케이션에서 구글, 애플 등이 제공하는 기능들 또한 사용이 가능하다. Google 로그인 시 Google Calendar를 받아와 출력할 수 있듯 말이다.

이 모든 과정은 OAuth 프로토콜을 통해 이루어진다.

즉 Inflearn(제3자 어플리케이션)에 구글 계정을 통해 로그인 시 Inflearn은 구글의 특정 자원(회원 정보, 회원의 캘린더 등)을 통해 사용자를 인증하고 구글에서 제공하는 기능들을 사용할 수 있는 권한을 인가받게 된다.

OAuth 참여자

Client

Resource Server(Google)에서 정보를 가져오는 외부 어플리케이션 (Inflearn)

Resource Server

Client에서 요구하는 정보를 가지고 있는 서버 (Google)

Resource Owner

Resource Server(Google)이 제공하는 정보를 통해 Client(Inflearn)에 로그인하는 유저


카카오 로그인

기본 구조

카카오 디벨로퍼 설정

카카오 디벨로퍼 내 어플리케이션 생성

카카오디벨로퍼

카카오 로그인 활성화

Redirect URI 설정

  • 사용자가 카카오 로그인을 진행했을 때 인가 code를 반환받을 URL

동의항목 설정

위 설정을 모두 마치면

kauth.kakao.com/oauth/authorize?client_id={REST_API_KEY}&redirect_uri={REDIRECT_URI}&response_type=code

해당 url로 접속 시 카카오 로그인 화면 출력


카카오 로그인 구현

AuthController 구현

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;

}

...
  • Front에서 해당 주소로 api 요청을 보내면 카카오 로그인 화면 url을 반환해줌
  • Front에서 사용자를 해당 url로 라우팅하고 사용자가 로그인을 완료하면 REDIRECT_URI로 인가 code가 넘어옴

KakaoController 구현

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;
    }
profile
제어할 수 없는 것에 의지하지 말자
post-custom-banner

0개의 댓글