본문 바로가기
프로젝트/쿠팡이츠 클론코딩

[외부 API] 카카오 소셜 로그인 API, 네이버 SENS SMS API

by 김긍수 2022. 3. 22.

1. 카카오 소셜 로그인 API

 

서버에서 클라이언트에게 액세스 토큰을 발급받아서 카카오로부터 사용자에 대한 정보를 얻어오고 해당 이메일이 서비스에 가입되어 있는지 검사를 하였다. 

[일반 회원가입 - 해당 이메일로 가입한 유저가 있는지 검사]

public PostUserRes createUser(PostUserReq postUserReq) throws BaseException {
        // 가입 중복 이메일 검증
        if(userProvider.checkEmail(postUserReq.getEmail()) == 1) {
            throw new BaseException(DUPLICATED_EMAIL);
        }
        if(userProvider.checkPhone(postUserReq.getPhone()) == 1) {
            throw new BaseException(DUPLICATED_PHONE);
        }

        String pwd;
        try{
            //비밀번호 암호화
            pwd = new AES128(Secret.USER_INFO_PASSWORD_KEY).encrypt(postUserReq.getPassword());
            postUserReq.setPassword(pwd);
        } catch (Exception ignored) {
            throw new BaseException(PASSWORD_ENCRYPTION_ERROR);
        }
        try{
            int userIdx = userDao.createUser(postUserReq, "INAPP"); // 가입유형 저장
            return new PostUserRes(userIdx);
        } catch (Exception exception) {
            throw new BaseException(DATABASE_ERROR);
        }
    }

해당 서비스에 가입하지않았는데 다른사람이 내 이메일로 가입한 경우가 있을 수 있다. 

이런 상황에서 사실상 다른 사람의 계정인데 이메일이 같다고 카카오 회원에게 jwt토큰을 줄 수 없다고 생각해서 User 테이블에 joinType이라는 컬럼을 추가하였다. 그리고 컬럼값은 [INAPP, KAKAO] 두 가지로 나누어 구분해주었다.

이걸로 이미 존재하는 이메일이 인앱가입인지, 카카오가입인지 구분해서 카카오 로그인으로 이어주거나 서비스 이메일로 로그인하라는 메시지를 전달해줄 수 있었다.

결론적으로 카카오 이메일이 INAPP타입으로 가입된 상태라면 서비스로그인, 가입되어있지않은 이메일인 경우에는 카카오에서 받은 이메일, 닉네임을 얻어와서 회원가입처리한 후 카카오 로그인으로 진행한 후 jwt 응답을 통해 진행을 이어주었다.

 

<<카카오 로그인 Controller와 Service단 코드>>

/**
     * 3. 카카오 로그인 API
     * [POST] /users/login/kakao
     */
    @ResponseBody
    @PostMapping("/login/kakao")
    public BaseResponse<PostLoginRes> kakaoLogin(@RequestBody PostKakaoLoginReq postKakaoLogin) {
        if (postKakaoLogin.getAccessToken() == null || postKakaoLogin.getAccessToken().isEmpty()) {
            return new BaseResponse<>(AUTH_KAKAO_EMPTY_TOKEN);
        }

        try {
            // 액세스 토큰으로 사용자 정보 받아온다.
            KaKaoUserInfo kaKaoUserInfo = KakaoApiService.getKakaoUserInfo(postKakaoLogin.getAccessToken());

            // 로그인 처리 or 회원가입 진행 후 jwt, userIdx 반환
            PostLoginRes postLoginRes = userProvider.kakaoLogin(kaKaoUserInfo);
            return new BaseResponse<>(postLoginRes);
        } catch (BaseException exception) {
            logger.warn("#3. " + exception.getStatus().getMessage());
            logger.warn(postKakaoLogin.toString());
            return new BaseResponse<>(exception.getStatus());
        }
    }

 

 

 

2. 네이버 SENS SMS API

네이버에서 제공하는 심플이지노티피케이션 서비스 API를 이용해서 SMS인증번호 발송 기능을 구현하였다.

이 기능은 회원가입 시, 휴대폰번호를 입력했는데 이미 다른 사람이 가입한 번호라면 자기 번호라고 인증하는 용도와 아이디찾기, 비밀번호 찾기에서 필요한 기능이어서 개발하였다.

 

 

 

 

 

 

 

 

 

 

 

 

 

@Service
public class SmsAuthService {
    private final UserDao userDao;

    @Autowired
    public SmsAuthService(UserDao userDao) {
        this.userDao = userDao;
    }

    public PhoneAuthInfo checkExistUserPhoneAndSendAuth(PostFindEmailAuthReq postFindEmailAuthReq) throws BaseException {
        if (userDao.checkUserByNameAndPhone(postFindEmailAuthReq.getUserName(), postFindEmailAuthReq.getPhone()) == 1) {
            return sendPhoneAuth(postFindEmailAuthReq.getPhone()); // 해당 이름과 전화번호로 가입된 회원이 있다면 인증번호를 발송한다.
        } else {
            throw new BaseException(USERS_NOT_FOUND_INFO); // 입력된 이름과 전화번호로 가입된 회원이 없음
        }
    }

    public PhoneAuthInfo sendPhoneAuth(String toPhone) throws BaseException {
        int authNumber = (int) (Math.random() * (99999 - 10000 + 1)) + 10000; // 인증번호 난수 생성
        String accessKey = SENS_ACCESS_KEY;
        String serviceId = SENS_SERVICE_ID;
        String method = "POST";
        String timestamp = Long.toString(System.currentTimeMillis());
        String tophone = toPhone;

        String requestURL = "https://sens.apigw.ntruss.com/sms/v2/services/" + serviceId + "/messages";

        //JSON을 활용한 doby data 생성
        JSONObject bodyJson = new JSONObject();
        JSONObject toJson = new JSONObject();
        JSONArray toArr = new JSONArray();

        toJson.put("to", tophone); // 메시지 수신자
        toArr.add(toJson);

        bodyJson.put("type", "sms");
        bodyJson.put("contentType", "comm");
        bodyJson.put("countryCode", "82");
        bodyJson.put("from", SENDERNUMBER);
        bodyJson.put("content", "[13th-쿠팡이츠] 인증번호\n" + authNumber);
        bodyJson.put("messages", toArr);

        String body = bodyJson.toJSONString();
        //System.out.println("body: : " + body);

        try {
            URL url = new URL(requestURL);

            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            con.setUseCaches(false);
            con.setDoOutput(true);
            con.setDoInput(true);
            con.setRequestProperty("content-type", "application/json; charset=utf-8");
            con.setRequestProperty("x-ncp-apigw-timestamp", timestamp);
            con.setRequestProperty("x-ncp-iam-access-key", accessKey);
            con.setRequestProperty("x-ncp-apigw-signature-v2", makeSignature(timestamp));
            con.setRequestMethod(method); // POST 방식
            con.setDoOutput(true);

            DataOutputStream wr = new DataOutputStream(con.getOutputStream());
            wr.write(body.getBytes());
            wr.flush();
            wr.close();

            int responseCode = con.getResponseCode();
            BufferedReader br;
            //System.out.println("responseCode : " + responseCode);

            if (responseCode == 202) { //정상
                br = new BufferedReader(new InputStreamReader(con.getInputStream()));
            } else {
                br = new BufferedReader(new InputStreamReader(con.getErrorStream()));
            }

            String inputLine;
            StringBuffer response = new StringBuffer();
            while ((inputLine = br.readLine()) != null) {
                response.append(inputLine);
            }

            br.close();
            con.disconnect();
            if (responseCode != 202) { // 정상이 아니라면
                throw new BaseException(FAILED_TO_SEND_PHONE_AUTH);
            }
            return new PhoneAuthInfo(tophone, String.valueOf(authNumber));
        } catch (Exception ignored) {
            throw new BaseException(FAILED_TO_SEND_PHONE_AUTH);
        }
    }
    private static String makeSignature(String timestamp) throws BaseException {
        String encodeBase64String = "";
        String space = " "; // one space
        String newLine = "\n"; // new line
        String method = "POST";
        String url = String.format("/sms/v2/services/%s/messages", SENS_SERVICE_ID);

        String message = new StringBuilder().append(method).append(space).append(url).append(newLine)
                .append(timestamp).append(newLine).append(SENS_ACCESS_KEY).toString();

        try {
            SecretKeySpec signingKey = new SecretKeySpec(SENS_SECRET_KEY.getBytes("UTF-8"), "HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(signingKey);

            byte[] rawHmac = mac.doFinal(message.getBytes("UTF-8"));
            encodeBase64String = java.util.Base64.getEncoder().encodeToString(rawHmac);
            return encodeBase64String;
        } catch (Exception ignored) {
            throw new BaseException(FAILED_TO_SEND_PHONE_AUTH);
        }
    }
}

 

댓글