메시지 api 와 목록 api 를 사용하기 위해선 사용자 권한 신청이 필요하다.
application test 단계에서는 팀원
권한을 가져야만 메시지 발송이 가능하다.
팀원
뿐 아니라 모든 사용자가 메시지 기능을 사용하려면 카카오톡 소셜 사용 권한 신청
이 완료되야 한다.개인정보 동의 항목을 참고해 필요한 기능의 동이 항복을 추가한다.
인가코드
을 받아야 했다.인가코드
는 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);
}
}
Comman
+ Shift
+ R
로 url 을 검색해 봤는데 매핑하는 것 처럼 보이는 코드를 찾을 수 없었다.SecurityConfig
객체에서 filterChain
메서드를 통해 작업을 할 수 있지 않을까 생각 이들었다.oauth2Login
에서 redirectionEndpoint()
를 추가해 redirect url 을 설정할 수 있다는 사실을 알았지만 redirect url 은 이미 application yml 에 설정해주어서 의미가 없을것 같다.@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();
}
...
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;
}
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);
}
}
@Data
public class KakaoMessageDto {
private String objType;
private String text;
private String webUrl;
private String mobileUrl;
private String btnTitle;
}
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);
}
}
HttpEntity
에 담아 HttpCallService
객체를 통해 http 메시지를 카카오 서버에 보내는 객체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;
}
}
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 를 더 편리하게 요청 응답 한다고 한다.