Skip to content

Commit

Permalink
Feat/GH-91-google-login (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
inh2613 authored Jul 14, 2023
2 parents a6f692e + 7032657 commit 83115ad
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 1 deletion.
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 src/main/java/org/swmaestro/repl/gifthub/auth/dto/GoogleDto.java
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;
}
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class SecurityConfig {
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authorizeHttpRequests ->
authorizeHttpRequests.requestMatchers("/auth/sign-up", "/auth/sign-in", "/auth/kakao/**",
authorizeHttpRequests.requestMatchers("/auth/sign-up", "/auth/sign-in", "/auth/kakao/**", "/auth/google/**",
"/swagger-resources/**", "/swagger-ui/**", "/v3/api-docs/**", "/webjars/**", "/error").permitAll()
.anyRequest().authenticated())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
Expand Down
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());
}
}

0 comments on commit 83115ad

Please sign in to comment.