SpringBoot2로 Rest api 만들기(10) – Social Login 연동(kakao)
- UserService name change : KakaoService - Social signin, signup Test
This commit is contained in:
@@ -1,14 +1,11 @@
|
|||||||
package com.rest.api.controller.common;
|
package com.rest.api.controller.common;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.rest.api.model.social.RetKakaoAuth;
|
import com.rest.api.service.social.KakaoService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.http.*;
|
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
@@ -21,10 +18,9 @@ import org.springframework.web.servlet.ModelAndView;
|
|||||||
public class SocialController {
|
public class SocialController {
|
||||||
|
|
||||||
private final Environment env;
|
private final Environment env;
|
||||||
|
|
||||||
private final RestTemplate restTemplate;
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
private final Gson gson;
|
private final Gson gson;
|
||||||
|
private final KakaoService kakaoService;
|
||||||
|
|
||||||
@Value("${spring.url.base}")
|
@Value("${spring.url.base}")
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
@@ -57,22 +53,7 @@ public class SocialController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping(value = "/kakao")
|
@GetMapping(value = "/kakao")
|
||||||
public ModelAndView redirectKakao(ModelAndView mav, @RequestParam String code) {
|
public ModelAndView redirectKakao(ModelAndView mav, @RequestParam String code) {
|
||||||
// Set header : Content-type: application/x-www-form-urlencoded
|
mav.addObject("authInfo", kakaoService.getKakaoTokenInfo(code));
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
||||||
// Set parameter
|
|
||||||
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
|
|
||||||
params.add("grant_type", "authorization_code");
|
|
||||||
params.add("client_id", kakaoClientId);
|
|
||||||
params.add("redirect_uri", baseUrl + kakaoRedirect);
|
|
||||||
params.add("code", code);
|
|
||||||
// Set http entity
|
|
||||||
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
|
|
||||||
ResponseEntity<String> response = restTemplate.postForEntity(env.getProperty("spring.social.kakao.url.token"), request, String.class);
|
|
||||||
if (response.getStatusCode() == HttpStatus.OK) {
|
|
||||||
RetKakaoAuth authInfo = gson.fromJson(response.getBody(), RetKakaoAuth.class);
|
|
||||||
mav.addObject("authInfo", authInfo);
|
|
||||||
}
|
|
||||||
mav.setViewName("social/redirectKakao");
|
mav.setViewName("social/redirectKakao");
|
||||||
return mav;
|
return mav;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.rest.api.controller.v1;
|
package com.rest.api.controller.v1;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.rest.api.advice.exception.CEmailSigninFailedException;
|
import com.rest.api.advice.exception.CEmailSigninFailedException;
|
||||||
import com.rest.api.advice.exception.CUserExistException;
|
import com.rest.api.advice.exception.CUserExistException;
|
||||||
import com.rest.api.advice.exception.CUserNotFoundException;
|
import com.rest.api.advice.exception.CUserNotFoundException;
|
||||||
@@ -11,15 +10,13 @@ import com.rest.api.model.response.SingleResult;
|
|||||||
import com.rest.api.model.social.KakaoProfile;
|
import com.rest.api.model.social.KakaoProfile;
|
||||||
import com.rest.api.repo.UserJpaRepo;
|
import com.rest.api.repo.UserJpaRepo;
|
||||||
import com.rest.api.service.ResponseService;
|
import com.rest.api.service.ResponseService;
|
||||||
import com.rest.api.service.user.UserService;
|
import com.rest.api.service.social.KakaoService;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import io.swagger.annotations.ApiParam;
|
import io.swagger.annotations.ApiParam;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.core.env.Environment;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -34,10 +31,7 @@ public class SignController {
|
|||||||
private final JwtTokenProvider jwtTokenProvider;
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
private final ResponseService responseService;
|
private final ResponseService responseService;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
private final RestTemplate restTemplate;
|
private final KakaoService kakaoService;
|
||||||
private final Environment env;
|
|
||||||
private final Gson gson;
|
|
||||||
private final UserService userService;
|
|
||||||
|
|
||||||
@ApiOperation(value = "로그인", notes = "이메일 회원 로그인을 한다.")
|
@ApiOperation(value = "로그인", notes = "이메일 회원 로그인을 한다.")
|
||||||
@PostMapping(value = "/signin")
|
@PostMapping(value = "/signin")
|
||||||
@@ -57,7 +51,7 @@ public class SignController {
|
|||||||
@ApiParam(value = "서비스 제공자 provider", required = true, defaultValue = "kakao") @PathVariable String provider,
|
@ApiParam(value = "서비스 제공자 provider", required = true, defaultValue = "kakao") @PathVariable String provider,
|
||||||
@ApiParam(value = "소셜 access_token", required = true) @RequestParam String accessToken) {
|
@ApiParam(value = "소셜 access_token", required = true) @RequestParam String accessToken) {
|
||||||
|
|
||||||
KakaoProfile profile = userService.getKakaoProfile(accessToken);
|
KakaoProfile profile = kakaoService.getKakaoProfile(accessToken);
|
||||||
User user = userJpaRepo.findByUidAndProvider(String.valueOf(profile.getId()), provider).orElseThrow(CUserNotFoundException::new);
|
User user = userJpaRepo.findByUidAndProvider(String.valueOf(profile.getId()), provider).orElseThrow(CUserNotFoundException::new);
|
||||||
return responseService.getSingleResult(jwtTokenProvider.createToken(String.valueOf(user.getMsrl()), user.getRoles()));
|
return responseService.getSingleResult(jwtTokenProvider.createToken(String.valueOf(user.getMsrl()), user.getRoles()));
|
||||||
}
|
}
|
||||||
@@ -83,9 +77,9 @@ public class SignController {
|
|||||||
@ApiParam(value = "소셜 access_token", required = true) @RequestParam String accessToken,
|
@ApiParam(value = "소셜 access_token", required = true) @RequestParam String accessToken,
|
||||||
@ApiParam(value = "이름", required = true) @RequestParam String name) {
|
@ApiParam(value = "이름", required = true) @RequestParam String name) {
|
||||||
|
|
||||||
KakaoProfile profile = userService.getKakaoProfile(accessToken);
|
KakaoProfile profile = kakaoService.getKakaoProfile(accessToken);
|
||||||
Optional<User> user = userJpaRepo.findByUidAndProvider(String.valueOf(profile.getId()), provider);
|
Optional<User> user = userJpaRepo.findByUidAndProvider(String.valueOf(profile.getId()), provider);
|
||||||
if(user.isPresent())
|
if (user.isPresent())
|
||||||
throw new CUserExistException();
|
throw new CUserExistException();
|
||||||
|
|
||||||
User inUser = User.builder()
|
User inUser = User.builder()
|
||||||
|
|||||||
@@ -2,15 +2,18 @@ package com.rest.api.model.social;
|
|||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
@ToString
|
||||||
public class KakaoProfile {
|
public class KakaoProfile {
|
||||||
private Long id;
|
private Long id;
|
||||||
private Properties properties;
|
private Properties properties;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
@ToString
|
||||||
private static class Properties {
|
private static class Properties {
|
||||||
private String nickname;
|
private String nickname;
|
||||||
private String thumbnail_image;
|
private String thumbnail_image;
|
||||||
|
|||||||
70
src/main/java/com/rest/api/service/social/KakaoService.java
Normal file
70
src/main/java/com/rest/api/service/social/KakaoService.java
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package com.rest.api.service.social;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.rest.api.advice.exception.CCommunicationException;
|
||||||
|
import com.rest.api.model.social.KakaoProfile;
|
||||||
|
import com.rest.api.model.social.RetKakaoAuth;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.http.*;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Service
|
||||||
|
public class KakaoService {
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
private final Environment env;
|
||||||
|
private final Gson gson;
|
||||||
|
|
||||||
|
@Value("${spring.url.base}")
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
|
@Value("${spring.social.kakao.client_id}")
|
||||||
|
private String kakaoClientId;
|
||||||
|
|
||||||
|
@Value("${spring.social.kakao.redirect}")
|
||||||
|
private String kakaoRedirect;
|
||||||
|
|
||||||
|
public KakaoProfile getKakaoProfile(String accessToken) {
|
||||||
|
// Set header : Content-type: application/x-www-form-urlencoded
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
headers.set("Authorization", "Bearer " + accessToken);
|
||||||
|
|
||||||
|
// Set http entity
|
||||||
|
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(null, headers);
|
||||||
|
try {
|
||||||
|
// Request profile
|
||||||
|
ResponseEntity<String> response = restTemplate.postForEntity(env.getProperty("spring.social.kakao.url.profile"), request, String.class);
|
||||||
|
if (response.getStatusCode() == HttpStatus.OK)
|
||||||
|
return gson.fromJson(response.getBody(), KakaoProfile.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new CCommunicationException();
|
||||||
|
}
|
||||||
|
throw new CCommunicationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RetKakaoAuth getKakaoTokenInfo(String code) {
|
||||||
|
// Set header : Content-type: application/x-www-form-urlencoded
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
// Set parameter
|
||||||
|
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
|
||||||
|
params.add("grant_type", "authorization_code");
|
||||||
|
params.add("client_id", kakaoClientId);
|
||||||
|
params.add("redirect_uri", baseUrl + kakaoRedirect);
|
||||||
|
params.add("code", code);
|
||||||
|
// Set http entity
|
||||||
|
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
|
||||||
|
ResponseEntity<String> response = restTemplate.postForEntity(env.getProperty("spring.social.kakao.url.token"), request, String.class);
|
||||||
|
if (response.getStatusCode() == HttpStatus.OK) {
|
||||||
|
return gson.fromJson(response.getBody(), RetKakaoAuth.class);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package com.rest.api.service.user;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.rest.api.advice.exception.CCommunicationException;
|
|
||||||
import com.rest.api.model.social.KakaoProfile;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.core.env.Environment;
|
|
||||||
import org.springframework.http.*;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Service
|
|
||||||
public class UserService {
|
|
||||||
|
|
||||||
private final RestTemplate restTemplate;
|
|
||||||
private final Environment env;
|
|
||||||
private final Gson gson;
|
|
||||||
|
|
||||||
public KakaoProfile getKakaoProfile(String accessToken) {
|
|
||||||
// Set header : Content-type: application/x-www-form-urlencoded
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
||||||
headers.set("Authorization", "Bearer " + accessToken);
|
|
||||||
|
|
||||||
// Set http entity
|
|
||||||
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(null, headers);
|
|
||||||
ResponseEntity<String> response = restTemplate.postForEntity(env.getProperty("spring.social.kakao.url.profile"), request, String.class);
|
|
||||||
if (response.getStatusCode() == HttpStatus.OK)
|
|
||||||
return gson.fromJson(response.getBody(), KakaoProfile.class);
|
|
||||||
else
|
|
||||||
throw new CCommunicationException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<button onclick="popupKakaoLogin()">KakaoLogin</button>
|
<button onclick="popupKakaoLogin()">KakaoLogin</button>
|
||||||
<script>
|
<script>
|
||||||
function popupKakaoLogin() {
|
function popupKakaoLogin() {
|
||||||
window.open('${loginUrl}', 'popupKakaoLogin', 'width=300,height=500,scrollbars=0,toolbar=0,menubar=no')
|
window.open('${loginUrl}', 'popupKakaoLogin', 'width=700,height=500,scrollbars=0,toolbar=0,menubar=no')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
package com.rest.api.controller.v1;
|
package com.rest.api.controller.v1;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.context.WebApplicationContext;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
@@ -86,4 +83,39 @@ public class SignControllerTest {
|
|||||||
.andExpect(jsonPath("$.success").value(false))
|
.andExpect(jsonPath("$.success").value(false))
|
||||||
.andExpect(jsonPath("$.code").value(-9999));
|
.andExpect(jsonPath("$.code").value(-9999));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void signInProviderFail() throws Exception {
|
||||||
|
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
|
||||||
|
params.add("accessToken", "XXXXXXXX");
|
||||||
|
mockMvc.perform(post("/v1/signin/kakao").params(params))
|
||||||
|
.andDo(print())
|
||||||
|
.andExpect(status().is5xxServerError())
|
||||||
|
.andExpect(jsonPath("$.success").value(false))
|
||||||
|
.andExpect(jsonPath("$.code").value(-1004));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void signUpProvider() throws Exception {
|
||||||
|
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
|
||||||
|
params.add("accessToken", "HizF3ir9522bMW3shkO0x0T9zBdXFCW1WsF56Qo9dVsAAAFqMwTqHw");
|
||||||
|
params.add("name", "kakaoKing!");
|
||||||
|
mockMvc.perform(post("/v1/signup/kakao").params(params))
|
||||||
|
.andDo(print())
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.success").value(true))
|
||||||
|
.andExpect(jsonPath("$.code").value(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void signInProvider() throws Exception {
|
||||||
|
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
|
||||||
|
params.add("accessToken", "HizF3ir9522bMW3shkO0x0T9zBdXFCW1WsF56Qo9dVsAAAFqMwTqHw");
|
||||||
|
mockMvc.perform(post("/v1/signin/kakao").params(params))
|
||||||
|
.andDo(print())
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.success").value(true))
|
||||||
|
.andExpect(jsonPath("$.code").value(0))
|
||||||
|
.andExpect(jsonPath("$.data").exists());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.rest.api.service.social;
|
||||||
|
|
||||||
|
import com.rest.api.model.social.KakaoProfile;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest
|
||||||
|
public class KakaoServiceTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private KakaoService kakaoService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenGetKakaoProfile_thenReturnProfile() {
|
||||||
|
|
||||||
|
String accessToken = "xjsMzpQtIr4w13FIQvL3R7BW7X4yvm1KmzXCTwopyWAAAAFqMxEcwA";
|
||||||
|
// given
|
||||||
|
KakaoProfile profile = kakaoService.getKakaoProfile(accessToken);
|
||||||
|
// then
|
||||||
|
assertNotNull(profile);
|
||||||
|
assertEquals(profile.getId(), Long.valueOf(1066788171));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user