Skip to content

Issue:(#9, #10) cors 설정 변경 및 온보딩 구현 #11

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

Merged
merged 9 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ dependencies {
runtimeOnly("mysql:mysql-connector-java:8.0.33")

// swagger
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.1")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0")
Copy link
Contributor

Choose a reason for hiding this comment

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

버전을 내린 이유가 궁금해!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

버전을 내린 이유가 궁금해!

아 이건 Swagger 응답이 좀 이상하게 나와서 버전 문제인 줄 알고 내렸거든.
근데 버전 문제는 아니었어
이건 내가 내렸다 안 올렸네
스웨거 2.8.1 이나 2.7.0 은 사용하는데 별 차이는 없으니까 그냥 가도 될 것 같아


// mail
implementation("org.springframework.boot:spring-boot-starter-mail")
Expand Down
5 changes: 3 additions & 2 deletions src/main/kotlin/gomushin/backend/core/CustomUserDetails.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ package gomushin.backend.core

import gomushin.backend.member.domain.entity.Member
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails

class CustomUserDetails(
private val member: Member,
) : UserDetails {
override fun getAuthorities(): MutableCollection<out GrantedAuthority> {
return mutableListOf()
return mutableListOf(SimpleGrantedAuthority("ROLE_${member.role.name}"))
}

override fun getPassword(): String {
return ""
}

override fun getUsername(): String {
return member.name
return member.nickname
}

override fun isAccountNonExpired(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonProperty
import gomushin.backend.core.common.web.response.exception.ApiError
import io.swagger.v3.oas.annotations.media.Schema
import java.util.concurrent.CompletableFuture
import java.util.function.Function
import java.util.function.Supplier
Expand All @@ -20,6 +21,7 @@ data class ApiResponse<T>(
val result: T? = null,

@get:JsonProperty("error")
@Schema(hidden = true)
val error: ApiError? = null,
) {
init {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ class CustomCorsConfiguration {
@Bean
fun corsConfigurationSource(): CorsConfigurationSource {
val configuration = CorsConfiguration()
configuration.allowedOrigins = listOf("http://localhost:5173", "http://localhost:8080")
configuration.allowedOrigins =
listOf("http://localhost:5173", "http://localhost:8080", "https://frontend-sarang.vercel.app")
Copy link
Contributor

Choose a reason for hiding this comment

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

프론트에서 로컬로 개발할 때 5173포트쓰나? 보통 3000번 포트 쓰지 않아?

Copy link
Collaborator Author

@HoyeongJeon HoyeongJeon Apr 15, 2025

Choose a reason for hiding this comment

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

프론트에서 로컬로 개발할 때 5173포트쓰나? 보통 3000번 포트 쓰지 않아?

프론트 포트 5173이래

configuration.allowedMethods = listOf("GET", "POST", "PUT", "DELETE", "OPTIONS")
configuration.allowedHeaders = listOf("*")
configuration.allowCredentials = true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package gomushin.backend.core.configuration.security

import gomushin.backend.core.oauth.handler.CustomSuccessHandler
import gomushin.backend.core.infrastructure.filter.JwtAuthenticationFilter
import gomushin.backend.core.jwt.JwtTokenProvider
import gomushin.backend.core.oauth.handler.CustomSuccessHandler
import gomushin.backend.core.oauth.service.CustomOAuth2UserService
import gomushin.backend.core.service.CustomUserDetailsService
import gomushin.backend.member.domain.repository.MemberRepository
Expand All @@ -18,7 +18,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
@EnableWebSecurity
class SecurityConfiguration(
private val jwtTokenProvider: JwtTokenProvider,
private val memberRepository: MemberRepository
private val memberRepository: MemberRepository,
) {

@Bean
Expand Down Expand Up @@ -67,6 +67,8 @@ class SecurityConfiguration(
"/favicon.ico",
"/error"
).permitAll()
it.requestMatchers("/v1/member/onboarding").hasRole("GUEST")
it.anyRequest().authenticated()
}
.addFilterBefore(
JwtAuthenticationFilter(
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ class JwtAuthenticationFilter(
) : OncePerRequestFilter() {

override fun shouldNotFilter(request: HttpServletRequest): Boolean {
return request.requestURI.contains("/v1/auth") || request.requestURI.contains("/v1/oauth")
val excludedPaths = listOf(
"/v1/auth", "/v1/oauth", "/swagger", "/v3/api-docs", "/api-docs"
)
return excludedPaths.any { path ->
request.requestURI.startsWith(path) || request.requestURI == path
}
}

override fun doFilterInternal(
Expand All @@ -40,6 +45,7 @@ class JwtAuthenticationFilter(
private fun applyAuthentication(token: String) {
val userId = tokenProvider.getMemberIdFromToken(token)
val userDetails = customUserDetailsService.loadUserById(userId)

val auth = UsernamePasswordAuthenticationToken(
userDetails,
null,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package gomushin.backend.core.jwt

interface JwtTokenProvider {
fun provideAccessToken(userId: Long): String
fun provideAccessToken(userId: Long, role: String): String
fun getMemberIdFromToken(token: String): Long
fun validateToken(token: String): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ class JwtTokenProviderImpl(
val ACCESS_TOKEN_EXPIRATION = jwtProperties.accessTokenExpiration
val REFRESH_TOKEN_EXPIRATION = jwtProperties.refreshTokenExpiration

override fun provideAccessToken(userId: Long): String {
return createToken(userId, ACCESS_TOKEN_EXPIRATION, Type.ACCESS)
override fun provideAccessToken(userId: Long, role: String): String {
return createToken(userId, role, ACCESS_TOKEN_EXPIRATION, Type.ACCESS)
}

override fun getMemberIdFromToken(token: String): Long {
Expand All @@ -41,12 +41,13 @@ class JwtTokenProviderImpl(
is UnsupportedJwtException,
is MalformedJwtException,
is IllegalArgumentException -> return false

else -> throw e
}
}
}

private fun createToken(userId: Long, expiration: Long, type: Type): String {
private fun createToken(userId: Long, role: String, expiration: Long, type: Type): String {
val expirationMs = expiration * 60 * 1000
val expiryDate = Date(System.currentTimeMillis() + expirationMs)

Expand All @@ -55,6 +56,7 @@ class JwtTokenProviderImpl(
.audience().add(AUDIENCE).and()
.subject(userId.toString())
.claim("type", type.name)
.claim("role", role)
.issuedAt(Date())
.expiration(expiryDate)
.signWith(SECRET_KEY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ class CustomOAuth2User(
fun getUserId(): Long {
return userDto.userId
}

fun getRole() : String {
return userDto.role
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ class CustomSuccessHandler(

var accessToken = ""
getMemberByEmail(principal.getEmail())?.let {
accessToken = jwtTokenProvider.provideAccessToken(it.id)
accessToken = jwtTokenProvider.provideAccessToken(it.id, it.role.name)
} ?: run {
accessToken = jwtTokenProvider.provideAccessToken(principal.getUserId())
accessToken = jwtTokenProvider.provideAccessToken(principal.getUserId(), principal.getRole())
}

response!!.addCookie(createCookie("access_token", accessToken))
response.sendRedirect("http://localhost:8080") // TODO: 프론트엔드 주소로 변경 , 환경변수 처리
response.sendRedirect("https://frontend-sarang.vercel.app")
Copy link
Contributor

Choose a reason for hiding this comment

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

처음에 프론트는 로컬환경에서 개발할건데 이렇게 하면 프론트입자에서 로컬에 테스트 할때 토큰을 못 받지 않나? 바로 배포주소로 리디렉션되니깐?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

처음에 프론트는 로컬환경에서 개발할건데 이렇게 하면 프론트입자에서 로컬에 테스트 할때 토큰을 못 받지 않나? 바로 배포주소로 리디렉션되니깐?

아 맞네 이것도 프론트 주소로 바꿀게
근데 우리 배포가 이미 둘 다 https 면 프론트에서 로컬로 쿠키를 못 받는데…
이건 고민 해봐야겠다

}

private fun createCookie(key: String, value: String): Cookie {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import gomushin.backend.core.oauth.CustomOAuth2User
import gomushin.backend.member.domain.entity.Member
import gomushin.backend.member.domain.repository.MemberRepository
import gomushin.backend.member.domain.value.Provider
import gomushin.backend.member.domain.value.Role
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest
import org.springframework.security.oauth2.core.user.OAuth2User
Expand All @@ -32,7 +33,7 @@ class CustomOAuth2UserService(

getMemberByEmail(email)?.let {
it.email = oAuth2Response.getEmail()
it.name = oAuth2Response.getName()
it.nickname = oAuth2Response.getName()
it.profileImageUrl = oAuth2Response.getProfileImage()

val savedMember = memberRepository.save(it)
Expand All @@ -42,18 +43,18 @@ class CustomOAuth2UserService(
name = oAuth2Response.getName(),
email = oAuth2Response.getEmail(),
profileImage = oAuth2Response.getProfileImage(),
role = "ROLE_USER",
role = Role.MEMBER.name,
registrationId = registrationId,
userId = savedMember.id,
)

return CustomOAuth2User(userDto)
} ?: run {
val newMember = Member.create(
name = oAuth2Response.getName(),
nickname = oAuth2Response.getName(),
email = oAuth2Response.getEmail(),
profileImageUrl = oAuth2Response.getProfileImage(),
provider = Provider.getProviderByValue(registrationId)
provider = Provider.getProviderByValue(registrationId),
)

val savedMember = memberRepository.save(newMember)
Expand All @@ -63,7 +64,7 @@ class CustomOAuth2UserService(
name = oAuth2Response.getName(),
email = oAuth2Response.getEmail(),
profileImage = oAuth2Response.getProfileImage(),
role = "ROLE_USER",
role = Role.MEMBER.name,
registrationId = registrationId,
userId = savedMember.id,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,21 @@ package gomushin.backend.core.service

import gomushin.backend.core.CustomUserDetails
import gomushin.backend.core.infrastructure.exception.BadRequestException
import gomushin.backend.member.domain.entity.Member
import gomushin.backend.member.domain.repository.MemberRepository
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.stereotype.Service

@Service
class CustomUserDetailsService(private val memberRepository: MemberRepository) : UserDetailsService {
override fun loadUserByUsername(username: String?): UserDetails {
if (username == null) {
throw BadRequestException("saranggun.member.not-provided-name")
}

return memberRepository.findByName(username!!)?.let { createUserDetail(it) }
override fun loadUserByUsername(email: String): UserDetails { // 파라미터명 username → email로 변경
val member = memberRepository.findByEmail(email)
?: throw BadRequestException("sarangggun.member.not-exist-member")
return CustomUserDetails(member)
}

fun loadUserById(id: Long): UserDetails =
memberRepository.findById(id).map { createUserDetail(it) }
memberRepository.findById(id)
.map { CustomUserDetails(it) }
.orElseThrow { BadRequestException("sarangggun.member.not-exist-member") }

private fun createUserDetail(member: Member): UserDetails {
return CustomUserDetails(member)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package gomushin.backend.member.application

import gomushin.backend.core.CustomUserDetails
import gomushin.backend.member.domain.service.MemberInfoService
import gomushin.backend.member.presentation.dto.response.GuestInfoResponse
import org.springframework.stereotype.Component

@Component
class MemberInfoFacade(
private val memberInfoService: MemberInfoService,
) {
fun getMemberInfo(customUserDetails: CustomUserDetails): Any {
val authorities = customUserDetails.authorities

// if (authorities.any { it.authority == "ROLE_GUEST" }) {
val member = memberInfoService.getGuestInfo(customUserDetails.getId())
return GuestInfoResponse.of(member)
// }

// TODO: Member 구현 시 , 분기를 통해 MEMBER와 GUEST를 구분할 수 있도록 수정
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package gomushin.backend.member.application

import gomushin.backend.member.domain.service.OnboardingService
import gomushin.backend.member.presentation.dto.request.OnboardingRequest
import org.springframework.stereotype.Component

@Component
class OnboardingFacade(
private val onboardingService: OnboardingService,
) {

fun onboarding(id: Long, onboardingRequest: OnboardingRequest) = onboardingService.onboarding(id, onboardingRequest)
}
22 changes: 17 additions & 5 deletions src/main/kotlin/gomushin/backend/member/domain/entity/Member.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package gomushin.backend.member.domain.entity

import gomushin.backend.core.infrastructure.jpa.shared.BaseEntity
import gomushin.backend.member.domain.value.Provider
import gomushin.backend.member.domain.value.Role
import jakarta.persistence.*
import java.time.LocalDate

@Entity
@Table(name = "member")
Expand All @@ -11,25 +13,35 @@ class Member(
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,

var name: String,
@Column(name = "nickname", nullable = false)
var nickname: String,

@Column(unique = true)
@Column(name = "email", unique = true, nullable = false)
var email: String,

@Column(name = "birth_date")
var birthDate: LocalDate? = null,

@Column(name = "profile_image_url")
var profileImageUrl: String?,

@Enumerated(EnumType.STRING)
@Column(name = "provider", nullable = false)
val provider: Provider,
): BaseEntity() {

@Enumerated(EnumType.STRING)
@Column(name = "role", nullable = false)
var role: Role = Role.GUEST,
) : BaseEntity() {
companion object {
fun create(
name: String,
nickname: String,
email: String,
profileImageUrl: String?,
provider: Provider
): Member {
return Member(
name = name,
nickname = nickname,
email = email,
profileImageUrl = profileImageUrl ?: "",
provider = provider,
Expand Down
Loading