Skip to content
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

feat: 소셜 로그인 구현 #9

Merged
merged 48 commits into from
Jan 12, 2025
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
fe102fc
feat: 소셜 로그인 DTO 추가
jwnnoh Jan 4, 2025
2721850
feat: 소셜 로그인 컨트롤러 로직 구현
jwnnoh Jan 4, 2025
d3677b2
fix: Docker 이미지가 빌드되지 못하는 오류 수정
yechan-kim Jan 5, 2025
182b8d1
feat: 모니터링 모듈 생성 및 Sentry 의존성 추가
yechan-kim Jan 5, 2025
3371d8f
feat: Sentry 설정
yechan-kim Jan 5, 2025
7c936fa
chore: Sentry WebHook 설정
yechan-kim Jan 5, 2025
bbaae57
chore: 의존성 설정 변경
yechan-kim Jan 5, 2025
e3f62c2
chore: Sentry 설정을 위한 프로파일 값 추가
yechan-kim Jan 5, 2025
e784620
chore: 불필요한 플러그인 삭제
yechan-kim Jan 6, 2025
70954fc
style: 코드 컨벤션에 맞게 코드 수정
yechan-kim Jan 6, 2025
3631cc6
fix: `.xml`에서 Jasypt 암호화를 인식하지 못하는 오류 해결
yechan-kim Jan 6, 2025
ccad504
chore: local 환경에서 Sentry 및 WebHook 적용 해제
yechan-kim Jan 6, 2025
747599a
feat: JWT Utils 구현
jwnnoh Jan 5, 2025
641d23e
feat: JWT 커스텀 예외 구현
jwnnoh Jan 5, 2025
b814427
feat: 커스텀 인증 객체 구현
jwnnoh Jan 5, 2025
731122e
feat: Member 엔티티 <-> 도메인 매핑 로직 구현
jwnnoh Jan 6, 2025
11f2f9e
feat: 이메일을 통한 멤버 조회 로직 구현
jwnnoh Jan 6, 2025
c00f644
feat: 소셜 로그인을 위한 VO, DTO 구현
jwnnoh Jan 6, 2025
743b3ef
feat: 카카오 소셜 로그인 로직 구현
jwnnoh Jan 6, 2025
2174833
refactor: 멤버 조회시 Null을 Facade에서 처리하도록 변경
jwnnoh Jan 6, 2025
64679d3
refactor: 토큰 발급 로직 DI 적용
jwnnoh Jan 7, 2025
bc2ecc5
refactor: 멤버 도메인 패키지 변경 및 엔티티 생성을 위한 VO 추가
jwnnoh Jan 7, 2025
45cb711
feat: MemberVO를 통한 엔티티 생성 방식으로 변경
jwnnoh Jan 7, 2025
6d2ff1a
feat: 소셜 로그인 파사드 로직 구현
jwnnoh Jan 7, 2025
4fca6f9
chore: 환경변수 설정
jwnnoh Jan 7, 2025
c244c3f
feat: 예외처리 핸들러 및 필터 구현
jwnnoh Jan 7, 2025
98624b0
feat: Security 필터 밀 에러 핸들링 적용
jwnnoh Jan 7, 2025
c9a90ce
fix: Security 오류 수정
jwnnoh Jan 7, 2025
6f35809
fix: 리다이렉트 uri 수정
jwnnoh Jan 7, 2025
37deadd
fix: EOL 적용
jwnnoh Jan 7, 2025
ff66156
feat: 멤버 필드 socialId 추가
jwnnoh Jan 7, 2025
b5a2f99
feat: 인증 객체 판별 기준 email -> socialId 변경
jwnnoh Jan 7, 2025
5bbc5a4
fix: 토큰 검증 로직 필터 추가
jwnnoh Jan 7, 2025
c2c2cc4
feat: 소셜 로그인 오류 수정 및 응답 값 변경
jwnnoh Jan 7, 2025
0d60254
Merge branch 'main' into feat/#5
jwnnoh Jan 7, 2025
4998810
chore: 워크플로 수정
jwnnoh Jan 7, 2025
144069c
feat: 전역 예외처리 핸들러 구현
jwnnoh Jan 8, 2025
73a46ee
feat: Member 도메인 로직 테스트 구현
jwnnoh Jan 8, 2025
b7d7161
chore: ci 워크플로 수정
jwnnoh Jan 8, 2025
955cf8c
chore: ci 워크플로 오류 수정
jwnnoh Jan 8, 2025
392c374
chore: ci 워크플로 오류 수정
jwnnoh Jan 8, 2025
bb71c8e
Auth 도메인 서비스 테스트 코드 작성
jwnnoh Jan 8, 2025
c4b9116
style: 코드 컨벤션 수정
jwnnoh Jan 9, 2025
68c8b4f
test: 컨트롤러 mockMVC 테스트 코드 작성
jwnnoh Jan 9, 2025
cb4d422
test: AuthFacade 테스트 코드 작성
jwnnoh Jan 9, 2025
0ce9c62
test: persistent-db 멤버 테스트 코드 작성
jwnnoh Jan 9, 2025
f93ad98
style: 불필요 어노테이션 제거
jwnnoh Jan 11, 2025
cee299c
style: 스타일 수정
jwnnoh Jan 11, 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
7 changes: 3 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
name: CI with Test Results
name: 테스트 통과 여부 검사

on:
pull_request:
branches-ignore: [ "main" ]
branches: [ "main" ]

jobs:
dev-build-TEST:
test-result:
runs-on: ubuntu-22.04

steps:
- name: GitHub Checkout
uses: actions/checkout@v4
Expand Down
10 changes: 10 additions & 0 deletions module-domain/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,13 @@ val bootJar: BootJar by tasks

bootJar.enabled = false
jar.enabled = true

plugins {
kotlin("plugin.jpa") version "2.1.0"
}

dependencies {
implementation(project(":module-independent"))

implementation("org.springframework.boot:spring-boot-starter-data-jpa")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package site.yourevents.auth.port.`in`.usecase

import site.yourevents.auth.vo.KakaoProfile

interface AuthUseCase {
fun getKakaoUserInfoFromCode(code: String): KakaoProfile
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package site.yourevents.auth.port.`in`.usecase

import java.util.UUID

interface TokenUseCase {
fun generateAccessToken(
id: UUID,
socialId: String,
role: String,
): String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package site.yourevents.auth.port.out

import java.util.UUID

interface SecurityPort {
fun generateAccessToken(
id: UUID,
socialId: String,
role: String,
): String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package site.yourevents.auth.port.out

import site.yourevents.auth.vo.KakaoProfile

interface SocialPort {
fun getKakaoUserInfoFromCode(code: String) : KakaoProfile
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package site.yourevents.auth.service

import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import site.yourevents.auth.port.`in`.usecase.AuthUseCase
import site.yourevents.auth.port.out.SocialPort
import site.yourevents.auth.vo.KakaoProfile

@Service
@Transactional
class AuthService(
private val socialPort: SocialPort
) : AuthUseCase {
override fun getKakaoUserInfoFromCode(code: String): KakaoProfile {
return socialPort.getKakaoUserInfoFromCode(code)
}
}
jwnnoh marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package site.yourevents.auth.service

import org.springframework.stereotype.Service
import site.yourevents.auth.port.`in`.usecase.TokenUseCase
import site.yourevents.auth.port.out.SecurityPort
import java.util.UUID

@Service
class TokenService(
private val securityPort: SecurityPort,
) : TokenUseCase {
override fun generateAccessToken(
id: UUID,
socialId: String,
role: String,
): String {
return securityPort.generateAccessToken(
id,
socialId,
role,
)
jwnnoh marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package site.yourevents.auth.vo

data class KakaoProfile(
val socialId: String,
val nickname: String,
val email: String,
) {
companion object {
fun of(
socialId: String,
nickname: String,
email: String,
): KakaoProfile = KakaoProfile(
socialId,
nickname,
email,
)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package site.yourevents.invitation

import site.yourevents.member.Member
import site.yourevents.member.domain.Member
import java.util.UUID

class Guest(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package site.yourevents.invitation

import site.yourevents.member.Member
import site.yourevents.member.domain.Member
import java.util.UUID

class Invitation(
Expand Down
11 changes: 0 additions & 11 deletions module-domain/src/main/kotlin/site/yourevents/member/Member.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package site.yourevents.member.domain

import java.util.UUID

class Member(
private val id: UUID,
private val socialId: String,
private val nickname: String,
private val email: String,
) {
fun getId(): UUID = id

fun getSocialId(): String = socialId

fun getNickname(): String = nickname

fun getEmail(): String = email
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package site.yourevents.member.domain

import site.yourevents.auth.vo.KakaoProfile

data class MemberVO(
val socialId: String,
val nickname: String,
val email: String,
) {
companion object {
fun from(kakaoProfile: KakaoProfile): MemberVO =
MemberVO(
kakaoProfile.socialId,
kakaoProfile.nickname,
kakaoProfile.email,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package site.yourevents.member.exception

import site.yourevents.error.exception.ServiceException
import site.yourevents.type.ErrorCode

class MemberNotFountException : ServiceException(ErrorCode.MEMBER_NOT_FOUND)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package site.yourevents.member.port.`in`

import site.yourevents.auth.vo.KakaoProfile
import site.yourevents.member.domain.Member

interface MemberUseCase {
fun findBySocialId(socialId: String): Member?
1winhyun marked this conversation as resolved.
Show resolved Hide resolved

fun createMember(kakaoProfile: KakaoProfile): Member
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package site.yourevents.member.port.out.persistence

import site.yourevents.member.domain.Member
import site.yourevents.member.domain.MemberVO

interface MemberPersistencePort {
fun findBySocialId(socialId: String): Member?

fun save(memberVO: MemberVO): Member
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package site.yourevents.member.service

import org.springframework.stereotype.Service
import site.yourevents.auth.vo.KakaoProfile
import site.yourevents.member.domain.Member
import site.yourevents.member.domain.MemberVO
import site.yourevents.member.port.`in`.MemberUseCase
import site.yourevents.member.port.out.persistence.MemberPersistencePort

@Service
class MemberService(
private val memberPersistencePort: MemberPersistencePort
) : MemberUseCase {
override fun findBySocialId(socialId: String): Member? {
return memberPersistencePort.findBySocialId(socialId)
}

override fun createMember(kakaoProfile: KakaoProfile): Member {
return memberPersistencePort.save(MemberVO.from(kakaoProfile))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package site.yourevents.auth.service

import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe
import io.mockk.confirmVerified
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import site.yourevents.auth.port.out.SocialPort
import site.yourevents.auth.vo.KakaoProfile
import java.util.UUID

class AuthServiceTest : DescribeSpec({
lateinit var socialPort: SocialPort
lateinit var authService: AuthService
lateinit var id: UUID
lateinit var socialId: String
lateinit var nickname: String
lateinit var email: String
lateinit var authorizationCode: String

beforeTest {
socialPort = mockk<SocialPort>()
authService = AuthService(socialPort)

id = UUID.randomUUID()
socialId = "12345678"
nickname = "yes"
email = "[email protected]"
authorizationCode = "someKindOfAuthorizationCode"
}

describe("AuthService") {
context("정상적인 카카오 인증 코드가 주어졌을 때") {
it("SocialPort를 통해 KakaoProfile 정보를 받아와야 한다.") {
val code = authorizationCode
val mockProfile = KakaoProfile(
socialId = socialId,
nickname = nickname,
email = email,
)

every { socialPort.getKakaoUserInfoFromCode(code) } returns mockProfile

val result = authService.getKakaoUserInfoFromCode(code)
result shouldBe mockProfile

verify(exactly = 1) {
socialPort.getKakaoUserInfoFromCode(code)
}
confirmVerified(socialPort)
}
}
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package site.yourevents.auth.service

import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe
import io.mockk.confirmVerified
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import site.yourevents.auth.port.out.SecurityPort
import java.util.UUID

class TokenServiceTest : DescribeSpec({
lateinit var securityPort: SecurityPort
lateinit var tokenService: TokenService
lateinit var id: UUID
lateinit var socialId: String
lateinit var role: String
lateinit var accessToken: String

beforeTest {
securityPort = mockk<SecurityPort>()
tokenService = TokenService(securityPort)

id = UUID.randomUUID()
socialId = "12345678"
role = "ROLE_USER"
accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
}

describe("TokenService") {
context("올바른 인자가 주어졌을 때") {
it("SecurityPort를 통해 토큰을 String 형식으로 반환받아야 한다.") {
every { securityPort.generateAccessToken(id, socialId, role) } returns accessToken

val result = tokenService.generateAccessToken(
id,
socialId,
role,
)
result shouldBe accessToken

verify(exactly = 1) {
securityPort.generateAccessToken(
match { it == id },
match { it == socialId },
match { it == role },
)
}
confirmVerified(securityPort)
}
}
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package site.yourevents.member.domain

import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe
import java.util.UUID

class MemberTest : DescribeSpec({
describe("Member 도메인") {
context("Member가 생성될 때") {
it("get 메서드를 통해 올바른 필드 값을 반환해야 한다.") {
val id = UUID.randomUUID()
val socialId = "12345678"
val nickname = "yes"
val email = "[email protected]"

val member = Member(
id = id,
socialId = socialId,
nickname = nickname,
email = email
)

member.apply {
getId() shouldBe id
getSocialId() shouldBe socialId
getNickname() shouldBe nickname
getEmail() shouldBe email
}
}
}
}
})
Loading