-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
305 additions
and
1 deletion.
There are no files selected for viewing
33 changes: 33 additions & 0 deletions
33
src/main/java/org/swmaestro/repl/gifthub/auth/controller/GoogleController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package org.swmaestro.repl.gifthub.auth.controller; | ||
|
||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.web.bind.annotation.*; | ||
import org.swmaestro.repl.gifthub.auth.dto.GoogleDto; | ||
import org.swmaestro.repl.gifthub.auth.dto.TokenDto; | ||
import org.swmaestro.repl.gifthub.auth.service.GoogleService; | ||
|
||
@RestController | ||
@RequestMapping("/auth") | ||
@RequiredArgsConstructor | ||
@Tag(name = "Auth", description = "구글 로그인을 이용한 사용자 인증 관련 API") | ||
public class GoogleController { | ||
private final GoogleService googleService; | ||
|
||
@GetMapping("/google/callback") | ||
@Operation(summary = "구글 로그인 콜백 메서드", description = "구글 로그인 후 리다이렉트 되어 인가 코드를 출력하는 메서드입니다.") | ||
public String callback(@RequestParam String code) { | ||
return code; | ||
} | ||
|
||
@PostMapping("/google/sign-in") | ||
@Operation(summary = "구글 로그인 메서드", description = "구글로부터 사용자 정보를 얻어와 회원가입 및 로그인을 하기 위한 메서드입니다.") | ||
public TokenDto signIn(@RequestHeader("Authorization") String code) { | ||
code = code.substring(7); | ||
TokenDto googleTokenDto = googleService.getToken(code); | ||
GoogleDto googleDto = googleService.getUserInfo(googleTokenDto); | ||
TokenDto tokenDto = googleService.signIn(googleDto); | ||
return tokenDto; | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
src/main/java/org/swmaestro/repl/gifthub/auth/dto/GoogleDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package org.swmaestro.repl.gifthub.auth.dto; | ||
|
||
import jakarta.validation.constraints.NotNull; | ||
import jakarta.validation.constraints.Size; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
||
@Builder | ||
@Getter | ||
@Setter | ||
public class GoogleDto { | ||
@NotNull | ||
@Size(min = 4, max = 60) | ||
private String username; | ||
|
||
@NotNull | ||
@Size(min = 2, max = 12) | ||
private String nickname; | ||
} |
191 changes: 191 additions & 0 deletions
191
src/main/java/org/swmaestro/repl/gifthub/auth/service/GoogleService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
package org.swmaestro.repl.gifthub.auth.service; | ||
|
||
import com.google.gson.JsonElement; | ||
import com.google.gson.JsonParser; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.PropertySource; | ||
import org.springframework.stereotype.Service; | ||
import org.swmaestro.repl.gifthub.auth.dto.GoogleDto; | ||
import org.swmaestro.repl.gifthub.auth.dto.TokenDto; | ||
import org.swmaestro.repl.gifthub.auth.entity.Member; | ||
import org.swmaestro.repl.gifthub.auth.repository.MemberRepository; | ||
import org.swmaestro.repl.gifthub.exception.BusinessException; | ||
import org.swmaestro.repl.gifthub.exception.ErrorCode; | ||
import org.swmaestro.repl.gifthub.util.JwtProvider; | ||
|
||
import java.io.*; | ||
import java.net.HttpURLConnection; | ||
import java.net.MalformedURLException; | ||
import java.net.ProtocolException; | ||
import java.net.URL; | ||
|
||
@Service | ||
@PropertySource("classpath:application.yml") | ||
public class GoogleService { | ||
private final MemberService memberService; | ||
private final MemberRepository memberRepository; | ||
private final RefreshTokenService refreshTokenService; | ||
private final JwtProvider jwtProvider; | ||
private final String clientId; | ||
private final String redirectUri; | ||
private final String clientSecret; | ||
|
||
public GoogleService(MemberService memberService, MemberRepository memberRepository, RefreshTokenService refreshTokenService, JwtProvider jwtProvider, | ||
@Value("${google.client_id}") String clientId, @Value("${google.client_secret}") String clientSecret, @Value("${google.redirect_uri}") String redirectUri) { | ||
this.memberService = memberService; | ||
this.memberRepository = memberRepository; | ||
this.refreshTokenService = refreshTokenService; | ||
this.jwtProvider = jwtProvider; | ||
this.clientId = clientId; | ||
this.redirectUri = redirectUri; | ||
this.clientSecret = clientSecret; | ||
} | ||
|
||
public TokenDto getToken(String code) { | ||
String reqURL = "https://oauth2.googleapis.com/token"; | ||
TokenDto tokenDto = null; | ||
|
||
try { | ||
URL url = new URL(reqURL); | ||
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); | ||
|
||
conn.setRequestMethod("POST"); | ||
conn.setDoOutput(true); | ||
|
||
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream())); | ||
StringBuilder sb = new StringBuilder(); | ||
|
||
sb.append("grant_type=authorization_code"); | ||
sb.append("&client_id=" + clientId); | ||
sb.append("&client_secret=" + clientSecret); | ||
sb.append("&redirect_uri=" + redirectUri); | ||
sb.append("&code=" + code); | ||
bw.write(sb.toString()); | ||
bw.flush(); | ||
|
||
int responseCode = conn.getResponseCode(); | ||
System.out.println("responseCode : " + responseCode); | ||
|
||
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); | ||
|
||
String line = ""; | ||
String result = ""; | ||
|
||
while ((line = br.readLine()) != null) { | ||
result += line; | ||
} | ||
System.out.println("response body : " + result); | ||
|
||
JsonParser parser = new JsonParser(); | ||
JsonElement element = parser.parse(result); | ||
|
||
String accessToken = element.getAsJsonObject().get("access_token").getAsString(); | ||
|
||
br.close(); | ||
bw.close(); | ||
tokenDto = TokenDto.builder() | ||
.accessToken(accessToken) | ||
.build(); | ||
|
||
} catch (ProtocolException e) { | ||
throw new BusinessException("잘못된 프로토콜을 사용하였습니다.", ErrorCode.INVALID_INPUT_VALUE); | ||
} catch (MalformedURLException e) { | ||
throw new BusinessException("잘못된 URL 형식을 사용하였습니다.", ErrorCode.INVALID_INPUT_VALUE); | ||
} catch (IOException e) { | ||
throw new BusinessException("HTTP 연결을 수행하는 동안 입출력 관련 오류가 발생하였습니다.", ErrorCode.INTERNAL_SERVER_ERROR); | ||
} | ||
return tokenDto; | ||
} | ||
|
||
public GoogleDto getUserInfo(TokenDto tokenDto) { | ||
String reqURL = "https://www.googleapis.com/oauth2/v2/userinfo"; | ||
|
||
GoogleDto googleDto = null; | ||
|
||
try { | ||
URL url = new URL(reqURL); | ||
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); | ||
|
||
conn.setRequestMethod("GET"); | ||
conn.setDoOutput(true); | ||
|
||
conn.setRequestProperty("Authorization", "Bearer " + tokenDto.getAccessToken()); | ||
|
||
int responseCode = conn.getResponseCode(); | ||
|
||
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); | ||
String line = ""; | ||
String result = ""; | ||
|
||
while ((line = br.readLine()) != null) { | ||
result += line; | ||
} | ||
|
||
JsonParser parser = new JsonParser(); | ||
JsonElement element = parser.parse(result); | ||
|
||
String nickname = element.getAsJsonObject().get("name").getAsString(); | ||
String email = element.getAsJsonObject().get("email").getAsString(); | ||
|
||
br.close(); | ||
googleDto = GoogleDto.builder() | ||
.nickname(nickname) | ||
.username(email) | ||
.build(); | ||
} catch (ProtocolException e) { | ||
throw new BusinessException("잘못된 프로토콜을 사용하였습니다.", ErrorCode.INVALID_INPUT_VALUE); | ||
} catch (MalformedURLException e) { | ||
throw new BusinessException("잘못된 URL 형식을 사용하였습니다.", ErrorCode.INVALID_INPUT_VALUE); | ||
} catch (IOException e) { | ||
throw new BusinessException("HTTP 연결을 수행하는 동안 입출력 관련 오류가 발생하였습니다.", ErrorCode.INTERNAL_SERVER_ERROR); | ||
} | ||
return googleDto; | ||
} | ||
|
||
public TokenDto signIn(GoogleDto googleDto) { | ||
if (memberService.isDuplicateUsername(googleDto.getUsername())) { | ||
TokenDto tokenDto = signInWithExistingMember(googleDto); | ||
return tokenDto; | ||
} | ||
Member member = convertGoogleDtotoMember(googleDto); | ||
|
||
memberRepository.save(member); | ||
|
||
String accessToken = jwtProvider.generateToken(member.getUsername()); | ||
String refreshToken = jwtProvider.generateRefreshToken(member.getUsername()); | ||
|
||
TokenDto tokenDto = TokenDto.builder() | ||
.accessToken(accessToken) | ||
.refreshToken(refreshToken) | ||
.build(); | ||
|
||
refreshTokenService.storeRefreshToken(tokenDto, member.getUsername()); | ||
|
||
return tokenDto; | ||
} | ||
|
||
public TokenDto signInWithExistingMember(GoogleDto googleDto) { | ||
Member member = memberRepository.findByUsername(googleDto.getUsername()); | ||
if (member == null) { | ||
throw new BusinessException("존재하지 않는 아이디입니다.", ErrorCode.INVALID_INPUT_VALUE); | ||
} | ||
String accessToken = jwtProvider.generateToken(member.getUsername()); | ||
String refreshToken = jwtProvider.generateRefreshToken(member.getUsername()); | ||
|
||
TokenDto tokenDto = TokenDto.builder() | ||
.accessToken(accessToken) | ||
.refreshToken(refreshToken) | ||
.build(); | ||
|
||
refreshTokenService.storeRefreshToken(tokenDto, member.getUsername()); | ||
|
||
return tokenDto; | ||
} | ||
|
||
public Member convertGoogleDtotoMember(GoogleDto googleDto) { | ||
return Member.builder() | ||
.nickname(googleDto.getNickname()) | ||
.username(googleDto.getUsername()) | ||
.build(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
src/test/java/org/swmaestro/repl/gifthub/auth/controller/GoogleControllerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package org.swmaestro.repl.gifthub.auth.controller; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; | ||
import org.springframework.boot.test.context.SpringBootTest; | ||
import org.springframework.boot.test.mock.mockito.MockBean; | ||
import org.springframework.test.web.servlet.MockMvc; | ||
import org.swmaestro.repl.gifthub.auth.dto.GoogleDto; | ||
import org.swmaestro.repl.gifthub.auth.dto.TokenDto; | ||
import org.swmaestro.repl.gifthub.auth.service.GoogleService; | ||
|
||
import static org.mockito.Mockito.when; | ||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; | ||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; | ||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
|
||
@SpringBootTest | ||
@AutoConfigureMockMvc | ||
public class GoogleControllerTest { | ||
@Autowired | ||
private MockMvc mockMvc; | ||
@MockBean | ||
private GoogleService googleService; | ||
|
||
@Test | ||
public void callbackTest() throws Exception { | ||
mockMvc.perform(get("/auth/google/callback") | ||
.param("code", "myawesomecode")) | ||
.andExpect(status().isOk()); | ||
} | ||
|
||
@Test | ||
public void signInTest() throws Exception { | ||
String code = "myawesomecode"; | ||
|
||
TokenDto googleTokenDto = TokenDto.builder() | ||
.accessToken("myawesomeKakaojwt") | ||
.refreshToken("myawesomeKakaojwt") | ||
.build(); | ||
|
||
TokenDto tokenDto = TokenDto.builder() | ||
.accessToken("myawesomejwt") | ||
.refreshToken("myawesomejwt") | ||
.build(); | ||
|
||
GoogleDto googleDto = GoogleDto.builder() | ||
.nickname("정인희") | ||
.username("[email protected]") | ||
.build(); | ||
|
||
when(googleService.getToken(code)).thenReturn(googleTokenDto); | ||
when(googleService.getUserInfo(googleTokenDto)).thenReturn(googleDto); | ||
when(googleService.signIn(googleDto)).thenReturn(tokenDto); | ||
|
||
mockMvc.perform(post("/auth/google/sign-in") | ||
.header("Authorization", "Bearer " + code)) | ||
.andExpect(status().isOk()); | ||
} | ||
} |