Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2121307
feat(oauth): oauth 정보 등록 추가
m-a-king Dec 30, 2024
bf43acc
refactor: 패키지 이동
m-a-king Dec 30, 2024
cdf7441
refactor(user): 유저 인증 정보 dto 네이밍 변경
m-a-king Dec 31, 2024
fc18102
refactor(user): userAuthInfo 네이밍 변경
m-a-king Jan 1, 2025
918a5aa
test(user): 유저 공통 속성 저장 테스트 추가
m-a-king Jan 1, 2025
7d93851
feat(kakao): 카카오 OAuth 설정 추가
m-a-king Jan 12, 2025
40fe2fe
feat(kakao): 카카오 OAuth 응답 DTO 추가
m-a-king Jan 12, 2025
8a1adfb
feat(oauth): 공통 oauth 정보 클래스 정적 팩토리 메서드 추가
m-a-king Jan 12, 2025
9416b84
feat(oauth): OAuth2User 클래스에 OAuthProvider를 필드로 추가한 CustomOAuth2User 추가
m-a-king Jan 12, 2025
62aa294
feat(oauth): defaultOAuth2UserService 의존성 주입 추가, CustomOAuth2User 사용으…
m-a-king Jan 12, 2025
43ca606
feat(oauth): OAuth 제공자 목록에 카카오 추가, 로직 개선
m-a-king Jan 12, 2025
fc9a7f6
feat(oauth): CustomOAuth2User 사용으로 로직 추가, 변경
m-a-king Jan 12, 2025
2c228d8
feat(oauth): authentication to CustomOAuth2User 로직 개선, 쿠키 값 수정
m-a-king Jan 12, 2025
99c877e
feat(oauth): 네이버 유저 응답 정보 전용 컨버터 삭제
m-a-king Jan 12, 2025
12a0f3f
feat(oauth): 주석 수정
m-a-king Jan 12, 2025
7f12ef2
feat: 불필요한 파일 삭제
m-a-king Jan 12, 2025
ae1799d
feat(oauth): 소셜 로그인 URL 획득 컨트롤러 개선
m-a-king Jan 12, 2025
f94f803
test(oauth): 테스트 수정
m-a-king Jan 13, 2025
fa8db02
test(oauth): DisplayName 추가
m-a-king Jan 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.somemore.domains.volunteer.controller;

import com.somemore.domains.volunteer.usecase.GenerateOAuthUrlUseCase;
import com.somemore.global.common.response.ApiResponse;
import com.somemore.global.exception.BadRequestException;
import com.somemore.global.auth.signout.usecase.SignOutUseCase;
import com.somemore.global.common.response.ApiResponse;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand Down Expand Up @@ -32,11 +31,7 @@ public RedirectView signIn(
@Parameter(name = "oauthProvider", description = "OAuth 제공자 선택", example = "naver", required = true, schema = @Schema(allowableValues = {"naver"}))
@PathVariable("oauthProvider") String oauthProvider) {

String redirectUrl = switch (oauthProvider.toLowerCase()) {
case "naver" -> generateOAuthUrlUseCase.generateUrl(oauthProvider);

default -> throw new BadRequestException("지원되지 않는 OAuth 제공자: " + oauthProvider);
};
String redirectUrl = generateOAuthUrlUseCase.generateUrl(oauthProvider);

return new RedirectView(redirectUrl);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.somemore.domains.volunteer.service;

import com.somemore.domains.volunteer.usecase.GenerateOAuthUrlUseCase;
import com.somemore.global.auth.oauth.domain.OAuthProvider;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -18,7 +19,7 @@ public class GenerateOAuthUrlService implements GenerateOAuthUrlUseCase {
@Override
public String generateUrl(String oAuthProvider) {
return UriComponentsBuilder.fromHttpUrl(generateBaseUrl())
.pathSegment(oAuthProvider)
.pathSegment(OAuthProvider.from(oAuthProvider).getProviderName())
.build()
.toUriString();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.somemore.global.auth.oauth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;

@Configuration
public class OAuth2Config {

@Bean
public DefaultOAuth2UserService defaultOAuth2UserService() {
return new DefaultOAuth2UserService();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.somemore.global.auth.oauth.converter;

import com.somemore.global.auth.oauth.domain.CommonOAuthInfo;
import org.springframework.security.oauth2.core.user.OAuth2User;
import com.somemore.global.auth.oauth.domain.CustomOAuth2User;

public interface OAuthResponseConverter {
CommonOAuthInfo convert(OAuth2User oAuth2User);
CommonOAuthInfo convert(CustomOAuth2User oAuth2User);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.somemore.global.auth.oauth.converter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.somemore.global.auth.oauth.domain.CommonOAuthInfo;
import com.somemore.global.auth.oauth.domain.CustomOAuth2User;
import com.somemore.global.auth.oauth.domain.OAuthProvider;
import com.somemore.global.auth.oauth.dto.KakaoUserProfileResponseDto;
import com.somemore.global.auth.oauth.dto.NaverUserProfileResponseDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class OAuthResponseConverterImpl implements OAuthResponseConverter {

private final ObjectMapper objectMapper;

@Override
public CommonOAuthInfo convert(CustomOAuth2User oAuth2User) {
OAuthProvider provider = oAuth2User.getProvider();
switch (provider) {
case KAKAO:
KakaoUserProfileResponseDto kakaoUserProfile = objectMapper.convertValue(
oAuth2User.getAttributes(),
KakaoUserProfileResponseDto.class
);
return CommonOAuthInfo.of(
OAuthProvider.KAKAO,
kakaoUserProfile.id()
);
case NAVER:
NaverUserProfileResponseDto naverUserProfile = objectMapper.convertValue(
oAuth2User.getAttributes(),
NaverUserProfileResponseDto.class
);
return CommonOAuthInfo.of(
OAuthProvider.NAVER,
naverUserProfile.response().id()
);
default:
throw new IllegalArgumentException("지원하지 않는 OAuth Provider입니다.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@
public record CommonOAuthInfo(
OAuthProvider provider,
String oauthId) {

public static CommonOAuthInfo of(OAuthProvider provider, String oauthId) {
return new CommonOAuthInfo(provider, oauthId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.somemore.global.auth.oauth.domain;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;

import java.util.Collection;
import java.util.Map;

@RequiredArgsConstructor
@Getter
public class CustomOAuth2User implements OAuth2User {

private final OAuth2User oAuth2User;
private final OAuthProvider provider;

@Override
public Map<String, Object> getAttributes() {
return oAuth2User.getAttributes();
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return oAuth2User.getAuthorities();
}

@Override
public String getName() {
return oAuth2User.getName();
}

public static CustomOAuth2User of(OAuth2User oAuth2User, OAuthProvider oAuthProvider) {
return new CustomOAuth2User(oAuth2User, oAuthProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

@Getter
public enum OAuthProvider {
NAVER("naver");
NAVER("naver"),
KAKAO("kakao"),
;

private final String providerName;

Expand All @@ -14,7 +16,7 @@ public enum OAuthProvider {

public static OAuthProvider from(String providerName) {
for (OAuthProvider provider : values()) {
if (provider.providerName.equals(providerName)) {
if (provider.providerName.equalsIgnoreCase(providerName)) {
return provider;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.somemore.global.auth.oauth.dto;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record KakaoUserProfileResponseDto(
String id // OAuth ID
) {
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.somemore.global.auth.oauth.naver.dto;
package com.somemore.global.auth.oauth.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
Expand All @@ -12,7 +12,7 @@ public record NaverUserProfileResponseDto(
) {
@JsonIgnoreProperties(ignoreUnknown = true)
public record Response(
String id // 일련 번호
String id // OAuth ID
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.somemore.global.auth.jwt.domain.EncodedToken;
import com.somemore.global.auth.jwt.domain.TokenType;
import com.somemore.global.auth.jwt.usecase.GenerateTokensOnLoginUseCase;
import com.somemore.global.auth.oauth.domain.CustomOAuth2User;
import com.somemore.global.auth.oauth.processor.OAuthUserProcessor;
import com.somemore.global.auth.redirect.RedirectUseCase;
import com.somemore.user.domain.UserRole;
Expand Down Expand Up @@ -35,7 +36,7 @@ public class CustomOAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHan
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {
OAuth2User oauthUser = extractOAuthUser(authentication);
CustomOAuth2User oauthUser = extractOAuthUser(authentication);
UUID userId = oauthUserProcessor.fetchUserIdByOAuthUser(oauthUser);

processAccessToken(response, userId);
Expand All @@ -55,14 +56,28 @@ private void processAccessToken(HttpServletResponse response, UUID userId) {
generateTokensOnLoginUseCase.generateLoginToken(
userId, UserRole.getOAuthUserDefaultRole());

cookieUseCase.setToken(response, loginToken.getValueWithPrefix(), TokenType.SIGN_IN);
cookieUseCase.setToken(response, loginToken.value(), TokenType.SIGN_IN);
}

private OAuth2User extractOAuthUser(Authentication authentication) {
private CustomOAuth2User extractOAuthUser(Authentication authentication) {
OAuth2AuthenticationToken oAuth2AuthenticationToken = castToOAuth2AuthenticationTokenBy(authentication);
OAuth2User oAuth2User = oAuth2AuthenticationToken.getPrincipal();
return castToCustomOAuth2UserBy(oAuth2User);
}

private OAuth2AuthenticationToken castToOAuth2AuthenticationTokenBy(Authentication authentication) {
if (authentication instanceof OAuth2AuthenticationToken token) {
return token.getPrincipal();
return token;
}
log.error("Authentication 객체가 OAuth2AuthenticationToken 타입이 아닙니다: {}", authentication.getClass().getName());
throw new IllegalArgumentException("잘못된 인증 객체입니다.");
throw new IllegalArgumentException();
}

private CustomOAuth2User castToCustomOAuth2UserBy(OAuth2User oAuth2User) {
if (oAuth2User instanceof CustomOAuth2User customOAuth2User) {
return customOAuth2User;
}
log.error("OAuth2User 객체가 CustomOAuth2User 타입이 아닙니다: {}", oAuth2User.getClass().getName());
throw new IllegalArgumentException();
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.somemore.global.auth.oauth.processor;

import org.springframework.security.oauth2.core.user.OAuth2User;
import com.somemore.global.auth.oauth.domain.CustomOAuth2User;

import java.util.UUID;

public interface OAuthUserProcessor {
UUID fetchUserIdByOAuthUser(OAuth2User oauthUser);
UUID fetchUserIdByOAuthUser(CustomOAuth2User oauthUser);
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package com.somemore.global.auth.oauth.processor;

import com.somemore.user.domain.UserRole;
import com.somemore.global.auth.oauth.checker.OAuthInfoChecker;
import com.somemore.global.auth.oauth.domain.CommonOAuthInfo;
import com.somemore.global.auth.oauth.converter.OAuthResponseConverter;
import com.somemore.global.auth.oauth.domain.CommonOAuthInfo;
import com.somemore.global.auth.oauth.domain.CustomOAuth2User;
import com.somemore.global.auth.oauth.registrar.OAuthInfoRegistrar;
import com.somemore.global.auth.oauth.service.OAuthInfoQueryService;
import com.somemore.user.domain.User;
import com.somemore.user.domain.UserRole;
import com.somemore.user.usecase.RegisterUserUseCase;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -29,7 +29,7 @@ public class OAuthUserProcessorImpl implements OAuthUserProcessor {
private final OAuthInfoQueryService oAuthInfoQueryService;

@Override
public UUID fetchUserIdByOAuthUser(OAuth2User oauthUser) {
public UUID fetchUserIdByOAuthUser(CustomOAuth2User oauthUser) {
CommonOAuthInfo oauthInfo = oauthResponseConverter.convert(oauthUser);
return findUserIdByOAuthInfo(oauthInfo);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,4 @@ public OAuthInfo register(User user, CommonOAuthInfo commonOAuthInfo) {
OAuthInfo oAuthInfo = OAuthInfo.create(user, commonOAuthInfo);
return oauthInfoRepository.save(oAuthInfo);
}


}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.somemore.global.auth.oauth.service;

import com.somemore.global.auth.oauth.domain.CustomOAuth2User;
import com.somemore.global.auth.oauth.domain.OAuthProvider;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
Expand All @@ -14,18 +16,26 @@
@RequiredArgsConstructor
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

private final DefaultOAuth2UserService oauth2UserService;

@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
try {
return fetchOAuthUser(userRequest);
OAuth2User oAuth2User = extractOAuth2User(userRequest);
OAuthProvider oAuthProvider = extractOAuthProvider(userRequest);

return CustomOAuth2User.of(oAuth2User, oAuthProvider);
} catch (OAuth2AuthenticationException e) {
log.error("OAuth 사용자 정보를 불러오는 중 문제가 발생했습니다: {}", e.getMessage(), e);
throw e;
}
}

private OAuth2User fetchOAuthUser(OAuth2UserRequest userRequest) {
OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService = new DefaultOAuth2UserService();
return oAuth2UserService.loadUser(userRequest);
private OAuth2User extractOAuth2User(OAuth2UserRequest userRequest) {
return oauth2UserService.loadUser(userRequest);
}

private OAuthProvider extractOAuthProvider(OAuth2UserRequest userRequest) {
return OAuthProvider.from(userRequest.getClientRegistration().getRegistrationId());
}
}
13 changes: 12 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,25 @@ spring:
redirect-uri: ${NAVER_REDIRECT_URL}
authorization-grant-type: authorization_code
scope: ${NAVER_SCOPE}
kakao:
client-id: ${KAKAO_CLIENT_ID}
clientSecret: ${KAKAO_CLIENT_SECRET}
redirect-uri: ${KAKAO_REDIRECT_URL}
client-authentication-method: client_secret_post
authorization-grant-type: authorization_code
scope: ${KAKAO_SCOPE}

provider:
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 # 네이버 API가 사용자 정보를 "response" 객체 안에 넣어 반환
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

web:
locale: ko_KR
Expand All @@ -72,7 +84,6 @@ spring:
max-file-size: 8MB
max-request-size: 8MB


schedules:
cron:
updateCommunityBoardDocuments: "0 0 0 * * *"
Expand Down
Loading
Loading