Skip to content

Commit ea21ef6

Browse files
authored
Merge pull request #209 from devping-kr/dev
deploy: 배포
2 parents 51d593d + 66d5df8 commit ea21ef6

File tree

20 files changed

+218
-27
lines changed

20 files changed

+218
-27
lines changed

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ dependencies {
5959
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
6060
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
6161

62+
//Oauth2.0
63+
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
64+
6265
//Spring Rest Docs
6366
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
6467
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

src/main/java/devping/nnplanner/domain/auth/controller/AuthController.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import devping.nnplanner.domain.auth.dto.request.AuthSignRequestDTO;
44
import devping.nnplanner.domain.auth.dto.request.EmailCodeRequestDTO;
55
import devping.nnplanner.domain.auth.dto.request.EmailRequestDTO;
6+
import devping.nnplanner.domain.auth.dto.response.AuthResponseDTO;
67
import devping.nnplanner.domain.auth.dto.response.AuthTokenResponseDTO;
78
import devping.nnplanner.domain.auth.service.AuthService;
89
import devping.nnplanner.domain.auth.service.EmailService;
@@ -20,6 +21,7 @@
2021
import org.springframework.web.bind.annotation.RequestBody;
2122
import org.springframework.web.bind.annotation.RequestHeader;
2223
import org.springframework.web.bind.annotation.RequestMapping;
24+
import org.springframework.web.bind.annotation.RequestParam;
2325
import org.springframework.web.bind.annotation.RestController;
2426

2527
@RequestMapping("/api/auths")
@@ -80,4 +82,18 @@ public ResponseEntity<ApiResponse<Void>> logout(
8082

8183
return GlobalResponse.OK("유저 로그아웃 성공", null);
8284
}
85+
86+
@PostMapping("/oauth2/google")
87+
public ResponseEntity<ApiResponse<String>> loginUrlGoogle() {
88+
return GlobalResponse.OK("구글 로그인 url 리턴 성공", authService.loginUrlGoogle());
89+
}
90+
91+
@GetMapping("/oauth2/google")
92+
public ResponseEntity<ApiResponse<AuthResponseDTO>> loginGoogle(
93+
@RequestParam(value = "code") String authCode) {
94+
95+
AuthResponseDTO authResponseDTO = authService.loginGoogle(authCode);
96+
97+
return GlobalResponse.OK("구글 로그인 성공", authResponseDTO);
98+
}
8399
}

src/main/java/devping/nnplanner/domain/auth/dto/request/AuthLoginRequestDTO.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
package devping.nnplanner.domain.auth.dto.request;
22

3-
import jakarta.validation.constraints.Email;
43
import jakarta.validation.constraints.NotBlank;
4+
import jakarta.validation.constraints.Pattern;
55
import jakarta.validation.constraints.Size;
66
import lombok.Getter;
77

88
@Getter
99
public class AuthLoginRequestDTO {
1010

1111
@Size(max = 50)
12-
@Email
1312
@NotBlank
13+
@Pattern(
14+
regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
15+
message = "유효하지 않은 이메일 형식입니다.")
1416
private String email;
1517

1618
@NotBlank

src/main/java/devping/nnplanner/domain/auth/dto/request/AuthSignRequestDTO.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package devping.nnplanner.domain.auth.dto.request;
22

3-
import jakarta.validation.constraints.Email;
43
import jakarta.validation.constraints.NotBlank;
54
import jakarta.validation.constraints.Pattern;
65
import jakarta.validation.constraints.Size;
@@ -15,7 +14,9 @@ public class AuthSignRequestDTO {
1514

1615
@NotBlank
1716
@Size(max = 50)
18-
@Email
17+
@Pattern(
18+
regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
19+
message = "유효하지 않은 이메일 형식입니다.")
1920
private String email;
2021

2122
@NotBlank
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package devping.nnplanner.domain.auth.dto.request;
2+
3+
import lombok.Getter;
4+
import lombok.NoArgsConstructor;
5+
6+
@Getter
7+
@NoArgsConstructor
8+
public class GoogleInfoResponseDTO {
9+
10+
private String iss;
11+
private String azp;
12+
private String aud;
13+
private String sub;
14+
private String email;
15+
private String email_verified;
16+
private String at_hash;
17+
private String name;
18+
private String picture;
19+
private String given_name;
20+
private String family_name;
21+
private String locale;
22+
private String iat;
23+
private String exp;
24+
private String alg;
25+
private String kid;
26+
private String typ;
27+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package devping.nnplanner.domain.auth.dto.request;
2+
3+
import lombok.Builder;
4+
import lombok.Getter;
5+
6+
@Getter
7+
@Builder
8+
public class GoogleRequestDTO {
9+
10+
private String clientId; // 애플리케이션의 클라이언트 ID
11+
private String redirectUri; // Google 로그인 후 redirect 위치
12+
private String clientSecret; // 클라이언트 보안 비밀
13+
private String responseType; // Google OAuth 2.0 엔드포인트가 인증 코드를 반환하는지 여부
14+
private String scope; // OAuth 동의범위
15+
private String code;
16+
private String accessType; // 사용자가 브라우저에 없을 때 애플리케이션이 액세스 토큰을 새로 고칠 수 있는지 여부
17+
private String grantType;
18+
private String state;
19+
private String includeGrantedScopes; // 애플리케이션이 컨텍스트에서 추가 범위에 대한 액세스를 요청하기 위해 추가 권한 부여를 사용
20+
private String loginHint; // 애플리케이션이 인증하려는 사용자를 알고 있는 경우 이 매개변수를 사용하여 Google 인증 서버에 힌트를 제공
21+
private String prompt; // default: 처음으로 액세스를 요청할 때만 사용자에게 메시지가 표시
22+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package devping.nnplanner.domain.auth.dto.request;
2+
3+
import lombok.Getter;
4+
import lombok.NoArgsConstructor;
5+
6+
@Getter
7+
@NoArgsConstructor
8+
public class GoogleResponseDTO {
9+
10+
private String access_token; // 애플리케이션이 Google API 요청을 승인하기 위해 보내는 토큰
11+
private String expires_in; // Access Token의 남은 수명
12+
private String refresh_token; // 새 액세스 토큰을 얻는 데 사용할 수 있는 토큰
13+
private String scope;
14+
private String token_type; // 반환된 토큰 유형(Bearer 고정)
15+
private String id_token;
16+
}

src/main/java/devping/nnplanner/domain/auth/dto/response/AuthResponseDTO.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package devping.nnplanner.domain.auth.dto.response;
22

3-
import devping.nnplanner.domain.auth.entity.User;
3+
import devping.nnplanner.domain.user.entity.User;
44
import lombok.Getter;
55

66
@Getter

src/main/java/devping/nnplanner/domain/auth/repository/UserRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package devping.nnplanner.domain.auth.repository;
22

3-
import devping.nnplanner.domain.auth.entity.User;
3+
import devping.nnplanner.domain.user.entity.User;
44
import java.util.Optional;
55
import org.springframework.data.jpa.repository.JpaRepository;
66

src/main/java/devping/nnplanner/domain/auth/service/AuthService.java

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
package devping.nnplanner.domain.auth.service;
22

33
import devping.nnplanner.domain.auth.dto.request.AuthSignRequestDTO;
4+
import devping.nnplanner.domain.auth.dto.request.GoogleInfoResponseDTO;
5+
import devping.nnplanner.domain.auth.dto.request.GoogleRequestDTO;
6+
import devping.nnplanner.domain.auth.dto.request.GoogleResponseDTO;
7+
import devping.nnplanner.domain.auth.dto.response.AuthResponseDTO;
48
import devping.nnplanner.domain.auth.dto.response.AuthTokenResponseDTO;
5-
import devping.nnplanner.domain.auth.entity.User;
6-
import devping.nnplanner.domain.auth.entity.User.LoginType;
79
import devping.nnplanner.domain.auth.repository.EmailRepository;
810
import devping.nnplanner.domain.auth.repository.UserRepository;
11+
import devping.nnplanner.domain.user.entity.User;
12+
import devping.nnplanner.domain.user.entity.User.LoginType;
913
import devping.nnplanner.global.exception.CustomException;
1014
import devping.nnplanner.global.exception.ErrorCode;
1115
import devping.nnplanner.global.jwt.token.JwtUtil;
1216
import devping.nnplanner.global.jwt.user.UserDetailsImpl;
1317
import jakarta.servlet.http.HttpServletRequest;
18+
import java.util.HashMap;
19+
import java.util.Map;
1420
import lombok.RequiredArgsConstructor;
1521
import lombok.extern.slf4j.Slf4j;
22+
import org.springframework.beans.factory.annotation.Value;
23+
import org.springframework.http.ResponseEntity;
1624
import org.springframework.security.crypto.password.PasswordEncoder;
1725
import org.springframework.stereotype.Service;
1826
import org.springframework.transaction.annotation.Transactional;
27+
import org.springframework.web.client.RestTemplate;
1928

2029
@Slf4j
2130
@Service
@@ -27,6 +36,13 @@ public class AuthService {
2736
private final EmailRepository emailRepository;
2837
private final JwtUtil jwtUtil;
2938

39+
@Value("${spring.security.oauth2.client.registration.google.client-id}")
40+
private String googleClientId;
41+
@Value("${spring.security.oauth2.client.registration.google.client-secret}")
42+
private String googleClientPw;
43+
@Value("${api.oauth.key}")
44+
private String oauthUrl;
45+
3046
@Transactional
3147
public void signUp(AuthSignRequestDTO authSignRequestDTO) {
3248

@@ -77,4 +93,60 @@ public void logout(HttpServletRequest httpRequest, UserDetailsImpl userDetails)
7793
jwtUtil.deleteRefreshToken(userId, email);
7894
jwtUtil.logoutAccessToken(httpRequest);
7995
}
96+
97+
public String loginUrlGoogle() {
98+
return "https://accounts.google.com/o/oauth2/v2/auth?client_id=" + googleClientId
99+
+ "&redirect_uri=" + oauthUrl + "/api/auths/oauth2/google"
100+
+ "&response_type=code&scope=email%20profile%20openid&access_type=offline";
101+
}
102+
103+
public AuthResponseDTO loginGoogle(String authCode) {
104+
105+
RestTemplate restTemplate = new RestTemplate();
106+
107+
GoogleRequestDTO googleOAuthRequestParam = GoogleRequestDTO
108+
.builder()
109+
.clientId(googleClientId)
110+
.clientSecret(googleClientPw)
111+
.code(authCode)
112+
.redirectUri(oauthUrl + "/api/auths/oauth2/google")
113+
.grantType("authorization_code")
114+
.build();
115+
116+
ResponseEntity<GoogleResponseDTO> resultEntity = restTemplate.postForEntity(
117+
"https://oauth2.googleapis.com/token",
118+
googleOAuthRequestParam, GoogleResponseDTO.class);
119+
120+
String jwtToken = resultEntity.getBody().getId_token();
121+
122+
Map<String, String> map = new HashMap<>();
123+
map.put("id_token", jwtToken);
124+
125+
ResponseEntity<GoogleInfoResponseDTO> resultEntity2 = restTemplate.postForEntity(
126+
"https://oauth2.googleapis.com/tokeninfo",
127+
map, GoogleInfoResponseDTO.class);
128+
129+
String email = resultEntity2.getBody().getEmail();
130+
String name = resultEntity2.getBody().getName();
131+
132+
if (!userRepository.existsByEmail(email)) {
133+
User user = new User();
134+
user.create(
135+
name,
136+
email,
137+
null,
138+
LoginType.GOOGLE);
139+
userRepository.save(user);
140+
}
141+
142+
User user = userRepository.findByEmail(email)
143+
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND));
144+
145+
String accessToken = jwtUtil.createAccessToken(email);
146+
String refreshToken = jwtUtil.createRefreshToken(user.getUserId(), email);
147+
148+
AuthResponseDTO authResponseDTO = new AuthResponseDTO(user, accessToken, refreshToken);
149+
150+
return authResponseDTO;
151+
}
80152
}

0 commit comments

Comments
 (0)