프로젝트

[SpringBoot & JPA 프로젝트] 카카오 전체 오류로 인해 서비스 이용불가 => 네이버 로그인 API 적용

sian han 2022. 10. 17. 20:48

 

카카오 데이터센터에 화재가 나서 전체 카카오 서비스가 이용 불가한 상황이 발생했다.

 

뜨개장터는 카카오 로그인 만을 통해서 회원가입하여 서비스를 이용 할 수 있다.

따라서 카카오가 복원되기 전까지 뜨개장터는 아무도 이용할 수 없게 되었다.

 

(데이터센터 화재라니 .. 한 3일정도 소요될 것 이라고 예상했었다)

 

따라서 뜨개장터를 굴러가게 만들기 위해 최대한 빨리 네이버 로그인 API 를 적용시키기로 했다.

 

 


 

 

1. 애플리케이션 등록

  - 이름, 전화번호, 이메일 3가지를 가져오기로 선택했음

 

애플리케이션을 등록하면 즉시 Client ID 와 Clisent Secret 키가 발급된다.

 

발급된 키로는 테스트 로그인만 가능하다. (개발자 ID로 테스트 가능)

실사용을 위해서는 네이버로부터 검수를 받아야 한다.

 

 

 

 

2. 코드작성

  - 사용자로 로그인 예시

 

 

▷ js

//네이버 로그인 버튼 클릭시
    $('#naverLoginBtn').click(function(){
        var role = "";
        if ($('#checkbox_user').is(":checked")) {
            role = "user";
        } else {
            role = "seller";
        }
        var kakaoUrl = "https://nid.naver.com/oauth2.0/authorize?" +
            "response_type=code" +
            "&client_id={client_id}" +
            "&redirect_uri=http://knitmarket.shop/naverLogin/requestToken_" + role +
            "&state=STATE_STRING";
        location.href = kakaoUrl;
    });

 

▷ service

@Service
public class NaverLoginService {

//application.properties 로부터 Client ID 와 Clisent Secret 키 주입 받음
    @Value("${naverLogin.sk}")
    private String CLIENT_SECRET;

    @Value("${naverLogin.ck}")
    private String CLIENT_ID;

    public String getAccessToken(String code, String state) throws JsonProcessingException {
        // 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", CLIENT_ID);
        body.add("client_secret", CLIENT_SECRET);
        body.add("code", code);
        body.add("state", state);

        // HTTP 요청 보내기
        HttpEntity<MultiValueMap<String, String>> naverTokenRequest =
                new HttpEntity<>(body, headers);
        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://nid.naver.com/oauth2.0/token",
                HttpMethod.POST,
                naverTokenRequest,
                String.class
        );

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

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

        // HTTP 요청 보내기
        HttpEntity<MultiValueMap<String, String>> naverUserInfoRequest = new HttpEntity<>(headers);
        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://openapi.naver.com/v1/nid/me",
                HttpMethod.POST,
                naverUserInfoRequest,
                String.class
        );

        // HTTP 응답 받아오기
        String responseBody = response.getBody();
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(responseBody);

        JsonNode jsonResponse = jsonNode.get("response");
        return jsonResponse;
    }
}

 

▷ controller

@RequestMapping("/naverLogin/requestToken_user")
    public String authNaver_user(@RequestParam String code, @RequestParam String state,
                            Model model, HttpServletRequest request,
                            HttpServletResponse response)
            throws JsonProcessingException {
        String role = "user";
        String accessToken = naver.getAccessToken(code, state);
        JsonNode json = naver.getNaverUserInfo(accessToken);

        String id = json.get("id").asText();
        String name = json.get("name").asText();
        String email = json.get("email").asText();
        String hp = json.get("mobile").asText().replace("-", "");
        log.info("id={}, name={}, email={}, mobile={}", id, name, email, hp);

        UserRequestDto userRequestDto = null;
        Long userId;
        String url="/";
        String msg = "";

        //db 중복 확인
        if (!userService.existsByEmail(email)) { //신규가입
            userRequestDto = new UserRequestDto(email, name, hp, id);
            User user = userService.save (userRequestDto);
            userId = user.getId();

            log.info("신규회원가입 회원번호={}", userId);

            //장바구니 생성
            CartDto createCartDto = CartDto.builder()
                    .user(user)
                    .build();

            Cart cart = cartRepository.save(createCartDto.toEntity());
            msg =name+"님, 회원가입을 축하드립니다 !";

        }else{ //기존회원로그인
            userId = userService.findByEmail(email).getId();
            log.info("기존회원 회원번호 ={}",userId);
            msg =name+"님, [사용자] 로그인되었습니다";
        }
        
        //세션저장
        HttpSession session=request.getSession();
        session.setAttribute("id", userId);
        session.setAttribute("email", email);
        session.setAttribute("access_Token",id);
        session.setAttribute("name", name);
        session.setAttribute("role",role);

        model.addAttribute("url",url);
        model.addAttribute("msg",msg);

        return "common/message";
    }

 

 

 

 

 

3. 검수요청

검수요청을 할 때

네이버에서 받아온 정보들을 사용자가 확인할 수 있어야 하는 조건이 있어

내 정보에서 전화번호를 확인할 수 있도록 수정했다

 

 

 

 

검수요청을 하면 아래와 같이 상태가 변경된다.

 

 

 

 

 

4. 테스트 로그인

검수를 기다리는 동안 로컬에서 로그인 테스트를 해보기로 한다

오류가 발생한다

리다이렉트 url 을 https 로 설정해서 발생했던 오류였다.

http://localhost:8080/ 로 변경하니 해결됨

 

정상적으로 회원가입 창이 뜬다.

 

이제 소셜 로그인 방법이 2개 되었으니

어떤 소셜 계정을 통해 회원가입했는지를 구분하기 위해서 소셜 로그인키를 회원정보에 함께 저장하기로 한다.

social_private_key 이름으로 컬럼을 생성했다.

 

다만, 이 상태로는 한 회원이 카카오 / 네이버 두개 계정을 사용해 중복 가입 할 수 있다.

네이버 계정으로 상품을 구매하고 카카오 계정으로 로그인해서 구매내역을 확인하면

다른 계정으로 인식되어 구매내역이 없음으로 뜨기 때문에 사용자가 혼란스러울 여지가 있다. 

 

사용자가 문의하면 다른 계정으로 로그인 해서 구매내역을 확인하라고 하면 해결할 수 있는 문제이기 때문에

우선 이대로 배포는 할 수 있겠지만, 이중 가입이 안되도록 하는 방안을 생각해봐야겠다. 

 

 

 

 

5. 검수완료