-
Notifications
You must be signed in to change notification settings - Fork 0
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
๐ :: google oauth2 ๋ก๊ทธ์ธ #6
Changes from 26 commits
1c3845c
e6a3f19
81171ab
91f94ff
87fc2b2
91c8125
90e8aff
cacda95
a2ab858
1c37c82
92ef096
dd11ed4
b534bf8
82159c3
521d117
6d1c0c5
2a67fec
f71bc22
2164d2e
66605e0
396b059
31346bd
c91aae5
5094a59
44d4f1c
438abb9
e06e043
292e005
3943c76
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,10 @@ | ||
package com.gcms.v3.domain.auth.exception; | ||
|
||
import com.gcms.v3.global.error.BasicException; | ||
import com.gcms.v3.global.error.ErrorCode; | ||
|
||
public class UserNotFoundException extends BasicException { | ||
public UserNotFoundException() { | ||
super(ErrorCode.USER_NOT_FOUND); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.gcms.v3.domain.auth.presentation; | ||
|
||
import com.gcms.v3.domain.auth.presentation.data.request.SignInRequestDto; | ||
import com.gcms.v3.domain.auth.presentation.data.response.TokenInfoResponseDto; | ||
import com.gcms.v3.domain.auth.service.SignInService; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
@RequiredArgsConstructor | ||
@RestController | ||
@RequestMapping("/v3/auth") | ||
public class AuthController { | ||
|
||
private final SignInService signInService; | ||
|
||
@PostMapping | ||
public ResponseEntity<TokenInfoResponseDto> signIn (@RequestBody SignInRequestDto signInRequestDto) { | ||
TokenInfoResponseDto res = signInService.execute(signInRequestDto); | ||
return ResponseEntity.ok(res); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.gcms.v3.domain.auth.presentation.data.request; | ||
|
||
public record SignInRequestDto ( | ||
String code | ||
){ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.gcms.v3.domain.auth.presentation.data.response; | ||
|
||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
@Getter | ||
@Builder | ||
public class TokenInfoResponseDto { | ||
private String accessToken; | ||
private String refreshToken; | ||
private LocalDateTime accessTokenExpiresIn; | ||
private LocalDateTime refreshTokenExpiresIn; | ||
} | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.gcms.v3.domain.auth.service; | ||
|
||
import com.gcms.v3.domain.auth.presentation.data.request.SignInRequestDto; | ||
import com.gcms.v3.domain.auth.presentation.data.response.TokenInfoResponseDto; | ||
|
||
public interface SignInService { | ||
TokenInfoResponseDto execute(SignInRequestDto signInRequestDto); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package com.gcms.v3.domain.auth.service.impl; | ||
|
||
import com.gcms.v3.domain.auth.presentation.data.request.SignInRequestDto; | ||
import com.gcms.v3.domain.auth.presentation.data.response.TokenInfoResponseDto; | ||
import com.gcms.v3.domain.auth.service.SignInService; | ||
import com.gcms.v3.domain.user.domain.entity.User; | ||
import com.gcms.v3.domain.user.domain.entity.UserRole; | ||
import com.gcms.v3.domain.user.domain.enums.Authority; | ||
import com.gcms.v3.domain.user.domain.repository.UserRepository; | ||
import com.gcms.v3.domain.user.domain.repository.UserRoleRepository; | ||
import com.gcms.v3.global.oauth2.GoogleOAuth2UserInfo; | ||
import com.gcms.v3.global.oauth2.OAuth2Service; | ||
import com.gcms.v3.global.security.jwt.JwtTokenProvider; | ||
import jakarta.transaction.Transactional; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
|
||
@Service | ||
@Transactional(rollbackOn = Exception.class) | ||
@RequiredArgsConstructor | ||
public class SignInServiceImpl implements SignInService { | ||
|
||
private final OAuth2Service oAuth2Service; | ||
private final UserRepository userRepository; | ||
private final JwtTokenProvider jwtTokenProvider; | ||
private final UserRoleRepository userRoleRepository; | ||
|
||
public TokenInfoResponseDto execute(SignInRequestDto signInRequestDto) { | ||
String accessToken = oAuth2Service.requestAccessToken(signInRequestDto.code()); | ||
GoogleOAuth2UserInfo googleOAuth2UserInfo = oAuth2Service.requestUserInfo(accessToken); | ||
|
||
User user = userRepository.findByEmail(googleOAuth2UserInfo.getEmail()) | ||
.orElseGet(() -> toEntity(googleOAuth2UserInfo)); | ||
|
||
return jwtTokenProvider.generateToken(user.getEmail()); | ||
} | ||
|
||
private User toEntity(GoogleOAuth2UserInfo googleOAuth2UserInfo) { | ||
User user = User.builder() | ||
.name(googleOAuth2UserInfo.getName()) | ||
.email(googleOAuth2UserInfo.getEmail()) | ||
.build(); | ||
|
||
userRepository.save(user); | ||
saveAuthority(user); | ||
return user; | ||
} | ||
|
||
private void saveAuthority(User user) { | ||
UserRole userRole = UserRole.builder() | ||
.user(user) | ||
.authority(Authority.ROLE_STUDENT) | ||
.build(); | ||
|
||
userRoleRepository.save(userRole); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.gcms.v3.domain.user.domain.repository; | ||
|
||
import com.gcms.v3.domain.user.domain.entity.User; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
import java.util.Optional; | ||
|
||
public interface UserRepository extends JpaRepository<User, byte[]> { | ||
Optional<User> findByEmail(String email); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.gcms.v3.domain.user.domain.repository; | ||
|
||
import com.gcms.v3.domain.user.domain.entity.User; | ||
import com.gcms.v3.domain.user.domain.entity.UserRole; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
import java.util.List; | ||
|
||
public interface UserRoleRepository extends JpaRepository<UserRole, byte[]> { | ||
List<UserRole> findByUser(User user); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.gcms.v3.global.error; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
|
||
@AllArgsConstructor | ||
@Getter | ||
public class BasicException extends RuntimeException{ | ||
ErrorCode errorCode; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.gcms.v3.global.error; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import org.springframework.http.HttpStatus; | ||
|
||
|
||
@AllArgsConstructor | ||
@Getter | ||
public enum ErrorCode { | ||
|
||
INVALID_AUTH_TOKEN(HttpStatus.UNAUTHORIZED, "๊ถํ ์ ๋ณด๊ฐ ์๋ ํ ํฐ์ ๋๋ค."), | ||
EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "๋ง๋ฃ๋ ํ ํฐ์ ๋๋ค."), | ||
INVALID_TOKEN_TYPE(HttpStatus.UNAUTHORIZED, "์ ํจํ์ง ์์ ํ ํฐ ํ์ ์ ๋๋ค."), | ||
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "ํด๋น ์ ์ ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."), | ||
|
||
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "์๋ฒ ๋ด๋ถ ์๋ฌ"); | ||
|
||
private final HttpStatus httpStatus; | ||
private final String message; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.gcms.v3.global.error; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
|
||
@Getter | ||
@Builder | ||
@AllArgsConstructor | ||
public class ErrorResponse { | ||
private HttpStatus status; | ||
private String message; | ||
|
||
public static ResponseEntity<ErrorResponse> toResponseEntity(ErrorCode e){ | ||
return ResponseEntity | ||
.status(e.getHttpStatus()) | ||
.body(ErrorResponse.builder() | ||
.status(e.getHttpStatus()) | ||
.message(e.getMessage()) | ||
.build() | ||
); | ||
} | ||
} | ||
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. ์์ ํ์ต๋๋ค |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.gcms.v3.global.error; | ||
|
||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.ControllerAdvice; | ||
import org.springframework.web.bind.annotation.ExceptionHandler; | ||
|
||
@ControllerAdvice | ||
public class GlobalExceptionHandler { | ||
|
||
@ExceptionHandler(BasicException.class) | ||
protected ResponseEntity<ErrorResponse> handleCustomException(BasicException e) { | ||
return ErrorResponse.toResponseEntity(e.getErrorCode()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package com.gcms.v3.global.oauth2; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
import java.util.Map; | ||
|
||
@Getter | ||
@JsonIgnoreProperties(ignoreUnknown = true) | ||
@NoArgsConstructor | ||
public class GoogleOAuth2UserInfo { | ||
|
||
protected Map<String, Object> attributes; | ||
|
||
public GoogleOAuth2UserInfo(Map<String, Object> attributes) { | ||
this.attributes = attributes; | ||
} | ||
|
||
public String getId() { | ||
return (String) attributes.get("sub"); | ||
} | ||
|
||
public String getName() { | ||
return (String) attributes.get("name"); | ||
} | ||
|
||
public String getEmail() { | ||
return (String) attributes.get("email"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.gcms.v3.global.oauth2; | ||
|
||
import lombok.Getter; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
@Getter | ||
public class GoogleProperties { | ||
@Value("${spring.security.oauth2.client.provider.google.token-uri}") | ||
private String tokenUri; | ||
|
||
@Value("${spring.security.oauth2.client.registration.google.client-id}") | ||
private String clientId; | ||
|
||
@Value("${spring.security.oauth2.client.registration.google.client-secret}") | ||
private String clientSecret; | ||
|
||
@Value("${spring.security.oauth2.client.provider.google.user-info-uri}") | ||
private String userInfoUri; | ||
|
||
@Value("${spring.security.oauth2.client.registration.google.redirect-uri}") | ||
private String redirectUri; | ||
|
||
public static final String GRANT_TYPE = "authorization_code"; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package com.gcms.v3.global.oauth2; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
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; | ||
|
||
import java.net.URLDecoder; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Map; | ||
|
||
import static com.gcms.v3.global.oauth2.GoogleProperties.GRANT_TYPE; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class OAuth2Service { | ||
|
||
private final GoogleProperties config; | ||
private final RestTemplate restTemplate; | ||
|
||
public String requestAccessToken(String code) { | ||
String decode = URLDecoder.decode(code, StandardCharsets.UTF_8); | ||
|
||
HttpHeaders httpHeaders = new HttpHeaders(); | ||
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||
|
||
MultiValueMap<String, String> body = new LinkedMultiValueMap<>(); | ||
body.add("client_id", config.getClientId()); | ||
body.add("client_secret", config.getClientSecret()); | ||
body.add("code", decode); | ||
body.add("redirect_uri", config.getRedirectUri()); | ||
body.add("grant_type", GRANT_TYPE); | ||
|
||
HttpEntity<?> request = new HttpEntity<>(body, httpHeaders); | ||
|
||
ResponseEntity<Map> response = restTemplate.postForEntity(config.getTokenUri(), request, Map.class); | ||
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. ์ฌ๊ธฐ๋ ๋๊ฐ์ด ์ ์ฉํด์ฃผ์๋ฉด ๋ ๊ฒ ๊ฐ์์ |
||
return (String) response.getBody().get("access_token"); | ||
} | ||
|
||
|
||
public GoogleOAuth2UserInfo requestUserInfo(String accessToken) { | ||
HttpHeaders httpHeaders = new HttpHeaders(); | ||
httpHeaders.setContentType(MediaType.APPLICATION_JSON); | ||
httpHeaders.setBearerAuth(accessToken); | ||
|
||
MultiValueMap<String, String> body = new LinkedMultiValueMap<>(); | ||
|
||
HttpEntity<?> request = new HttpEntity<>(body, httpHeaders); | ||
|
||
ResponseEntity<Map> response = restTemplate.exchange(config.getUserInfoUri(), HttpMethod.GET, request, Map.class); | ||
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. RestTemplate์์ ๋ฐํ๋ฐ์ ๊ฐ์ ํ์
์ ์ง์ ํ ๋ ์ ๋ค๋ฆญ์ ์ฌ์ฉํ๋ ค๋ฉด ParameterizedTypeReference<๋ฐํ๋ฐ์ ๊ฐ ํ์
>() {}๊ณผ ๊ฐ์ด ์ฌ์ฉํด์ ์ ํํ ํ์
์ ์ง์ ํ ์ ์์ด์ 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. ์์ ํ์ด์ |
||
|
||
return new GoogleOAuth2UserInfo(response.getBody()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.gcms.v3.global.oauth2.config; | ||
|
||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.web.client.RestTemplate; | ||
|
||
@Configuration | ||
public class ClientConfig { | ||
@Bean | ||
public RestTemplate restTemplate() { | ||
return new RestTemplate(); | ||
} | ||
} |
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.
Dto๋ผ๊ณ ํ์ผ๋ Response์๋ ๋๊ฐ์ด ์ ์ฉํด ์ฃผ์๋ฉด ๋ ๊ฒ ๊ฐ์์!
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.
e06e043
response ๋ถ๋ถ์ ๊น๋จน์๋๊ฒ ๊ฐ์์ ์์ ํ์ต๋๋ค