Skip to content

Conversation

@m-a-king
Copy link
Collaborator

@m-a-king m-a-king commented Nov 24, 2024

resolved:

📌 과제 설명

  • Spring Security 및 Spring OAuth2 Client/Server를 활용한 인증 구현
  • Volunteer 도메인 및 관련 서비스 로직 추가

👩‍💻 요구 사항과 구현 내용

1. 기본 설정

  • Redis 설정 추가: 로컬 환경과 테스트 환경에서 작동하도록 구성
  • OAuth 클라이언트 설정: 네이버 OAuth 클라이언트 등록 정보 추가
  • JWT 설정 추가: 테스트 환경에서 토큰 생성을 위한 시크릿 키 설정
  • 프론트엔드 URL 및 Spring Security 로깅 수준 설정

2. Redis 관련 작업

  • Redis Template, ConnectionFactory 추가

3. ENUM

  • 봉사자 등급 Enum 추가
  • 유저 역할(Role) Enum 추가
  • OAuth 제공자 Enum 추가
  • 성별 ENUM 추가

4. 자원봉사자 도메인

  • Volunteer 엔티티 추가
    • Volunteer 등록을 위한 VolunteerRegisterRequestDto 추가
    • createDefault 메서드로 여러 기본 값이 설정된 채로 만 생성 가능
  • VolunteerDetail 엔티티 추가
  • VolunteerRepository 인터페이스 추가
  • VolunteerDetailRepository 인터페이스 추가
  • Volunteer 등록을 위한 UseCase 및 Service 구현
  • Volunteer 조회을 위한 UseCase 및 Service 구현

5. OAuth 관련 작업

  • NaverUser 엔티티 추가
  • NaverUserRepository 인터페이스 추가
  • NaverUserProfileResponseDto 구현
  • OAuth2User를 NaverUserProfileResponseDto로 변환하는 유틸리티 추가
  • 네이버 OAuth 사용자 등록 UseCase 및 Service 구현
  • 네이버 사용자 존재 여부 확인을 위한 UseCase 및 Service 구현
  • 네이버 OAuth 사용자 처리 UseCase 및 Service 구현
  • OAuth 인증 성공 처리 핸들러 구현
  • Spring Security 설정 추가 및 OAuth2 인증 처리 구성
  • 쿠키 설정을 위한 SetCookieUseCase 및 SetCookieService 구현
  • 리디렉션을 위한 RedirectConfig, RedirectUseCase, RedirectService 구현

6. 테스트 환경 및 테스트 코드 추가

  • 테스트 환경을 위한 Redis, 프론트엔드, JWT 설정 추가
  • 봉사자 ID 조회 서비스 테스트 추가
  • 봉사자와 상세 정보 저장 서비스 테스트 추가

✅ PR 포인트 & 궁금한 점

  • 별도의 Controller 없이 프레임워크에 의존한 OAuth 인증 흐름에 집중해주세요!
  • 새로운 OAuth Provider가 추가되어도 문제가 없는지 확인해주세요!

…l 추가

- redis host, port, password 정보 추가
- spring security, oauth(NAVER) 관련 정보 추가
- frontend root url 추가 (임시 - localhost)
- security logging level DEBUG로 설정
- Redis 연결을 위한 LettuceConnectionFactory 설정
- RedisTemplate 설정 및 key/value 직렬화를 위한 StringRedisSerializer 적용
- RedisStandaloneConfiguration을 사용하여 host, port, password 설정
- RED부터 RAINBOW까지 8개의 등급 정의
- VOLUNTEER, CENTER, ADMIN 3가지 사용자 역할 정의
- 각 역할은 시스템에서의 권한 구분을 위해 사용
- NAVER 제공자를 위한 Enum 항목 추가
- providerName 필드를 통해 OAuth 제공자(registerId)의 이름 관리
- 문자열을 기반으로 OAuthProvider를 찾는 from 메서드 구현
- BaseEntity 사용을 위해 @EnableJpaAuditing 어노테이션 추가
- 남, 여
- 이외의 값은 미지정
- 필드:
  - volunteerId: UUID (BINARY(16))
  - gender: EnumType.STRING
  - birthDate: 문자열 (YYYY-MM-DD 형식)
  - contactNumber: 문자열

- 정적 팩토리 메서드를 통해 DTO로부터 VolunteerDetail 생성 가능
- BaseEntity 상속
- 필드:
  - id: UUID (BINARY(16), 고유 식별자)
  - oauthProvider: OAuth 인증 제공자 (EnumType.STRING)
  - oauthId: OAuth 제공자 ID
  - tier: 자원봉사 티어 (EnumType.STRING)

- 정적 메서드를 통해 초기 값이 설정된 Volunteer 객체 생성 가능
- findByOauthId: OAuth ID를 기준으로 Volunteer 조회
- SetCookieService 클래스 구현:
  - SetCookieUseCase를 구현한 쿠키 설정 서비스
  - setToken 메서드를 통해 토큰 타입(TokenType)에 따라 쿠키 생성 및 응답 헤더에 추가
  - generateCookie 메서드로 HttpOnly, Secure, SameSite 등 안전한 쿠키 생성 로직 포함
  - ResponseCookie를 활용
- RegisterVolunteerUseCase 구현체로 자원봉사자(유저) 등록 로직 처리
- Volunteer 엔티티 생성 및 VolunteerRepository에 저장
- VolunteerDetail 엔티티 생성 및 VolunteerDetailRepository에 저장
- createDefault 및 VolunteerDetail.of 메서드를 활용한 객체 생성
- 네이버 사용자 프로필 정보를 처리하기 위한 DTO 클래스 추가
- 필드:
  - resultcode: 결과 코드
  - message: 결과 메시지
  - response: 응답 데이터 (중첩 record)
    - id: 네이버 사용자 일련 번호
    - name: 이름
    - email: 이메일
    - gender: 성별 (F, M, U)
    - birthday: 생일 (MM-DD)
    - birthyear: 출생 연도
    - mobile: 휴대 전화 번호
- JSON 직렬화를 위한 @JsonNaming
- mobile-164 pattern 무시를 위한 @JsonIgnoreProperties
- toVolunteerRegisterRequestDto 메서드 추가:
  - Naver 사용자 데이터를 VolunteerRegisterRequestDto로 변환
  - OAuthProvider를 NAVER로 설정하여 Volunteer 등록 로직과 연동
- 네이버 사용자 정보를 저장하기 위한 NaverUser 엔티티 구현
- 정적 팩토리 메서드: OAuth ID를 기반으로 NaverUser 객체를 생성
- OAuth2User 객체의 attributes를 NaverUserProfileResponseDto로 변환
- ObjectMapper를 사용하여 JSON 데이터를 DTO로 매핑
- RedirectConfig:
  - RedirectStrategy Bean:
    - DefaultRedirectStrategy를 사용하여 리다이렉트 처리

- RedirectService:
  - RedirectStrategy를 사용하여 주어진 URL로 클라이언트를 리다이렉트
- SimpleUrlAuthenticationFailureHandler를 상속하여 OAuth 인증 실패 처리, 로그 기록
- 프론트엔드와의 협의 후 추가 처리 로직 구현 예정 (TODO 추가)
- 사용자 정보 처리 로직:
  - OAuth2User를 NaverUserProfileResponseDto로 변환
  - 사용자 존재 여부 확인 (CheckNaverUserUseCase)

- 신규 사용자일 경우:
  - 네이버 사용자 등록 (RegisterNaverUserUseCase)
  - 자원봉사자 등록 (RegisterVolunteerUseCase)
- 기존 사용자일 경우 OAuth2User 반환
- DefaultOAuth2UserService를 활용하여 OAuth2 사용자 로드
- OAuthProvider에 따라 네이버 사용자 처리 로직 분기:
  - NaverOAuth2UserInfoService와 연동
  - 추후에 추가 가능
- onAuthenticationSuccess 구현:
  - OAuthProvider에 따라 사용자 정보를 처리 (현재는 NAVER만 지원)
    - getOAuthProvider: 인증된 OAuth 제공자 추출
    - 지원하지 않는 OAuth 제공자에 대한 예외 처리 추가
  - ProcessNaverOAuthUserService를 통해 네이버 사용자 정보 처리
  - FindVolunteerIdUseCase로 OAuth ID 기반 Volunteer ID 조회
  - GenerateTokensOnLoginUseCase로 RefreshToken 저장 및 AccessToken 생성
  - SetCookieUseCase를 사용하여 AccessToken을 클라이언트 쿠키에 저장
  - RedirectUseCase로 프론트엔드 URL로 리다이렉션 수행
- Stateless 세션 관리 설정(SessionCreationPolicy.STATELESS)
- CSRF, HTTP Basic, Form Login, Logout 기능 비활성화

- SecurityFilterChain 설정:
  - 공개 API 경로 설정
  - 그 외 모든 요청 인증 필요

- OAuth2 인증 처리:
  - CustomOAuth2UserService로 사용자 정보 처리
  - CustomOAuthSuccessHandler로 인증 성공 처리
  - CustomOAuthFailureHandler로 인증 실패 처리

- TODO:
  - JWT 인증 필터 추가
  - JWT 예외 필터 추가
@m-a-king m-a-king changed the title Feature/40 Spring Security / OAuth2 Clinet, Server 구현 Feature/40 Spring Security / OAuth2 Client, Server 구현 Nov 24, 2024
String oAuthId;
switch (getOAuthProvider(authentication)) {
case NAVER -> oAuthId = processNaverOAuthService.processOAuthUser(authentication);
default -> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 자바 21 문법인가요?
Good 입니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다. 자바 17 문법입니다 ㅎㅎ


@Slf4j
@Service
@RequiredArgsConstructor
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

문제가 되는 부분은 아니지만 NaverOAuth2UserInfoService랑 어노테이션 순서가 달라보여서 댓글 남겨놓겠습니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 맞추려고 노력했는데, 자꾸 섞이네요. 라이브 템플릿으로 관리해보겠습니다!

import static com.somemore.auth.oauth.naver.util.OAuthResponseConverter.convertToNaverUserProfileResponseDto;

@Slf4j
@Component
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@service가 아니라 @component로 두신 이유가 있으신가요?
기능적으로 차이가 없는건 알고있지만 궁금해서 여쭤봤습니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 클래스는 서비스로 두는 것이 맞았는데, 잘못 작성했습니다. 다른 클래스들에서는 일부러 구분한 것은 맞습니다.
명시적으로 서비스와 그 서비스를 보조하는 작은 컴포넌트로 구분하고 싶었습니다!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이해했습니다 감사합니다!


.authorizeHttpRequests(request ->
request
.requestMatchers(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀드렸었지만 배포전까지만 전부 permitAll 허가해주시면 감사하겠습니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵! 수정하겠습니다

Copy link
Collaborator

@leebs0521 leebs0521 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다~
상당히 많은 부분 하셨군요...

@Entity
@Table(name = "naver_user")
public class NaverUser {
@Id
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naver_user 테이블 생기는건가요??
ERD 상에 없길래 여쭤봅니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 erdcloud에 그려두겠습니다

public class OAuthResponseConverter {

private OAuthResponseConverter(){
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NoArgsConstructor(access = PRIVATE) 해도 될것 같습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 것 같습니다!


@SpringBootTest
@Transactional
class RegisterVolunteerServiceTest extends IntegrationTestSupport {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IntegrationTestSupport 상속받으면 @SpringBootTest 이거 없어도 됩니당

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트 안되는 거 고치다가 해결한 시점부터 설정 부분을 건드리지 않았더니 덕지덕지 붙었네요.

private VolunteerDetailRepository volunteerDetailRepository;

@AfterEach
void tearDown() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트에서 @transactional 쓰시면 tearDown 안쓰셔도 됩니당 자동 롤백돼요


@SpringBootTest
class FindVolunteerIdServiceTest extends IntegrationTestSupport {

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 @SpringBootTest 없어도 될것 같아요


import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IntegrationTestSupport에 포함되어 있는데 혹시 적용이 안되셔서 넣어주신건가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트 안되는 거 고치다가 해결한 시점부터 설정 부분을 건드리지 않았더니 덕지덕지 붙었네요.

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

@SpringBootTest
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IntegrationTestSupport에 포함되어 있는데 혹시 적용이 안되셔서 넣어주신건가요?

@7zrv
Copy link
Collaborator

7zrv commented Nov 25, 2024

많이 신경쓰신게 느껴져요 정말로 고생하셨습니다

Copy link
Collaborator

@ayoung-dev ayoung-dev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정말 고생많으셨습니다..
sonarcloud가 잡아준 이슈들도 수정하면 좋을 거 같아요!

Comment on lines +11 to +24
String resultcode, // 결과 코드
String message, // 결과 메시지
Response response // 응답 데이터
) {
@JsonIgnoreProperties(ignoreUnknown = true)
public record Response(
String id, // 일련 번호
String name, // 이름
String email, // 이메일
String gender, // 성별 (F, M, U)
String birthday, // 생일 (MM-DD)
String birthyear, // 출생 연도
String mobile // 휴대 전화 번호
) {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

주석 일부러 넣어두신 건가요?
저희 주석 최대한 안 남기기로 했던 거 같아서요

Copy link
Collaborator Author

@m-a-king m-a-king Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네, 외부 시스템과 연결된 DTO라서 일부러 표기해뒀습니다.

public class Volunteer extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(name = "id", nullable = false, updatable = false, columnDefinition = "BINARY(16)")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 length로만 해뒀는데 updatable이랑 columnDefinition 좋네요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니당

@Column(name = "id", nullable = false)
private Long id;

@Column(name = "volunteer_id", nullable = false, columnDefinition = "BINARY(16)")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기는 updatable을 안한 이유가 따로 있나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개념적인 FK라고 생각해서 변경되지 않는 값이라고 생각했습니다. 일관성 있게 모두 수정하는 게 좋을까요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Volunteer 엔티티의 Id 값에서 updatable을 빼는 건 어떨까요?
@id 어노테이션이 붙어서 중복되는 거 같기도 하고 일관성도 있을 거 같아보여요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 수정했습니다

// Volunteer
assertThat(volunteer.getOauthProvider()).isEqualTo(OAuthProvider.NAVER);
assertThat(volunteer.getOauthId()).isEqualTo("oauth-id-example");
assertThat(volunteer.getNickname()).hasSize(8); // 8자리 default UUID
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id가 UUID이고 nickname은 default로 빈값 들어가는 거 아닌가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nickname 설정 로직을 확인해보면, 기본값으로 UUID를 생성한 뒤, 이 값을 substring으로 잘라 길이 8의 랜덤값을 설정합니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아! 감사합니다~

import static com.somemore.auth.oauth.naver.util.OAuthResponseConverter.convertToNaverUserProfileResponseDto;

@Slf4j
@Component
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이해했습니다 감사합니다!

7zrv and others added 3 commits November 25, 2024 11:51
* feat: 예외 메시지 상수를 enum으로 관리하도록 개선

- 예외 메시지를 ExceptionMessage enum으로 분리하여 중앙 관리
- 상수로 관리하여 메시지의 일관성 유지 및 중복 제거
- 향후 메시지 변경 시 한 곳에서 관리 가능하도록 개선

* feat: 선호물품 등록 기능 구현

- 기관이 선호하는 물품을 등록할 수 있는 API 추가
- 선호물품 테이블 추가
- 선호물품 등록 요청 DTO 구현
- 선호물품 등록 서비스 레이어 구현
- 테스트 코드 작성 및 검증 완료

* feat: 기관 존재 여부 검증 기능 추가

- 기관 존재 여부를 확인하는 validateCenterExists 메서드 구현
- 존재하지 않는 센터에 대해 BadRequestException 처리 추가
- 테스트 코드 작성및 검증 완

* feat: 기관 프로필 조회 기능 구현

- 기관 id로 기관 정보와 기관이 등록한 선호 물품을 조회하는 메서드 구현
- 레포지토리에 기관 id를 통한 단건 조회 메서드 추가
- 기관과 선호물품을 함께 반환하기 위한 응답Dto 구현
- 테스트 코드 작성및 검증 완료

* chore: sonar 커버리지 설정 변경

- 누락된 exclude 설정을 추가

* chore: 파일 마지막 라인에 개행 추가

- 코딩 표준을 준수하기 위해 "no newline at end of file" 경고를 수정했습니다.

* Fix: 코드 리뷰 사항 반영

- 서비스 레이어의 public 메서드를 상단에 배치하여 가독성 향상
- CQRS 적용을 위한 Query, Command 서비스 클래스에 @Transactinal(readOnly = true), @Transactinal 어노테이션을 각각 추가
- 레포지토리의 불필요한 @query 어노테이션 삭제

* feat: 예외 메시지 상수를 enum으로 관리하도록 개선

- 예외 메시지를 ExceptionMessage enum으로 분리하여 중앙 관리
- 상수로 관리하여 메시지의 일관성 유지 및 중복 제거
- 향후 메시지 변경 시 한 곳에서 관리 가능하도록 개선

* feat: 선호물품 등록 기능 구현

- 기관이 선호하는 물품을 등록할 수 있는 API 추가
- 선호물품 테이블 추가
- 선호물품 등록 요청 DTO 구현
- 선호물품 등록 서비스 레이어 구현
- 테스트 코드 작성 및 검증 완료

* feat: 기관 존재 여부 검증 기능 추가

- 기관 존재 여부를 확인하는 validateCenterExists 메서드 구현
- 존재하지 않는 센터에 대해 BadRequestException 처리 추가
- 테스트 코드 작성및 검증 완

* feat: 기관 프로필 조회 기능 구현

- 기관 id로 기관 정보와 기관이 등록한 선호 물품을 조회하는 메서드 구현
- 레포지토리에 기관 id를 통한 단건 조회 메서드 추가
- 기관과 선호물품을 함께 반환하기 위한 응답Dto 구현
- 테스트 코드 작성및 검증 완료

* chore: sonar 커버리지 설정 변경

- 누락된 exclude 설정을 추가

* chore: 파일 마지막 라인에 개행 추가

- 코딩 표준을 준수하기 위해 "no newline at end of file" 경고를 수정했습니다.

* Fix: 코드 리뷰 사항 반영

- 서비스 레이어의 public 메서드를 상단에 배치하여 가독성 향상
- CQRS 적용을 위한 Query, Command 서비스 클래스에 @Transactinal(readOnly = true), @Transactinal 어노테이션을 각각 추가
- 레포지토리의 불필요한 @query 어노테이션 삭제

* Refactor: 코드 리뷰 사항 반영

- 기관 프로필 조회 응답값중 선호 물품 엔티티를 직접 응답하는것이 아닌 Dto로 전환하여 응답하도록 수정과 그에 따른 usecase, service 수정, 선호물품 응답 Dto 생성
- 단위 테스트 코드 작성과 검증 완료
@sonarqubecloud
Copy link

@m-a-king m-a-king merged commit 03ea3ab into main Nov 25, 2024
2 checks passed
@m-a-king m-a-king deleted the feature/40-auth-spring-security-oauth branch November 25, 2024 02:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Spring Security와 Spring OAuth2를 조합하여 인증 구현 [FEATURE] Spring security/oauth2, JWT를 활용한 회원가입/로그인

5 participants