-
Notifications
You must be signed in to change notification settings - Fork 112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
경북대 BE_김은선 5주차 과제 (1단계) #339
Changes from all commits
774bbff
5d1bd22
419033c
8a612db
24ea912
2532a37
f8ce4ba
819fb3b
25c0f52
bd31dcd
a7b13b9
24fb815
357eba9
f14fe59
7b8d9eb
41355da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package gift.controller; | ||
|
||
import gift.entity.Member; | ||
import gift.exception.MemberNotFoundException; | ||
import gift.service.KakaoService; | ||
import gift.util.KakaoProperties; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.ui.Model; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
|
||
@Controller | ||
public class KakaoLoginController { | ||
|
||
private final KakaoProperties kakaoProperties; | ||
private final KakaoService kakaoService; | ||
|
||
@Autowired | ||
public KakaoLoginController(KakaoProperties kakaoProperties, KakaoService kakaoService) { | ||
this.kakaoProperties = kakaoProperties; | ||
this.kakaoService = kakaoService; | ||
} | ||
@GetMapping("/kakao/login") | ||
public String login(Model model) { | ||
model.addAttribute("kakaoClientId", kakaoProperties.clientId()); | ||
model.addAttribute("kakaoRedirectUrl", kakaoProperties.redirectUrl()); | ||
return "kakaoLogin"; | ||
} | ||
|
||
@GetMapping("/kakao/oauth2/callback") | ||
public String callbackKakao(@RequestParam String code, Model model) { | ||
|
||
try { | ||
String accessToken = kakaoService.login(code); | ||
model.addAttribute("accessToken", accessToken); | ||
return "home"; | ||
} catch (MemberNotFoundException e) { | ||
model.addAttribute("member", new Member()); // Member 객체 추가 | ||
return "register"; | ||
} | ||
|
||
} | ||
|
||
@GetMapping("/kakao/loginSuccess") | ||
public String loginSuccess() { | ||
return "kakaoLoginSuccess"; | ||
} | ||
|
||
|
||
|
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package gift.service; | ||
|
||
|
||
import gift.exception.MemberNotFoundException; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.http.HttpEntity; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.util.MultiValueMap; | ||
import org.springframework.web.client.RestTemplate; | ||
import org.springframework.web.util.UriComponentsBuilder; | ||
import org.springframework.http.*; | ||
import org.json.JSONObject; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
@Service | ||
public class KakaoService { | ||
|
||
@Value("${kakao.client-id}") | ||
private String clientId; | ||
|
||
@Value("${kakao.redirect-url}") | ||
private String redirectUri; | ||
|
||
@Autowired | ||
private RestTemplate restTemplate; | ||
|
||
public String getKakaoAccessToken(String code) { | ||
RestTemplate restTemplate = new RestTemplate(); | ||
|
||
String url = UriComponentsBuilder.fromHttpUrl("https://kauth.kakao.com/oauth/token") | ||
.queryParam("grant_type", "authorization_code") | ||
.queryParam("client_id", clientId) | ||
.queryParam("redirect_uri", redirectUri) | ||
.queryParam("code", code) | ||
.toUriString(); | ||
|
||
Map<String, String> response = restTemplate.postForObject(url, null, HashMap.class); | ||
|
||
return response.get("access_token"); | ||
Comment on lines
+42
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 토큰을 이런식으로 빼셨군요 👍🏼 |
||
} | ||
|
||
public JSONObject getUserInfo(String accessToken) { | ||
String reqURL = "https://kapi.kakao.com/v2/user/me"; | ||
|
||
HttpHeaders headers = new HttpHeaders(); | ||
headers.add("Authorization", "Bearer " + accessToken); | ||
|
||
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(headers); | ||
ResponseEntity<String> response = restTemplate.exchange(reqURL, HttpMethod.GET, request, String.class); | ||
|
||
if (response.getStatusCode() == HttpStatus.OK) { | ||
return new JSONObject(response.getBody()); | ||
} | ||
|
||
throw new RuntimeException("Failed to get user info from Kakao"); | ||
} | ||
|
||
public String login(String code) throws MemberNotFoundException { | ||
String accessToken = getKakaoAccessToken(code); | ||
JSONObject userInfo = getUserInfo(accessToken); | ||
|
||
String email= null; | ||
if (userInfo.has("kakao_account")) { | ||
JSONObject kakaoAccount = userInfo.getJSONObject("kakao_account"); | ||
if (kakaoAccount.has("email")) { | ||
email = kakaoAccount.getString("email"); | ||
} | ||
} | ||
|
||
if (email == null) { | ||
throw new MemberNotFoundException("Email not found"); | ||
} | ||
// 로그인 성공 처리 | ||
return accessToken; | ||
} | ||
Comment on lines
+63
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. getKakaoAccessToken에서 리프레시토큰은 따로 가져오지 않아도 괜찮을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 토큰 두 개 모두 받아오는 객체를 새로 정의해서 받아오도록 수정했습니다! |
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package gift.util; | ||
|
||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
|
||
@ConfigurationProperties("kakao") | ||
public record KakaoProperties( | ||
String clientId, | ||
String redirectUrl | ||
) {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
kakao.client-id=7996810f652c133a85581783a17c69ff | ||
kakao.redirect-url=http://localhost:8000/kakao/oauth2/callback | ||
kakao.client-secret=aaaa |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<!DOCTYPE html> | ||
<html xmlns:th="http://www.thymeleaf.org"> | ||
<head> | ||
<title>Kakao Login</title> | ||
</head> | ||
<body> | ||
<h1>Kakao Login</h1> | ||
<a th:href="@{|https://kauth.kakao.com/oauth/authorize?client_id=${kakaoClientId}&redirect_uri=${kakaoRedirectUrl}&response_type=code&scope=account_email|}"> | ||
Login with Kakao | ||
</a> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<!DOCTYPE html> | ||
<html xmlns:th="http://www.thymeleaf.org"> | ||
<head> | ||
<title>Kakao Login Success</title> | ||
</head> | ||
<body> | ||
<h1>Kakao Login Success</h1> | ||
<p>Access Token: <span th:text="${accessToken}"></span></p> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<!DOCTYPE html> | ||
<html xmlns:th="http://www.thymeleaf.org"> | ||
<head> | ||
<title>Login Failure</title> | ||
</head> | ||
<body> | ||
<h1>Login Failure</h1> | ||
<p>Something went wrong. Please try again.</p> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package gift; | ||
|
||
import gift.service.KakaoService; | ||
import gift.exception.MemberNotFoundException; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.context.SpringBootTest; | ||
import org.springframework.boot.test.web.client.TestRestTemplate; | ||
import org.springframework.boot.test.web.server.LocalServerPort; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.mockito.Mockito.when; | ||
import org.springframework.boot.test.mock.mockito.MockBean; | ||
|
||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) | ||
public class KakaoControllerTest { | ||
|
||
@LocalServerPort | ||
private int port; | ||
|
||
@Autowired | ||
private TestRestTemplate restTemplate; | ||
|
||
@MockBean | ||
private KakaoService kakaoService; | ||
|
||
// "/kakao/login" 엔드포인트에 GET 요청 | ||
@Test | ||
public void testLogin() { | ||
String url = "http://localhost:" + port + "/kakao/login"; | ||
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class); | ||
|
||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); | ||
} | ||
|
||
// 유효한 코드로 카카오 로그인을 시도 | ||
@Test | ||
public void testCallbackKakaoSuccess() { | ||
String code = "valid_code"; | ||
String accessToken = "valid_access_token"; | ||
|
||
when(kakaoService.login(code)).thenReturn(accessToken); | ||
|
||
String url = "http://localhost:" + port + "/kakao/oauth2/callback?code=" + code; | ||
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class); | ||
|
||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); | ||
} | ||
|
||
// 회원을 찾을 수 없는 경우 | ||
@Test | ||
public void testCallbackKakaoMemberNotFound() { | ||
String code = "valid_code"; | ||
|
||
when(kakaoService.login(code)).thenThrow(MemberNotFoundException.class); // 메시지 없이 예외 타입만 설정 | ||
|
||
String url = "http://localhost:" + port + "/kakao/oauth2/callback?code=" + code; | ||
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class); | ||
|
||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); | ||
assertThat(response.getBody()).contains("register"); | ||
} | ||
Comment on lines
+30
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 로그인 테스트 👍🏼 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package gift; | ||
|
||
import gift.util.KakaoProperties; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
import org.springframework.boot.test.context.SpringBootTest; | ||
import org.springframework.boot.web.client.RestTemplateBuilder; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.HttpMethod; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.http.RequestEntity; | ||
import org.springframework.test.context.ActiveProfiles; | ||
import org.springframework.util.LinkedMultiValueMap; | ||
import org.springframework.web.client.RestTemplate; | ||
|
||
import java.net.URI; | ||
|
||
@ActiveProfiles("secret") | ||
@SpringBootTest | ||
class RestTemplateTest { | ||
private final RestTemplate client = new RestTemplateBuilder().build(); | ||
|
||
@Autowired | ||
private KakaoProperties properties; | ||
|
||
@ConfigurationProperties | ||
|
||
|
||
@Test | ||
void test1(){ | ||
var url = "https://kauth.kakao.com/oauth/token"; | ||
var code = "MZqnpa_Ch0moTrUURmm4PEi74CgD3sYdPdG1cJU4nnv4ilOioKXq5AAAAAQKPCQfAAABkPMfVqjNsk3jZ7dWzg"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 코드가 여기 있어도 괜찮을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵 수정하고 서비스 내에서도 url 사용하도록 바꿨습니다! |
||
var headers = new HttpHeaders(); | ||
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); | ||
var body = new LinkedMultiValueMap<String, String>(); | ||
body.add("grant_type", "authorization_code"); | ||
body.add("client_id", properties.clientId()); | ||
body.add("redirect_uri", properties.redirectUrl()); | ||
body.add("code", code); | ||
var request = new RequestEntity<>(body, headers, HttpMethod.POST, URI.create(url)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이렇게 전부 kakao를 prefix path로 사용한다면 컨트롤러 자체에 path 등록해두는게 좋지 않을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 @RequestMapping("/kakao") 로 따로 뺐습니다!