diff --git a/build.gradle b/build.gradle index e678725..6555ead 100644 --- a/build.gradle +++ b/build.gradle @@ -59,6 +59,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-mail' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + // OAuth2 + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' // test testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/controller/AuthController.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/controller/AuthController.java index 2ad149b..f34509e 100644 --- a/src/main/java/org/withtime/be/withtimebe/domain/auth/controller/AuthController.java +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/controller/AuthController.java @@ -24,7 +24,7 @@ public class AuthController { private final AuthCommandService authCommandService; private final EmailCommandService emailCommandService; - @Operation(summary = "회원가입 API by 요시", description = "최초 회원가입 시 필요한 정보를 포함하여 회원가입 진행") + @Operation(summary = "회원가입 API by 요시", description = "최초 회원가입 시 필요한 정보를 포함하여 회원가입 진행, 소셜 로그인인 경우에만 socialId 포함하고 아닌 경우 제거하거나 null") @ApiResponses({ @ApiResponse(responseCode = "204", description = "회원가입 성공"), @ApiResponse( @@ -33,6 +33,13 @@ public class AuthController { 다음과 같은 이유로 실패할 수 있습니다: - AUTH400_1: 이미 존재하는 이메일입니다. """ + ), + @ApiResponse( + responseCode = "404", + description = """ + 다음과 같은 이유로 실패할 수 있습니다: + - SOCIAL404_1: 소셜을 찾을 수 없습니다. + """ ) }) @PostMapping("/sign-up") diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/controller/OAuth2Controller.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/controller/OAuth2Controller.java new file mode 100644 index 0000000..ff11b3d --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/controller/OAuth2Controller.java @@ -0,0 +1,55 @@ +package org.withtime.be.withtimebe.domain.auth.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.namul.api.payload.response.DefaultResponse; +import org.springframework.web.bind.annotation.*; +import org.withtime.be.withtimebe.domain.auth.dto.response.OAuth2ResponseDTO; +import org.withtime.be.withtimebe.domain.auth.service.command.OAuth2CommandService; + +@RestController +@RequestMapping("/api/v1/oauth2") +@RequiredArgsConstructor +@Tag(name = "소셜 로그인 API") +public class OAuth2Controller { + + private final OAuth2CommandService oAuth2CommandService; + + @Operation(summary = "소셜 로그인 API by 요시", description = "/oauth2/authorization/{provider}로 서버에 요청을 보낸 뒤 리다이렉트된 URI의 코드를 사용하여 요청, 리다이렉트되는 URI 의 Endpoint는 해당 API와 동일합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", + description = """ + 소셜 로그인 성공 + - isFirst: true 시 최초 회원가입 필요, 이메일은 인증된 상태로 1시간 유효 + - isFirst: false 시 최초 회원가입 필요 X, 로그인 처리 + """ + ), + @ApiResponse( + responseCode = "400", + description = """ + 다음과 같은 이유로 실패할 수 있습니다: + - AUTH400_1: 지원하지 않는 소셜 로그인입니다. provider가 잘못되었거나 지원하지 않는 provider입니다. + """ + ), + @ApiResponse( + responseCode = "500", + description = """ + 다음과 같은 이유로 실패할 수 있습니다: + - AUTH500_1: 사용자 정보를 가져오는데 실패했습니다. 인가코드가 잘못되었거나 OAuth2 인증 서버나 리소스 서버에 보낸 요청이 실패했습니다. + """ + ), + + }) + @GetMapping("/callback/{provider}") + public DefaultResponse loginWithOAuth2(HttpServletRequest request, HttpServletResponse response, + @Parameter(description = "소셜 로그인 플랫폼(대소문자 상관 없음), [kakao, google, naver]", example = "kakao") @PathVariable String provider, + @RequestParam String code) { + return DefaultResponse.ok(oAuth2CommandService.login(request, response, provider, code)); + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/converter/OAuth2Converter.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/converter/OAuth2Converter.java new file mode 100644 index 0000000..06e3ea7 --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/converter/OAuth2Converter.java @@ -0,0 +1,41 @@ +package org.withtime.be.withtimebe.domain.auth.converter; + +import org.withtime.be.withtimebe.domain.auth.dto.response.OAuth2ResponseDTO; +import org.withtime.be.withtimebe.domain.auth.factory.support.dto.GoogleOAuth2ResponseDTO; +import org.withtime.be.withtimebe.domain.auth.factory.support.dto.KakaoOAuth2ResponseDTO; +import org.withtime.be.withtimebe.domain.auth.factory.support.dto.NaverOAuth2ResponseDTO; +import org.withtime.be.withtimebe.domain.member.entity.enums.SocialType; + +public class OAuth2Converter { + public static OAuth2ResponseDTO.Login toLogin(String email, boolean isFirst, Long socialId) { + return OAuth2ResponseDTO.Login.builder() + .email(email) + .socialId(socialId) + .isFirst(isFirst) + .build(); + } + + public static OAuth2ResponseDTO.GetUserInfo toGetUserInfo(KakaoOAuth2ResponseDTO.KakaoProfile kakaoProfile) { + return OAuth2ResponseDTO.GetUserInfo.builder() + .email(kakaoProfile.kakao_account().email()) + .providerId(String.valueOf(kakaoProfile.id())) + .socialType(SocialType.KAKAO) + .build(); + } + + public static OAuth2ResponseDTO.GetUserInfo toGetUserInfo(NaverOAuth2ResponseDTO.UserInfo.UserInfoData naver) { + return OAuth2ResponseDTO.GetUserInfo.builder() + .email(naver.email()) + .providerId(naver.id()) + .socialType(SocialType.NAVER) + .build(); + } + + public static OAuth2ResponseDTO.GetUserInfo toGetUserInfo(GoogleOAuth2ResponseDTO.UserInfo google) { + return OAuth2ResponseDTO.GetUserInfo.builder() + .email(google.email()) + .providerId(google.id()) + .socialType(SocialType.GOOGLE) + .build(); + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/dto/request/AuthRequestDTO.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/dto/request/AuthRequestDTO.java index dca0fbf..858b1c3 100644 --- a/src/main/java/org/withtime/be/withtimebe/domain/auth/dto/request/AuthRequestDTO.java +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/dto/request/AuthRequestDTO.java @@ -22,7 +22,8 @@ public record SignUp( String password, Gender gender, String phoneNumber, - LocalDate birth + LocalDate birth, + Long socialId ) { } diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/dto/response/OAuth2ResponseDTO.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/dto/response/OAuth2ResponseDTO.java new file mode 100644 index 0000000..038e8cd --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/dto/response/OAuth2ResponseDTO.java @@ -0,0 +1,26 @@ +package org.withtime.be.withtimebe.domain.auth.dto.response; + +import lombok.Builder; +import org.withtime.be.withtimebe.domain.member.entity.enums.SocialType; + +public record OAuth2ResponseDTO() { + + @Builder + public record Login( + String email, + Long socialId, + boolean isFirst + ) { + + } + + @Builder + public record GetUserInfo( + String email, + String providerId, + SocialType socialType + ) { + + } + +} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/OAuth2UserLoader.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/OAuth2UserLoader.java new file mode 100644 index 0000000..583058c --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/OAuth2UserLoader.java @@ -0,0 +1,8 @@ +package org.withtime.be.withtimebe.domain.auth.factory; + +import org.withtime.be.withtimebe.domain.auth.dto.response.OAuth2ResponseDTO; + +public interface OAuth2UserLoader { + OAuth2ResponseDTO.GetUserInfo loadUser(String code); + String getSocialType(); +} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/OAuth2UserLoaderFactory.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/OAuth2UserLoaderFactory.java new file mode 100644 index 0000000..9d834b8 --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/OAuth2UserLoaderFactory.java @@ -0,0 +1,29 @@ +package org.withtime.be.withtimebe.domain.auth.factory; + +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class OAuth2UserLoaderFactory { + + private final Map oAuth2UserLoaderMap = new ConcurrentHashMap<>(); + + public OAuth2UserLoaderFactory(List oAuth2UserLoaders) { + oAuth2UserLoaders.forEach( + oAuth2UserLoader -> { + String socialType = oAuth2UserLoader.getSocialType().toLowerCase(); + if (oAuth2UserLoaderMap.get(socialType) != null) { + throw new IllegalStateException("OAuth2UserLoader social type 중복: " + socialType); + } + oAuth2UserLoaderMap.put(socialType, oAuth2UserLoader); + } + ); + } + + public OAuth2UserLoader getUserLoader(String provider) { + return oAuth2UserLoaderMap.get(provider.toLowerCase()); + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/AbstractOAuth2UserLoader.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/AbstractOAuth2UserLoader.java new file mode 100644 index 0000000..430442d --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/AbstractOAuth2UserLoader.java @@ -0,0 +1,108 @@ +package org.withtime.be.withtimebe.domain.auth.factory.support; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; +import org.withtime.be.withtimebe.domain.auth.dto.response.OAuth2ResponseDTO; +import org.withtime.be.withtimebe.domain.auth.factory.OAuth2UserLoader; +import org.withtime.be.withtimebe.global.data.OAuth2ConfigData; +import org.withtime.be.withtimebe.global.error.code.OAuthErrorCode; +import org.withtime.be.withtimebe.global.error.exception.OAuthException; + +import java.io.IOException; +import java.util.Optional; + +@RequiredArgsConstructor +public abstract class AbstractOAuth2UserLoader implements OAuth2UserLoader { + + private final OAuth2ConfigData oAuth2ConfigData; + + @Override + public OAuth2ResponseDTO.GetUserInfo loadUser(String code) { + try { + String token = getAccessToken(code); + return getUserInfo(token); + } + catch (Exception e) { + throw new OAuthException(OAuthErrorCode.FAIL_TO_GET_USER_INFO); + } + } + + protected abstract String getAccessToken(String code) throws IOException; + + protected abstract OAuth2ResponseDTO.GetUserInfo getUserInfo(String token) throws IOException; + + protected T getToken(String code, Class clz) throws IOException { + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders httpHeaders = new HttpHeaders(); + + httpHeaders.add("Content-Type", "application/x-www-form-urlencoded"); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", "authorization_code"); + params.add("client_id", getClientId()); + params.add("redirect_uri", getRedirectUri()); + params.add("code", code); + Optional.ofNullable(getClientSecret()).ifPresent(secret -> params.add("client_secret", secret)); + HttpEntity request = new HttpEntity<>(params, httpHeaders); + + ResponseEntity response = restTemplate.exchange( + getTokenUri(), + HttpMethod.POST, + request, + String.class); + + ObjectMapper objectMapper = new ObjectMapper(); + + return objectMapper.readValue(response.getBody(), clz); + + } + + protected T getProfile(String tokenPrefix, String token, Class clz) throws IOException { + // 토큰으로 정보 가져오기 + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders httpHeaders = new HttpHeaders(); + + httpHeaders.add("Authorization", tokenPrefix + token); + httpHeaders.add("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); + + HttpEntity request1 = new HttpEntity<>(httpHeaders); + + ResponseEntity response = restTemplate.exchange( + getUserInfoUri(), + HttpMethod.GET, + request1, + String.class + ); + + ObjectMapper om = new ObjectMapper(); + + return om.readValue(response.getBody(), clz); + } + + protected String getClientId() { + return this.oAuth2ConfigData.getRegistration().get(this.getSocialType()).getClientId(); + } + + protected String getClientSecret() { + return this.oAuth2ConfigData.getRegistration().get(this.getSocialType()).getClientSecret(); + } + + protected String getRedirectUri() { + return this.oAuth2ConfigData.getRegistration().get(this.getSocialType()).getRedirectUri(); + } + + protected String getTokenUri() { + return this.oAuth2ConfigData.getProvider().get(this.getSocialType()).getTokenUri(); + } + + protected String getUserInfoUri() { + return this.oAuth2ConfigData.getProvider().get(this.getSocialType()).getUserInfoUri(); + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/GoogleUserLoader.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/GoogleUserLoader.java new file mode 100644 index 0000000..c6d71cd --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/GoogleUserLoader.java @@ -0,0 +1,39 @@ +package org.withtime.be.withtimebe.domain.auth.factory.support; + +import org.springframework.stereotype.Component; +import org.withtime.be.withtimebe.domain.auth.converter.OAuth2Converter; +import org.withtime.be.withtimebe.domain.auth.dto.response.OAuth2ResponseDTO; +import org.withtime.be.withtimebe.domain.auth.factory.support.dto.GoogleOAuth2ResponseDTO; +import org.withtime.be.withtimebe.domain.member.entity.enums.SocialType; +import org.withtime.be.withtimebe.global.data.OAuth2ConfigData; + +import java.io.IOException; + +@Component +public class GoogleUserLoader extends AbstractOAuth2UserLoader { + + private static final String AUTHORIZATION_TOKEN_PREFIX = "Bearer "; + private static final SocialType SOCIAL_TYPE = SocialType.GOOGLE; + + public GoogleUserLoader(OAuth2ConfigData oAuth2ConfigData) { + super(oAuth2ConfigData); + } + + @Override + protected String getAccessToken(String code) throws IOException { + GoogleOAuth2ResponseDTO.Token token = super.getToken(code, GoogleOAuth2ResponseDTO.Token.class); + return token.access_token(); + } + + @Override + protected OAuth2ResponseDTO.GetUserInfo getUserInfo(String token) throws IOException { + GoogleOAuth2ResponseDTO.UserInfo userInfo = super.getProfile(AUTHORIZATION_TOKEN_PREFIX, token, GoogleOAuth2ResponseDTO.UserInfo.class); + return OAuth2Converter.toGetUserInfo(userInfo); + } + + @Override + public String getSocialType() { + return SOCIAL_TYPE.name().toLowerCase(); + } + +} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/KakaoUserLoader.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/KakaoUserLoader.java new file mode 100644 index 0000000..6d7d6af --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/KakaoUserLoader.java @@ -0,0 +1,40 @@ +package org.withtime.be.withtimebe.domain.auth.factory.support; + +import org.springframework.stereotype.Component; +import org.withtime.be.withtimebe.domain.auth.converter.OAuth2Converter; +import org.withtime.be.withtimebe.domain.auth.dto.response.OAuth2ResponseDTO; +import org.withtime.be.withtimebe.domain.auth.factory.support.dto.KakaoOAuth2ResponseDTO; +import org.withtime.be.withtimebe.domain.member.entity.enums.SocialType; +import org.withtime.be.withtimebe.global.data.OAuth2ConfigData; + +import java.io.IOException; + +@Component +public class KakaoUserLoader extends AbstractOAuth2UserLoader { + + private static final String AUTHORIZATION_TOKEN_PREFIX = "Bearer "; + private static final SocialType SOCIAL_TYPE = SocialType.KAKAO; + + public KakaoUserLoader(OAuth2ConfigData oAuth2ConfigData) { + super(oAuth2ConfigData); + } + + @Override + protected String getAccessToken(String code) throws IOException { + KakaoOAuth2ResponseDTO.Token oAuth2TokenDTO = getToken(code, KakaoOAuth2ResponseDTO.Token.class); + return oAuth2TokenDTO.access_token(); + } + + @Override + protected OAuth2ResponseDTO.GetUserInfo getUserInfo(String token) throws IOException { + KakaoOAuth2ResponseDTO.KakaoProfile kakaoProfile = super.getProfile(AUTHORIZATION_TOKEN_PREFIX, token, KakaoOAuth2ResponseDTO.KakaoProfile.class); + return OAuth2Converter.toGetUserInfo(kakaoProfile); + } + + + @Override + public String getSocialType() { + return SOCIAL_TYPE.name().toLowerCase(); + } + +} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/NaverUserLoader.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/NaverUserLoader.java new file mode 100644 index 0000000..893a33a --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/NaverUserLoader.java @@ -0,0 +1,38 @@ +package org.withtime.be.withtimebe.domain.auth.factory.support; + +import org.springframework.stereotype.Component; +import org.withtime.be.withtimebe.domain.auth.converter.OAuth2Converter; +import org.withtime.be.withtimebe.domain.auth.dto.response.OAuth2ResponseDTO; +import org.withtime.be.withtimebe.domain.auth.factory.support.dto.NaverOAuth2ResponseDTO; +import org.withtime.be.withtimebe.domain.member.entity.enums.SocialType; +import org.withtime.be.withtimebe.global.data.OAuth2ConfigData; + +import java.io.IOException; + +@Component +public class NaverUserLoader extends AbstractOAuth2UserLoader { + + private static final String AUTHORIZATION_TOKEN_PREFIX = "Bearer "; + private static final SocialType SOCIAL_TYPE = SocialType.NAVER; + + public NaverUserLoader(OAuth2ConfigData oAuth2ConfigData) { + super(oAuth2ConfigData); + } + + @Override + protected String getAccessToken(String code) throws IOException { + NaverOAuth2ResponseDTO.Token oAuth2TokenDTO = getToken(code, NaverOAuth2ResponseDTO.Token.class); + return oAuth2TokenDTO.access_token(); + } + + @Override + protected OAuth2ResponseDTO.GetUserInfo getUserInfo(String token) throws IOException { + NaverOAuth2ResponseDTO.UserInfo.UserInfoData userInfo = super.getProfile(AUTHORIZATION_TOKEN_PREFIX, token, NaverOAuth2ResponseDTO.UserInfo.class).response(); + return OAuth2Converter.toGetUserInfo(userInfo); + } + + @Override + public String getSocialType() { + return SOCIAL_TYPE.name().toLowerCase(); + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/dto/GoogleOAuth2ResponseDTO.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/dto/GoogleOAuth2ResponseDTO.java new file mode 100644 index 0000000..383adda --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/dto/GoogleOAuth2ResponseDTO.java @@ -0,0 +1,23 @@ +package org.withtime.be.withtimebe.domain.auth.factory.support.dto; + +public record GoogleOAuth2ResponseDTO() { + public record Token( + String access_token, + String refresh_token, + Long expires_in, + String token_type, + String scope, + String id_token + ) { + + } + + public record UserInfo( + String id, + String email, + Boolean verified_email, + String picture + ) { + + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/dto/KakaoOAuth2ResponseDTO.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/dto/KakaoOAuth2ResponseDTO.java new file mode 100644 index 0000000..e7b80f4 --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/dto/KakaoOAuth2ResponseDTO.java @@ -0,0 +1,53 @@ +package org.withtime.be.withtimebe.domain.auth.factory.support.dto; + +import lombok.Getter; + +public record KakaoOAuth2ResponseDTO () { + + public record Token ( + String token_type, + String access_token, + String refresh_token, + Long expires_in, + Long refresh_token_expires_in, + String scope + ) { + } + + public record KakaoProfile ( + Long id, + String connected_at, + Properties properties, + KakaoAccount kakao_account + ) { + + public record Properties ( + String nickname, + String profile_image, + String thumbnail_image + ) { + } + + public record KakaoAccount ( + String email, + Boolean is_email_verified, + Boolean email_needs_agreement, + Boolean has_email, + Boolean profile_nickname_needs_agreement, + Boolean profile_image_needs_agreement, + Boolean email_needs_argument, + Boolean is_email_valid, + Profile profile + ) { + + public record Profile ( + String nickname, + String thumbnail_image_url, + String profile_image_url, + Boolean is_default_nickname, + Boolean is_default_image + ) { + } + } + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/dto/NaverOAuth2ResponseDTO.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/dto/NaverOAuth2ResponseDTO.java new file mode 100644 index 0000000..9545438 --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/factory/support/dto/NaverOAuth2ResponseDTO.java @@ -0,0 +1,24 @@ +package org.withtime.be.withtimebe.domain.auth.factory.support.dto; + +public record NaverOAuth2ResponseDTO() { + public record Token( + String access_token, + String refresh_token, + String token_type, + String expires_in + ) { + } + + public record UserInfo( + String resultcode, + String message, + UserInfoData response + ) { + public record UserInfoData( + String id, + String email + ) { + + } + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/service/command/AuthCommandServiceImpl.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/service/command/AuthCommandServiceImpl.java index 48d7d10..cdd75fd 100644 --- a/src/main/java/org/withtime/be/withtimebe/domain/auth/service/command/AuthCommandServiceImpl.java +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/service/command/AuthCommandServiceImpl.java @@ -12,15 +12,11 @@ import org.withtime.be.withtimebe.domain.auth.service.query.TokenQueryService; import org.withtime.be.withtimebe.domain.auth.service.query.TokenStorageQueryService; import org.withtime.be.withtimebe.domain.member.entity.Member; +import org.withtime.be.withtimebe.domain.member.entity.Social; import org.withtime.be.withtimebe.domain.member.repository.MemberRepository; -import org.withtime.be.withtimebe.global.error.code.AuthErrorCode; -import org.withtime.be.withtimebe.global.error.code.EmailErrorCode; -import org.withtime.be.withtimebe.global.error.code.MemberErrorCode; -import org.withtime.be.withtimebe.global.error.code.TokenErrorCode; -import org.withtime.be.withtimebe.global.error.exception.AuthException; -import org.withtime.be.withtimebe.global.error.exception.EmailException; -import org.withtime.be.withtimebe.global.error.exception.MemberException; -import org.withtime.be.withtimebe.global.error.exception.TokenException; +import org.withtime.be.withtimebe.domain.member.repository.SocialRepository; +import org.withtime.be.withtimebe.global.error.code.*; +import org.withtime.be.withtimebe.global.error.exception.*; import org.withtime.be.withtimebe.global.security.constants.AuthenticationConstants; import org.withtime.be.withtimebe.global.security.domain.CustomUserDetails; import org.withtime.be.withtimebe.global.util.CookieUtil; @@ -32,6 +28,7 @@ public class AuthCommandServiceImpl implements AuthCommandService { private final PasswordEncoder passwordEncoder; private final MemberRepository memberRepository; + private final SocialRepository socialRepository; private final TokenCommandService tokenCommandService; private final TokenStorageCommandService tokenStorageCommandService; private final TokenQueryService tokenQueryService; @@ -42,8 +39,12 @@ public class AuthCommandServiceImpl implements AuthCommandService { public void signUp(AuthRequestDTO.SignUp request) { validateSignUp(request); - Member member = AuthConverter.toLocalMember(request.email(), request.username(), passwordEncoder.encode(request.password()), request.phoneNumber(), request.gender(), request.birth()); - memberRepository.save(member); + Member member = memberRepository.save(AuthConverter.toLocalMember(request.email(), request.username(), passwordEncoder.encode(request.password()), request.phoneNumber(), request.gender(), request.birth())); + if (request.socialId() != null) { + Social social = socialRepository.findById(request.socialId()).orElseThrow(() -> + new SocialException(SocialErrorCode.NOT_FOUND_SOCIAL)); + social.addMember(member); + } } @Override diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/service/command/OAuth2CommandService.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/service/command/OAuth2CommandService.java new file mode 100644 index 0000000..cd81cd7 --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/service/command/OAuth2CommandService.java @@ -0,0 +1,9 @@ +package org.withtime.be.withtimebe.domain.auth.service.command; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.withtime.be.withtimebe.domain.auth.dto.response.OAuth2ResponseDTO; + +public interface OAuth2CommandService { + OAuth2ResponseDTO.Login login(HttpServletRequest request, HttpServletResponse response, String provider, String code); +} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/service/command/OAuth2CommandServiceImpl.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/service/command/OAuth2CommandServiceImpl.java new file mode 100644 index 0000000..4c547ff --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/service/command/OAuth2CommandServiceImpl.java @@ -0,0 +1,69 @@ +package org.withtime.be.withtimebe.domain.auth.service.command; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.withtime.be.withtimebe.domain.auth.converter.OAuth2Converter; +import org.withtime.be.withtimebe.domain.auth.dto.response.OAuth2ResponseDTO; +import org.withtime.be.withtimebe.domain.auth.factory.OAuth2UserLoader; +import org.withtime.be.withtimebe.domain.auth.factory.OAuth2UserLoaderFactory; +import org.withtime.be.withtimebe.domain.member.converter.SocialConverter; +import org.withtime.be.withtimebe.domain.member.entity.Member; +import org.withtime.be.withtimebe.domain.member.entity.Social; +import org.withtime.be.withtimebe.domain.member.repository.MemberRepository; +import org.withtime.be.withtimebe.domain.member.repository.SocialRepository; +import org.withtime.be.withtimebe.global.error.code.OAuthErrorCode; +import org.withtime.be.withtimebe.global.error.exception.OAuthException; +import org.withtime.be.withtimebe.global.security.domain.CustomUserDetails; +import org.withtime.be.withtimebe.global.security.manager.TokenManager; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Transactional +public class OAuth2CommandServiceImpl implements OAuth2CommandService { + + private final OAuth2UserLoaderFactory oAuth2UserLoaderFactory; + private final SocialRepository socialRepository; + private final MemberRepository memberRepository; + private final TokenManager tokenManager; + + private final EmailVerificationCodeStorageCommandService emailVerificationCodeStorageCommandService; + + @Override + public OAuth2ResponseDTO.Login login(HttpServletRequest request, HttpServletResponse response, String provider, String code) { + OAuth2UserLoader userLoader = oAuth2UserLoaderFactory.getUserLoader(provider); + if (userLoader == null) { + throw new OAuthException(OAuthErrorCode.UNSUPPORTED_SOCIAL_TYPE); + } + OAuth2ResponseDTO.GetUserInfo userInfo = userLoader.loadUser(code); + return successfulOAuth2(request, response, userInfo); + } + + private OAuth2ResponseDTO.Login successfulOAuth2(HttpServletRequest request, HttpServletResponse response, OAuth2ResponseDTO.GetUserInfo userInfo) { + Optional socialOptional = socialRepository.findByProviderIdAndSocialType(userInfo.providerId(), userInfo.socialType()); + Optional memberOptional = memberRepository.findByEmail(userInfo.email()); + + // 해당 이메일로 회원가입이 된 경우 + if (memberOptional.isPresent()) { + // 소셜이 있으면 가져오고 아니면 새로 만들기 + Social social = socialOptional.orElseGet(() -> socialRepository.save(SocialConverter.toSocial(userInfo, memberOptional.get()))); + processLogin(request, response, memberOptional.get()); + return OAuth2Converter.toLogin(userInfo.email(), false, social.getId()); + } + // 회원가입이 안 된 경우 + else { + Social social = socialOptional.orElseGet(() -> socialRepository.save(SocialConverter.toSocial(userInfo))); + emailVerificationCodeStorageCommandService.saveVerifiedEmail(userInfo.email()); + return OAuth2Converter.toLogin(userInfo.email(), true, social.getId()); + } + } + + private void processLogin(HttpServletRequest request, HttpServletResponse response, Member member) { + CustomUserDetails customUserDetails = new CustomUserDetails(member); + tokenManager.addToken(request, response, customUserDetails); + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/member/converter/SocialConverter.java b/src/main/java/org/withtime/be/withtimebe/domain/member/converter/SocialConverter.java new file mode 100644 index 0000000..89918a7 --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/member/converter/SocialConverter.java @@ -0,0 +1,22 @@ +package org.withtime.be.withtimebe.domain.member.converter; + +import org.withtime.be.withtimebe.domain.auth.dto.response.OAuth2ResponseDTO; +import org.withtime.be.withtimebe.domain.member.entity.Member; +import org.withtime.be.withtimebe.domain.member.entity.Social; + +public class SocialConverter { + public static Social toSocial(OAuth2ResponseDTO.GetUserInfo userInfo) { + return Social.builder() + .socialType(userInfo.socialType()) + .providerId(userInfo.providerId()) + .build(); + } + + public static Social toSocial(OAuth2ResponseDTO.GetUserInfo userInfo, Member member) { + return Social.builder() + .socialType(userInfo.socialType()) + .providerId(userInfo.providerId()) + .member(member) + .build(); + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/member/entity/Member.java b/src/main/java/org/withtime/be/withtimebe/domain/member/entity/Member.java index fb0aba4..c97e6a7 100644 --- a/src/main/java/org/withtime/be/withtimebe/domain/member/entity/Member.java +++ b/src/main/java/org/withtime/be/withtimebe/domain/member/entity/Member.java @@ -3,7 +3,6 @@ import jakarta.persistence.*; import lombok.*; import org.withtime.be.withtimebe.domain.member.entity.enums.Gender; -import org.withtime.be.withtimebe.domain.member.entity.enums.ProviderType; import org.withtime.be.withtimebe.domain.member.entity.enums.Role; import org.withtime.be.withtimebe.domain.member.entity.enums.UserRank; import org.withtime.be.withtimebe.global.common.BaseEntity; diff --git a/src/main/java/org/withtime/be/withtimebe/domain/member/entity/Social.java b/src/main/java/org/withtime/be/withtimebe/domain/member/entity/Social.java index 5cadc14..b6e8fd3 100644 --- a/src/main/java/org/withtime/be/withtimebe/domain/member/entity/Social.java +++ b/src/main/java/org/withtime/be/withtimebe/domain/member/entity/Social.java @@ -27,4 +27,8 @@ public class Social extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member; + + public void addMember(Member member) { + this.member = member; + } } diff --git a/src/main/java/org/withtime/be/withtimebe/domain/member/entity/enums/ProviderType.java b/src/main/java/org/withtime/be/withtimebe/domain/member/entity/enums/ProviderType.java deleted file mode 100644 index d2f7a66..0000000 --- a/src/main/java/org/withtime/be/withtimebe/domain/member/entity/enums/ProviderType.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.withtime.be.withtimebe.domain.member.entity.enums; - -public enum ProviderType { - KAKAO, GOOGLE, NAVER, LOCAL -} diff --git a/src/main/java/org/withtime/be/withtimebe/domain/member/repository/SocialRepository.java b/src/main/java/org/withtime/be/withtimebe/domain/member/repository/SocialRepository.java new file mode 100644 index 0000000..e8dd17e --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/domain/member/repository/SocialRepository.java @@ -0,0 +1,11 @@ +package org.withtime.be.withtimebe.domain.member.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.withtime.be.withtimebe.domain.member.entity.Social; +import org.withtime.be.withtimebe.domain.member.entity.enums.SocialType; + +import java.util.Optional; + +public interface SocialRepository extends JpaRepository { + Optional findByProviderIdAndSocialType(String providerId, SocialType socialType); +} diff --git a/src/main/java/org/withtime/be/withtimebe/global/data/OAuth2ConfigData.java b/src/main/java/org/withtime/be/withtimebe/global/data/OAuth2ConfigData.java new file mode 100644 index 0000000..81efa0c --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/global/data/OAuth2ConfigData.java @@ -0,0 +1,38 @@ +package org.withtime.be.withtimebe.global.data; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.List; +import java.util.Map; + +@Getter +@Setter +@Configuration +@ConfigurationProperties(prefix = "spring.security.oauth2.client") +public class OAuth2ConfigData { + private Map registration; + private Map provider; + + @Getter + @Setter + public static class Registration { + private String clientId; + private String clientSecret; + private String redirectUri; + private String authorizationGrantType; + private String clientAuthenticationMethod; + private List scope; + } + + @Getter + @Setter + public static class Provider { + private String authorizationUri; + private String tokenUri; + private String userInfoUri; + private String userNameAttribute; + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/global/error/code/OAuthErrorCode.java b/src/main/java/org/withtime/be/withtimebe/global/error/code/OAuthErrorCode.java new file mode 100644 index 0000000..32a9aa9 --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/global/error/code/OAuthErrorCode.java @@ -0,0 +1,26 @@ +package org.withtime.be.withtimebe.global.error.code; + +import lombok.AllArgsConstructor; +import org.namul.api.payload.code.BaseErrorCode; +import org.namul.api.payload.code.dto.supports.DefaultResponseErrorReasonDTO; +import org.springframework.http.HttpStatus; + +@AllArgsConstructor +public enum OAuthErrorCode implements BaseErrorCode { + + FAIL_TO_GET_USER_INFO(HttpStatus.INTERNAL_SERVER_ERROR, "OAUTH500_1", "사용자 정보를 가져오는 데 실패했습니다."), + UNSUPPORTED_SOCIAL_TYPE(HttpStatus.BAD_REQUEST, "OAUTH400_1", "지원하지 않는 소셜 로그인입니다.") + ; + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public DefaultResponseErrorReasonDTO getReason() { + return DefaultResponseErrorReasonDTO.builder() + .httpStatus(this.httpStatus) + .code(this.code) + .message(this.message) + .build(); + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/global/error/code/SocialErrorCode.java b/src/main/java/org/withtime/be/withtimebe/global/error/code/SocialErrorCode.java new file mode 100644 index 0000000..4526c34 --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/global/error/code/SocialErrorCode.java @@ -0,0 +1,25 @@ +package org.withtime.be.withtimebe.global.error.code; + +import lombok.AllArgsConstructor; +import org.namul.api.payload.code.BaseErrorCode; +import org.namul.api.payload.code.dto.supports.DefaultResponseErrorReasonDTO; +import org.springframework.http.HttpStatus; + +@AllArgsConstructor +public enum SocialErrorCode implements BaseErrorCode { + + NOT_FOUND_SOCIAL(HttpStatus.NOT_FOUND, "SOCIAL404_1", "해당 소셜을 찾을 수 없습니다."), + ; + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public DefaultResponseErrorReasonDTO getReason() { + return DefaultResponseErrorReasonDTO.builder() + .httpStatus(this.httpStatus) + .code(this.code) + .message(this.message) + .build(); + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/global/error/exception/OAuthException.java b/src/main/java/org/withtime/be/withtimebe/global/error/exception/OAuthException.java new file mode 100644 index 0000000..ccf6743 --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/global/error/exception/OAuthException.java @@ -0,0 +1,10 @@ +package org.withtime.be.withtimebe.global.error.exception; + +import org.namul.api.payload.code.BaseErrorCode; +import org.namul.api.payload.error.exception.ServerApplicationException; + +public class OAuthException extends ServerApplicationException { + public OAuthException(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/global/error/exception/SocialException.java b/src/main/java/org/withtime/be/withtimebe/global/error/exception/SocialException.java new file mode 100644 index 0000000..a733299 --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/global/error/exception/SocialException.java @@ -0,0 +1,11 @@ +package org.withtime.be.withtimebe.global.error.exception; + +import org.namul.api.payload.code.BaseErrorCode; +import org.namul.api.payload.error.exception.ServerApplicationException; + +public class SocialException extends ServerApplicationException { + + public SocialException(BaseErrorCode code) { + super (code); + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/global/security/SecurityConfig.java b/src/main/java/org/withtime/be/withtimebe/global/security/SecurityConfig.java index b256970..fec718b 100644 --- a/src/main/java/org/withtime/be/withtimebe/global/security/SecurityConfig.java +++ b/src/main/java/org/withtime/be/withtimebe/global/security/SecurityConfig.java @@ -8,6 +8,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -50,7 +51,9 @@ public class SecurityConfig { private String[] allowUrl = { API_PREFIX + "/auth/**", API_PREFIX + "/notices/**", + API_PREFIX + "/oauth2/**", API_PREFIX + "/faqs/**", + "/oauth2/authorization/**", "/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs/**" @@ -90,6 +93,7 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .csrf(AbstractHttpConfigurer::disable) .httpBasic(AbstractHttpConfigurer::disable) .formLogin(AbstractHttpConfigurer::disable) + .oauth2Login(Customizer.withDefaults()) .exceptionHandling(exception -> exception .accessDeniedHandler(accessDeniedHandler()) .authenticationEntryPoint(authenticationEntryPoint()) diff --git a/src/main/java/org/withtime/be/withtimebe/global/security/handler/CustomAuthenticationSuccessHandler.java b/src/main/java/org/withtime/be/withtimebe/global/security/handler/CustomAuthenticationSuccessHandler.java index 5de3ab8..b0ca269 100644 --- a/src/main/java/org/withtime/be/withtimebe/global/security/handler/CustomAuthenticationSuccessHandler.java +++ b/src/main/java/org/withtime/be/withtimebe/global/security/handler/CustomAuthenticationSuccessHandler.java @@ -17,6 +17,7 @@ import org.withtime.be.withtimebe.domain.auth.service.command.TokenStorageCommandService; import org.withtime.be.withtimebe.global.security.constants.AuthenticationConstants; import org.withtime.be.withtimebe.global.security.domain.CustomUserDetails; +import org.withtime.be.withtimebe.global.security.manager.TokenManager; import org.withtime.be.withtimebe.global.util.CookieUtil; import java.io.IOException; @@ -25,15 +26,12 @@ @RequiredArgsConstructor public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { - private final TokenStorageCommandService tokenStorageCommandService; - private final TokenCommandService tokenCommandService; - private final TokenQueryService tokenQueryService; + private final TokenManager tokenManager; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { CustomUserDetails customUserDetails =(CustomUserDetails) authentication.getPrincipal(); - AuthResponseDTO.Login loginResponse = tokenCommandService.createLoginToken(customUserDetails); - addToken(request, response, loginResponse, customUserDetails); + tokenManager.addToken(request, response, customUserDetails); ObjectMapper objectMapper = new ObjectMapper(); response.setStatus(HttpStatus.OK.value()); @@ -42,16 +40,5 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo objectMapper.writeValue(response.getOutputStream(), DefaultResponse.noContent()); } - private void addToken(HttpServletRequest request, HttpServletResponse response, AuthResponseDTO.Login loginResponse, CustomUserDetails customUserDetails) { - // 쿠키에 이미 토큰이 있는 경우 블랙리스트 처리 - for (String token : new String[] {CookieUtil.getCookie(request, AuthenticationConstants.ACCESS_TOKEN_NAME), CookieUtil.getCookie(request, AuthenticationConstants.REFRESH_TOKEN_NAME)}) { - if (token != null) { - tokenStorageCommandService.addBlackList(token); - } - } - - CookieUtil.addCookie(request, response, AuthenticationConstants.ACCESS_TOKEN_NAME, loginResponse.accessToken(), (int) tokenQueryService.getAccessTokenExpiration().toSeconds()); - CookieUtil.addCookie(request, response, AuthenticationConstants.REFRESH_TOKEN_NAME, loginResponse.refreshToken(), (int) tokenQueryService.getRefreshTokenExpiration().toSeconds()); - tokenStorageCommandService.addRefreshToken(customUserDetails.getId(), loginResponse.refreshToken()); - } + } diff --git a/src/main/java/org/withtime/be/withtimebe/global/security/manager/CookieTokenManager.java b/src/main/java/org/withtime/be/withtimebe/global/security/manager/CookieTokenManager.java new file mode 100644 index 0000000..913e753 --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/global/security/manager/CookieTokenManager.java @@ -0,0 +1,38 @@ +package org.withtime.be.withtimebe.global.security.manager; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.withtime.be.withtimebe.domain.auth.dto.response.AuthResponseDTO; +import org.withtime.be.withtimebe.domain.auth.dto.response.OAuth2ResponseDTO; +import org.withtime.be.withtimebe.domain.auth.service.command.TokenCommandService; +import org.withtime.be.withtimebe.domain.auth.service.command.TokenStorageCommandService; +import org.withtime.be.withtimebe.domain.auth.service.query.TokenQueryService; +import org.withtime.be.withtimebe.global.security.constants.AuthenticationConstants; +import org.withtime.be.withtimebe.global.security.domain.CustomUserDetails; +import org.withtime.be.withtimebe.global.util.CookieUtil; + +@Component +@RequiredArgsConstructor +public class CookieTokenManager implements TokenManager { + + private final TokenStorageCommandService tokenStorageCommandService; + private final TokenCommandService tokenCommandService; + private final TokenQueryService tokenQueryService; + + @Override + public void addToken(HttpServletRequest request, HttpServletResponse response, CustomUserDetails customUserDetails) { + AuthResponseDTO.Login loginResponse = tokenCommandService.createLoginToken(customUserDetails); + for (String token : new String[] {CookieUtil.getCookie(request, AuthenticationConstants.ACCESS_TOKEN_NAME), CookieUtil.getCookie(request, AuthenticationConstants.REFRESH_TOKEN_NAME)}) { + if (token != null) { + tokenStorageCommandService.addBlackList(token); + } + } + + CookieUtil.addCookie(request, response, AuthenticationConstants.ACCESS_TOKEN_NAME, loginResponse.accessToken(), (int) tokenQueryService.getAccessTokenExpiration().toSeconds()); + CookieUtil.addCookie(request, response, AuthenticationConstants.REFRESH_TOKEN_NAME, loginResponse.refreshToken(), (int) tokenQueryService.getRefreshTokenExpiration().toSeconds()); + tokenStorageCommandService.addRefreshToken(customUserDetails.getId(), loginResponse.refreshToken()); + } + +} diff --git a/src/main/java/org/withtime/be/withtimebe/global/security/manager/TokenManager.java b/src/main/java/org/withtime/be/withtimebe/global/security/manager/TokenManager.java new file mode 100644 index 0000000..38ad7b0 --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/global/security/manager/TokenManager.java @@ -0,0 +1,9 @@ +package org.withtime.be.withtimebe.global.security.manager; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.withtime.be.withtimebe.global.security.domain.CustomUserDetails; + +public interface TokenManager { + void addToken(HttpServletRequest request, HttpServletResponse response,CustomUserDetails customUserDetails); +} diff --git a/src/main/resources/application-develop.yml b/src/main/resources/application-develop.yml index 8290dfd..34e0df7 100644 --- a/src/main/resources/application-develop.yml +++ b/src/main/resources/application-develop.yml @@ -26,6 +26,45 @@ spring: auth: true starttls: enable: true + security: + oauth2: + client: + registration: + kakao: + authorization-grant-type: authorization_code + client-id: ${KAKAO_CLIENT_ID} + redirect-uri: ${KAKAO_REDIRECT_URI} + client-authentication-method: client_secret_post + scope: + - account_email + naver: + authorization-grant-type: authorization_code + redirect-uri: ${NAVER_REDIRECT_URI} + client-id: ${NAVER_CLIENT_ID} + client-secret: ${NAVER_CLIENT_SECRET} + scope: + - email + google: + client-id: ${GOOGLE_CLIENT_ID} + client-secret: ${GOOGLE_CLIENT_SECRET} + redirect-uri: ${GOOGLE_REDIRECT_URI} + scope: + - email + provider: + kakao: + authorization-uri: https://kauth.kakao.com/oauth/authorize + token-uri: https://kauth.kakao.com/oauth/token + user-info-uri: https://kapi.kakao.com/v2/user/me + user-name-attribute: id + naver: + authorization_uri: https://nid.naver.com/oauth2.0/authorize + token-uri: https://nid.naver.com/oauth2.0/token + user-info-uri: https://openapi.naver.com/v1/nid/me + user-name-attribute: response + google: + authorization-uri: https://accounts.google.com/o/oauth2/v2/auth + token-uri: https://oauth2.googleapis.com/token + user-info-uri: https://www.googleapis.com/userinfo/v2/me jwt: secret: ${JWT_SECRET} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 91d21de..ebb1f92 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -11,6 +11,60 @@ spring: show-sql: true hibernate: ddl-auto: update + data: + redis: + host: ${REDIS_HOST} + port: 6379 + mail: + host: ${MAIL_SENDER_HOST} + port: 587 + username: ${MAIL_SENDER_USERNAME} + password: ${MAIL_SENDER_PASSWORD} + properties: + mail: + smtp: + auth: true + starttls: + enable: true + security: + oauth2: + client: + registration: + kakao: + authorization-grant-type: authorization_code + client-id: ${KAKAO_CLIENT_ID} + redirect-uri: ${KAKAO_REDIRECT_URI} + client-authentication-method: client_secret_post + scope: + - account_email + naver: + authorization-grant-type: authorization_code + redirect-uri: ${NAVER_REDIRECT_URI} + client-id: ${NAVER_CLIENT_ID} + client-secret: ${NAVER_CLIENT_SECRET} + scope: + - email + google: + client-id: ${GOOGLE_CLIENT_ID} + client-secret: ${GOOGLE_CLIENT_SECRET} + redirect-uri: ${GOOGLE_REDIRECT_URI} + scope: + - email + provider: + kakao: + authorization-uri: https://kauth.kakao.com/oauth/authorize + token-uri: https://kauth.kakao.com/oauth/token + user-info-uri: https://kapi.kakao.com/v2/user/me + user-name-attribute: id + naver: + authorization_uri: https://nid.naver.com/oauth2.0/authorize + token-uri: https://nid.naver.com/oauth2.0/token + user-info-uri: https://openapi.naver.com/v1/nid/me + user-name-attribute: response + google: + authorization-uri: https://accounts.google.com/o/oauth2/v2/auth + token-uri: https://oauth2.googleapis.com/token + user-info-uri: https://www.googleapis.com/userinfo/v2/me jwt: secret: ${JWT_SECRET}