본문 바로가기

TIL

231128_TIL

  • 오늘한일
  • 카카오 로그인 구현하기

 

 

소셜 로그인 탄생 배경

  • 모든 웹 사이트에서 회원가입 과정을 거치는 것은 사용자에게 부담이 됩니다.
  • 매번 번거로운 회원가입 과정을 수행해야 할 뿐 아니라, 웹 사이트마다 다른 아이디와 비밀번호를 기억해야 합니다.
  •  웹 사이트를 운영하는 측에서도 회원들의 개인정보를 지켜야하는 역할이 부담이 됩니다.
  • 바이러스와 백신의 관계 처럼, 발전하는 해킹 기술을 막기 위해 보안을 강화하는 노력이 지속적으로 필요하기 때문이죠.
  • 이런 문제를 해결하기 위해 OAuth 를 사용한 소셜 로그인이 등장합니다.

OAuth탄생 배경

  • "A" 서비스에서 카카오의 정보(리소스)를 가져오기 위해서는 카카오의 ID PW를 직접 입력 받아서
  • "A" 서비스에 저장해서 필요할때마다 불러와서 사용을 해야했는데.

이렇게 사용하면

1. 사용자는 'A' 서비스에  카카오의 ID와 PW를 넘겨주는 것에 대해 신뢰할 수 없다.

2. 'A' 서비스는 카카오의 ID와 PW를 저장하기 때문에 보안 문제가 생기는 경우 모든 책임을 져야한다.

3. 카카오는  'A' 어플리케이션을 신뢰할 수 없다

라는 문제가 생깁니다.

이문제를 해결하기 위해 OAuth를 도입하여 인증을 외부 어플리케이션에 위임하여 처리하도록 해결합니다.

OAuth 란?

  • OAuth의 사전적 정의는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹 사이트 상의 자신들의 정보에 대해 웹 사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는 접근 위임을 위한 개방형 표준이다.
  • OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준입니다. 사용자가 애플리케이션에게 모든 권한을 넘기지 않고 사용자 대신 서비스를 이용할 수 있게 해주는 HTTP 기반의 보안 프로토콜 입니다. OAuth를 사용하는 서비스 제공자는 대표적으로 구글, 페이스북 등이 있습니다. 국내에는 대표적으로 네이버와 카카오가 있습니다.

OAuth 2.0 용어 (구성 요소)

ex)

 

애플,페이스북,구글,카카오,네이버,트위터 등 제공한다.

 

 

 

 

카카오 로그인 사용 승인 받기

1. 내 애플리케이션 메뉴 선택 > 애플리케이션 추가하기

2. 앱 아이콘, 앱 이름, 사업자명 저장

3. 사이트 도메인 등록하기

  • 애플리케이션 선택
  • 플랫폼 메뉴 선택 > 플랫폼 설정하기 클릭
  • Web 플랫폼 등록
  • 사이트 도메인 입력

4. 카카오로 로그인 했을 때 인가토큰을 받게 될 Redirect URI (callback) 를 설정하기

5. 동의항목 설정하기

 

 

 

 

카카오 사용자 정보 가져오기

카카오 인가코드 받기

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

 

 

 

카카오에서 보내주는 '인가코드' 처리

UserController

@GetMapping("/user/kakao/callback")
public String kakaoLogin(@RequestParam String code, HttpServletResponse response) throws JsonProcessingException {
    // code: 카카오 서버로부터 받은 인가 코드 Service 전달 후 인증 처리 및 JWT 반환
    String token = kakaoService.kakaoLogin(code);

    // Cookie 생성 및 직접 브라우저에 Set
    Cookie cookie = new Cookie(JwtUtil.AUTHORIZATION_HEADER, token.substring(7));
    cookie.setPath("/");
    response.addCookie(cookie);

    return "redirect:/";
}
  1. 카카오에서 보내주는 '인가코드'를 받음 ⇒ Controller
  2. '인가코드'를 가지고 카카오 로그인 처리 ⇒ Service
  3. 로그인 성공 시 "/" 으로 redirect ⇒ Controller

 

 

카카오 사용자 정보 가져오기

 

1."인가 코드"로 "액세스 토큰" 요청

private String getToken(String code) throws JsonProcessingException {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("https://kauth.kakao.com")
            .path("/oauth/token")
            .encode()
            .build()
            .toUri();

    // HTTP Header 생성
    HttpHeaders headers = new HttpHeaders();
    headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

    // HTTP Body 생성
    MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
    body.add("grant_type", "authorization_code");
    body.add("client_id", "본인의 REST API키");
    body.add("redirect_uri", "http://localhost:8080/api/user/kakao/callback");
    body.add("code", code);

    RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
            .post(uri)
            .headers(headers)
            .body(body);

    // HTTP 요청 보내기
    ResponseEntity<String> response = restTemplate.exchange(
            requestEntity,
            String.class
    );

    // HTTP 응답 (JSON) -> 액세스 토큰 파싱
    JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
    return jsonNode.get("access_token").asText();
}

2."액세스 토큰"으로 "카카오 사용자 정보" 가져오기

private KakaoUserInfoDto getKakaoUserInfo(String accessToken) throws JsonProcessingException {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("https://kapi.kakao.com")
            .path("/v2/user/me")
            .encode()
            .build()
            .toUri();

    // HTTP Header 생성
    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization", "Bearer " + accessToken);
    headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

    RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
            .post(uri)
            .headers(headers)
            .body(new LinkedMultiValueMap<>());

    // HTTP 요청 보내기
    ResponseEntity<String> response = restTemplate.exchange(
            requestEntity,
            String.class
    );

    JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
    Long id = jsonNode.get("id").asLong();
    String nickname = jsonNode.get("properties")
            .get("nickname").asText();
    String email = jsonNode.get("kakao_account")
            .get("email").asText();

    log.info("카카오 사용자 정보: " + id + ", " + nickname + ", " + email);
    return new KakaoUserInfoDto(id, nickname, email);
}

 

 

 

카카오 사용자 정보로 회원가입 구현

카카오 사용자 회원가입 설계

  • 카카오로 부터 받은 사용자 정보
    1. kakaoId
    2. nickname
    3. email

테이블 설계 옵션

  1. 카카오 User 를 위한 테이블 (ex. KakaoUser) 을 하나 더 만든다.
    1. 장점: 결합도가 낮아짐
      1. 성격이 다른 유저 별로 분리 → 차후 각 테이블의 변화에 서로 영향을 주지 않음
      2. 예) 카카오 사용자들만 profile_image 컬럼 추가해서 사용 가능
    2. 단점: 구현 난이도가 올라감
      1. 예) 관심상품 등록 시, 회원별로 다른 테이블을 참조해야 함
        1. 일반 회원: User - Product
        2. 카카오 회원: KakaoUser - Product
  2. 기존 회원 (User) 테이블에 카카오 User 추가
    1. 장점: 구현이 단순해짐
    2. 단점: 결합도가 높아짐

 

회원 (User) 테이블에 적용시킨 예)

  • 패스워드를 UUID 로 설정한 이유: 폼 로그인을 통해서 로그인되지 않기위해

 

 

 

  • 카카오 사용자 정보로 회원가입
    • User 테이블에 'kakaoId' 추가
  • 회원 가입 처리

-DB에 kakaoId 를 가진 회원이 없을 경우에만 신규로 회원 가입을 진행합니다.

public String kakaoLogin(String code, HttpServletResponse response) throws JsonProcessingException {
    // 1. "인가 코드"로 "액세스 토큰" 요청
    String accessToken = getToken(code);

    // 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
    KakaoUserInfoDto kakaoUserInfo = getKakaoUserInfo(accessToken);

    // 3. 필요시에 회원가입
    User kakaoUser = registerKakaoUserIfNeeded(kakaoUserInfo);

    // 4. JWT 토큰 반환
    String createToken =  jwtUtil.createToken(kakaoUser.getUsername(), kakaoUser.getRole());

    return createToken;
}
private User registerKakaoUserIfNeeded(KakaoUserInfoDto kakaoUserInfo) {
    // DB 에 중복된 Kakao Id 가 있는지 확인
    Long kakaoId = kakaoUserInfo.getId();
    User kakaoUser = userRepository.findByKakaoId(kakaoId).orElse(null);

    if (kakaoUser == null) {
        // 카카오 사용자 email 동일한 email 가진 회원이 있는지 확인
        String kakaoEmail = kakaoUserInfo.getEmail();
        User sameEmailUser = userRepository.findByEmail(kakaoEmail).orElse(null);
        if (sameEmailUser != null) {
            kakaoUser = sameEmailUser;
            // 기존 회원정보에 카카오 Id 추가
            kakaoUser = kakaoUser.kakaoIdUpdate(kakaoId);
        } else {
            // 신규 회원가입
            // password: random UUID
            String password = UUID.randomUUID().toString();
            String encodedPassword = passwordEncoder.encode(password);

            // email: kakao email
            String email = kakaoUserInfo.getEmail();

            kakaoUser = new User(kakaoUserInfo.getNickname(), encodedPassword, email, UserRoleEnum.USER, kakaoId);
        }

        userRepository.save(kakaoUser);
    }
    return kakaoUser;
}

 

느낀점

  • OAuth에 대해 알게 되었고 카카오 소셜 로그인 회원가입 적용법에 대해 알게 되었다,

'TIL' 카테고리의 다른 글

231130_TIL  (0) 2023.12.01
231129_TIL  (0) 2023.11.29
231117_TIL  (1) 2023.11.17
231110_TIL  (0) 2023.11.10
231109_TIL  (1) 2023.11.09