카카오 메시지 api 로 나에게 메시지 보내기

알파로그·2023년 4월 30일
0

🔗 카카오 메시지 공식 문서

✏️ 최종 목표

  • 친구 목록 가져오기 API 를 사용해 친구에게 메시지를 보내는 것이 최종 목표이다.
  • 카카오톡 메시지 API 를 사용하면 실제 서비스가 appliation 에서 일어나기 때문에 공유 API 를 사용하는 것 보다 조금 복잡하다.
    1. 친구 가져오기 API 호출
    2. 가져온 값을 정리해 application 에서 클라이언트에게 직접 랜더링
      • 친구목록을 직접 구현해야함
  • 공식문서를 살펴보면 친구목록 가져오기 API 를 요청하기 위해서 사용자 권한 신청이 필요하다고 한다.

📍 사용자 권한

🔗 메시지 사용 권한 신청 공식 문서

  • 메시지 api 와 목록 api 를 사용하기 위해선 사용자 권한 신청이 필요하다.

  • application test 단계에서는 팀원 권한을 가져야만 메시지 발송이 가능하다.

    • 서비스에서 팀원 뿐 아니라 모든 사용자가 메시지 기능을 사용하려면 카카오톡 소셜 사용 권한 신청 이 완료되야 한다.
  • 개인정보 동의 항목을 참고해 필요한 기능의 동이 항복을 추가한다.

✏️ access_token 발급받기

  • kakao developers 에서 설정이 끝났다면 access token 을 발급받아야 한다.
    • 조사해보니 토큰 발급 과정은 프론트 영역이지만 java 코드로도 가능하고 ssr 방식으로 프로젝트를 하고있기 때문에 java 코드로 구현해보기로 했다.

📍 인가코드 받기

  • 토큰을 받기위해선 먼저 카카오 서버에서 redirect url 로 보내주는 인가코드을 받아야 했다.
    • 인가코드 는 redirect url 에 파리미터 형식으로 반환해 준다고 한다.
    • 먼저 잘 작동되는지 확인해보기 위해 테스트용 컨트롤러를 만들었다.
@Slf4j
@RestController
@AllArgsConstructor
public class GetCodeController {

    @ResponseBody
    @GetMapping("/login/oauth2/code/kakao")
    public void getCode(
            @RequestParam String code
    ) {
        log.info("redirect url 매핑 성공 code = {}", code);
    }
}
@Slf4j
@RestController
@AllArgsConstructor
public class GetCodeController {

    @ResponseBody
    @GetMapping("/login/oauth2/code/kakao")
    public void getCode(
            @RequestParam String code
    ) {
        log.info("redirect url 매핑 성공 code = {}", code);
    }
}
  • 테스트 해보니 method 자체가 매핑되지 않았다.
    • 아무래도 일반적인 방법으로는 url 을 매핑할 수 없는것같다.

📍 url 매핑하기

  1. 문제를 생각해보다 내가 별도로 매핑하지 않아도 정상적으로 소셜로그인이 작동했다는 것은 어디선가 redirect url 을 매핑하고 있을거라 생각했다.
    • 그래서 Comman + Shift + R 로 url 을 검색해 봤는데 매핑하는 것 처럼 보이는 코드를 찾을 수 없었다.
  2. 다른 방법을 찾아보던중 SecurityConfig 객체에서 filterChain 메서드를 통해 작업을 할 수 있지 않을까 생각 이들었다.
    • oauth2Login 에서 redirectionEndpoint() 를 추가해 redirect url 을 설정할 수 있다는 사실을 알았지만 redirect url 은 이미 application yml 에 설정해주어서 의미가 없을것 같다.
    • SecurityConfig 객체
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    private final FailureHandler failureHandler;

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .formLogin(
                        formLogin -> formLogin
                                .loginPage("/member/login")
                )
                .formLogin(
                        loginFail -> loginFail
                                .failureHandler(failureHandler)
                )
                .oauth2Login(
                        oauth2Login -> oauth2Login
                                .loginPage("/member/login")
                )
                .logout(
                        logout -> logout
                                .logoutUrl("/member/logout")
                ).build();
    }

...

  1. 수업중에 만들었던 CustomOAuth2UserService 객체에서 소셜로그인 응답값들을 받았던걸 생각해서 변수들이 갖고있는 값을 찾아보다 토큰값을 발견했다.
    • OAuth2UserRequest userRequest 변수에서 getToken() 으로 인가코드 과정을 생략하고 바로 토큰값을 얻을 수 있었다.
@Service
@Transactional
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

		...

		private String token;

		@Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) {

        OAuth2User oAuth2User = super.loadUser(userRequest);
        String provider = userRequest.getClientRegistration().getRegistrationId().toUpperCase();

        switch (provider) {
            case "NAVER" -> naverPathing(oAuth2User);
            case "KAKAO" -> kakaoPathing(oAuth2User, userRequest);
            default -> username = oAuth2User.getName();
        }

				// member 를 생성할 때 tocken 값 추가
        Member member = memberService.whenSocialLogin(provider, username, nickName, email, token).getData();
        return new CustomOAuth2User(member.getUsername(), member.getPassword(), member.getGrantedAuthorities());
    }

		//-- 카카오 Json mapping --//
    private void kakaoPathing(OAuth2User oAuth2User, OAuth2UserRequest userRequest) {
        Map<String, Object> attributes = oAuth2User.getAttributes();
        Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
        Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");
        String nickname = (String) profile.get("nickname");
        String email = (String) kakaoAccount.get("email");

				// 토큰값을 구한 후 필드값에 주입
        this.token = userRequest.getAccessToken().getTokenValue();

        this.username = oAuth2User.getName();
        this.nickName = nickname;
        this.email = email;
    }

✏️ 본격적인 메시지 보내는 코드 구현하기

📍 HttpCallService

  • 카카오 서버로 보낼 http 메시지를 생성하는 객체이다.
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

public class HttpCallService {
    protected static final String APP_TYPE_URL_ENCODED = "application/x-www-form-urlencoded;charset=UTF-8";
    protected static final String APP_TYPE_JSON = "application/json;charset=UTF-8";

    //-- http 요청 클라이언트 객체 생성 --//
    public HttpEntity<?> httpClientEntity(HttpHeaders header, Object params) {
        HttpHeaders requestHeaders = header;

        if (params ==null || "".equals(params))
            return new HttpEntity<>(requestHeaders);
        else
            return new HttpEntity<>(params, requestHeaders);
    }

    //-- http 요청 메서드 --//
    public ResponseEntity<String> httpRequest(String url, HttpMethod method, HttpEntity<?> entity) {
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.exchange(url, method, entity, String.class);
    }
}

📍 KakaoMessageDto

  • 메시지에 포함될 내용을 전달하는 dto
@Data
public class KakaoMessageDto {

    private String objType;
    private String text;
    private String webUrl;
    private String mobileUrl;
    private String btnTitle;
}

📍 CustomMessageService

  • 메시지 dto 에 값을 추가하고 KakaoMessageService 를 호출해 메시지를 보내주는 객체
    • 변수값으로 토큰을 받는다.
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class CustomMessageService {

    private final KakaoMessageService service;

    public boolean sendMessage(String accessToken) {
        KakaoMessageDto myMsg = new KakaoMessageDto();
        myMsg.setBtnTitle("버튼");
        myMsg.setMobileUrl("");
        myMsg.setObjType("text");
        myMsg.setWebUrl("");
        myMsg.setText("메시지 테스트입니다.");

        return service.sendMessage(accessToken, myMsg);
    }
}

📍 KakaoMessageService

  • 준비된 값들을 Json 객체에 담아주고 HttpEntity 에 담아 HttpCallService 객체를 통해 http 메시지를 카카오 서버에 보내는 객체
    • 응답값을 받아서 성공여부를 확인해야 하는데 응답 메시지 바디값이 map 이 아닌 Stirng 값이라 json 으로 변환후, 코드 값을 꺼내는데 실패했다..
import org.json.simple.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

@Service
public class KakaoMessageService extends HttpCallService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());
    private static final String MSG_SEND_SERVICE_URL = "https://kapi.kakao.com/v2/api/talk/memo/default/send";
    private static final String SEND_SUCCESS_MSG = "메시지 전송에 성공했습니다.";
    private static final String SEND_FAIL_MSG = "메시지 전송에 실패했습니다.";

    //kakao api 에서 return 하는 success code 값
    private static final String SUCCESS_CODE = "0";

    public boolean sendMessage(String accessToken, KakaoMessageDto dto) {

        JSONObject linkObj = new JSONObject();
        linkObj.put("web_url", dto.getWebUrl());
        linkObj.put("mobile_web_url", dto.getMobileUrl());

        JSONObject templateObj = new JSONObject();
        templateObj.put("object_type", dto.getObjType());
        templateObj.put("text", dto.getText());
        templateObj.put("link", linkObj);
        templateObj.put("button_title", dto.getBtnTitle());

        HttpHeaders header = new HttpHeaders();
        header.set("Content-Type", APP_TYPE_URL_ENCODED);
        header.set("Authorization", "Bearer " + accessToken);

        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
        parameters.add("template_object", templateObj.toString());

        HttpEntity<?> messageRequestEntity = httpClientEntity(header, parameters);

        String resultCode = "";
        ResponseEntity<String> response = httpRequest(MSG_SEND_SERVICE_URL, HttpMethod.POST, messageRequestEntity);

				// http 응답 메시지 바디를 json 으로 변경한 뒤,
				// 결과 코드를 받아 성공 실패 여부를 확인해야 하는데,
				// 메시지 바디가 Map 타입이 아닌 String 타입으로 되어있어서 json 으로 파싱이 안된다 ㅠㅠ

//        JSONObject jsonData = new JSONObject(response.getBody());
//        resultCode = jsonData.get("result_code").toString();

        return true;
    }

		private boolean successCheck(String resultCode) {
        if (resultCode.equals(SUCCESS_CODE)) {
            logger.info(SEND_SUCCESS_MSG);
            return true;
        } else {
            logger.debug(SEND_FAIL_MSG);
            return false;
        }
    }

📍 KakaoMessageController

  • 로직을 작동시키기 위해서 url 을 매핑해 로직을 실행시키는 Controller
    • Rq 객체에서 로그인한 member 의 토큰값을 받아와 매개변수로 전달해준다.
    • 이제 CustomMessageService 와 토큰값만 있다면 모든 Controller method 에서 메시지 전송 기능을 작동시킬 수 있다.
@RestController
@RequiredArgsConstructor
public class KakaoMessageController {

    private final CustomMessageService msgService;
    private final Rq rq;

    @GetMapping("/kakao")
    public String sendMessage() {
        String accessToken = rq.getMember().getAccessToken();

        msgService.sendMessage(accessToken);
        return "메시지 전송 완료";
    }
}

⚠️ FeignClient 로 더 편리하게 HTTP 메시지 보내기

  • 이렇게 Reat Template 을 사용해 Http 메시지를 전송하는 방법은 예전 방식이고 요즘엔 FeignClient 방식으로 HTTP 를 더 편리하게 요청 응답 한다고 한다.
    🔗 FeignClient 사용방법
profile
잘못된 내용 PR 환영

0개의 댓글