본문 바로가기

카테고리 없음

유저 만들기

@Entity
class User(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    var id: Long? = null,

    @Column(nullable = false, unique = true)
    val email: String,

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    val provider: OAuth2Provider,

    @Column(nullable = false, unique = true)
    val providerId: String,

    @Column(nullable = false)
    val nickname: String,
) {
    companion object {
        fun ofKakao(
            email: String,
            providerId: String,
            nickname: String,
        ): User {
            return User(
                email = email,
                provider = OAuth2Provider.KAKAO,
                providerId = providerId,
                nickname = nickname,
            )
        }
    }
}

enum class OAuth2Provider {
    KAKAO,
}

여기에서 companion object 내의 ofKakao 함수는 카카오에서 받아온 이메일, 프로바이더 아이디, 닉네임을 이용하여 새로운 User를 만드는 역할를 한다. 그리고 만들어진걸 return이 반환하는것이다.

 

interface UserService {
    fun findOrCreateUser(email: String, providerId: String, nickname: String): User
    fun existUser(provider: OAuth2Provider, providerId: String): Boolean
    fun findUser(provider: OAuth2Provider, providerId: String): User
}

findOrCreateUser(email: String, providerId: String, nickname: String): User
이메일, 제공자 ID, 닉네임을 기반으로 사용자를 찾습니다. 만약 해당 정보를 가진 사용자가 존재하지 않는 경우, 새로운 사용자를 생성하고 반환합니다.


existUser(provider: OAuth2Provider, providerId: String): Boolean
특정 OAuth2 제공자와 제공자 ID를 기반으로 사용자의 존재 여부를 확인합니다. 사용자가 존재하면 true를, 존재하지 않으면 false를 반환합니다.


findUser(provider: OAuth2Provider, providerId: String): User
특정 OAuth2 제공자와 제공자 ID로 사용자를 찾아 반환합니다. 이 메소드는 사용자가 반드시 존재한다고 가정하며, 사용자를 찾지 못하는 경우 예외를 발생시킬 수 있습니다.

 

@Service
class UserServiceImpl(
    private val userRepository: UserRepository
) : UserService {

    @Transactional
    override fun findOrCreateUser(email: String, providerId: String, nickname: String): User {
        val user = userRepository.findByProviderAndProviderId(OAuth2Provider.KAKAO, providerId)
        return user ?: userRepository.save(User.ofKakao(email, providerId, nickname))
    }
    
    주어진 email, providerId, nickname을 사용하여 사용자를 찾거나, 없다면 새로 생성하는 메서드입니다.
먼저 userRepository.findByProviderAndProviderId 메서드를 사용하여 사용자를 찾습니다.
사용자가 존재하지 않으면 User.ofKakao 메서드를 통해 새 사용자 객체를 생성하고, 
userRepository.save 메서드를 통해 저장합니다.

    @Transactional
    override fun existUser(provider: OAuth2Provider, providerId: String): Boolean {
        return userRepository.findByProviderAndProviderId(provider, providerId) != null
    }
   특정 provider와 providerId를 기준으로 사용자의 존재 여부를 확인하는 메서드입니다.
userRepository.findByProviderAndProviderId 메서드를 사용하여 사용자를 찾고, 결과가 null이 아니면
true, null이면 false를 반환합니다.

    @Transactional
    override fun findUser(provider: OAuth2Provider, providerId: String): User {
        return userRepository.findByProviderAndProviderId(provider, providerId)
            ?: throw IllegalArgumentException("User not found with providerId: $providerId")
    }
특정 provider와 providerId를 기준으로 사용자를 찾는 메서드입니다.
userRepository.findByProviderAndProviderId 메서드를 사용하여 사용자를 찾고, 사용자가 존재하지 
않으면 IllegalArgumentException 예외를 던집니다.
}

existUser 메소드는 특정 provider와 providerId를 가진 사용자가 존재하는지 여부만을 확인하는데,

 findUser 메소드는 사용자의 존재 여부뿐만 아니라, 해당 사용자의 정보도 확인하기에 둘은 다르다

 

서비스 임플의 오버라이드에 빨간줄은 서비스에 해당이 없는거

 

 

 

 

@Component
class JwtHelper(
    private val jwtPlugin: JwtPlugin,
) {
    fun generateAccessToken(
        subject: String,
        nickname: String,
        email: String,
        httpServletResponse: HttpServletResponse,
    ): String {
        return jwtPlugin.generateAccessTokenForSocialUser(subject, nickname, email, httpServletResponse)
    }
}

fun generateAccessToken(subject: String, nickname: String, email: String, httpServletResponse: HttpServletResponse): String
이 메서드는 액세스 토큰을 생성하는 역할을 합니다.

subject: 토큰의 주체(subject)로, 일반적으로 사용자 ID나 사용자명을 의미합니다.
nickname: 사용자의 닉네임.
email: 사용자의 이메일 주소.
httpServletResponse: HTTP 응답 객체로, 토큰을 HTTP 응답 헤더에 설정할 때 사용됩니다.


반환값: 생성된 JWT 액세스 토큰 문자열을 반환합니다.
내부적으로 jwtPlugin 객체의 generateAccessTokenForSocialUser 메서드를 호출하여 실제 토큰을 생성합니다.

 

서비스에 

fun registerIfAbsent(userInfo: OAuth2UserInfo): User

 

서비스임플에 

@Transactional
override fun registerIfAbsent(userInfo: OAuth2UserInfo): User {
    val provider = OAuth2Provider.valueOf(userInfo.provider)
    return if (!existUser(provider, userInfo.providerId)) {
        val newUser = User.ofKakao(userInfo.email, userInfo.providerId, userInfo.nickname)
        userRepository.save(newUser)
    } else {
        findUser(provider, userInfo.providerId)
    }
}

 

을 추가했다. 이것의 역할은

OAuth2Provider.valueOf(userInfo.provider)를 사용하여 문자열로 된 제공자 정보를 OAuth2Provider 열거형으로 변환합니다.
existUser 메소드를 호출하여 해당 제공자와 제공자 ID를 가진 사용자가 이미 존재하는지 확인합니다.
만약 사용자가 존재하지 않는다면, 새로운 User 인스턴스를 생성합니다. 여기서는 User.ofKakao라는 정적 팩토리 메소드를 사용하여 userInfo에서 제공된 이메일, 제공자 ID, 닉네임을 바탕으로 새 사용자 객체를 생성합니다.
생성된 새 사용자 객체는 userRepository.save(newUser)를 통해 데이터베이스에 저장됩니다.
만약 해당 제공자와 제공자 ID를 가진 사용자가 이미 존재한다면, findUser 메소드를 사용하여 해당 사용자를 찾아 반환합니다.
결과
이 메소드는 사용자가 이미 존재하지 않는 경우 새로운 사용자를 생성하고 데이터베이스에 저장한 후 그 사용자를 반환합니다.
만약 사용자가 이미 존재하는 경우, 해당 사용자를 찾아 반환합니다.

 

@Service
class OAuth2UserService(
    private val UserService: UserService,
) : DefaultOAuth2UserService() {
    override fun loadUser(userRequest: OAuth2UserRequest): OAuth2User {
        val originUser = super.loadUser(userRequest)
        val provider = userRequest.clientRegistration.clientName // "KAKAO"
        return OAuth2UserInfo.of(provider, userRequest, originUser)
            .also { UserService.registerIfAbsent(it) }
    }
}

 

 

loadUser 메소드
DefaultOAuth2UserService의 loadUser 메소드를 오버라이드(재정의)합니다.
userRequest 파라미터를 받아 OAuth2 사용자 정보를 로드합니다.

 

세부 로직
originUser = super.loadUser(userRequest)

부모 클래스(DefaultOAuth2UserService)의 loadUser 메소드를 호출하여 원본 사용자 정보를 로드합니다.
이 메소드는 OAuth2 공급자(예: 카카오)로부터 사용자 정보를 가져옵니다.
provider = userRequest.clientRegistration.clientName

클라이언트 등록 정보를 통해 제공자 이름(예: "KAKAO")을 가져옵니다.
OAuth2UserInfo.of(provider, userRequest, originUser)

OAuth2UserInfo 객체를 생성합니다. 이 객체는 제공자, 사용자 요청, 원본 사용자 정보를 포함합니다.
.also { UserService.registerIfAbsent(it) }

생성된 OAuth2UserInfo 객체를 통해 사용자가 존재하지 않는 경우 새로운 사용자를 등록하거나, 기존 사용자를 조회합니다.
UserService의 registerIfAbsent 메소드를 호출하여 이 작업을 수행합니다.

 

결과
이 메소드는 소셜 로그인 과정에서 OAuth2 사용자 정보를 로드하고, 해당 정보를 바탕으로 사용자를 등록하거나 조회하여 반환합니다.
새로운 사용자가 등록되거나 기존 사용자가 조회된 후, 최종 OAuth2UserInfo 객체가 반환됩니다.

 

요약
OAuth2UserService 클래스는 OAuth2 인증 과정에서 사용자 정보를 관리하는 서비스입니다.
loadUser 메소드는 OAuth2 사용자 정보를 로드하고, 필요 시 새로운 사용자를 등록하거나 기존 사용자를 조회합니다.
이 과정을 통해 소셜 로그인 기능을 구현할 수 있으며, 사용자 정보가 자동으로 관리됩니다.

 

@Component
class OAuth2LoginSuccessHandler(
    private val jwtHelper: JwtHelper,
    private val UserService: UserService,
) : AuthenticationSuccessHandler {
    override fun onAuthenticationSuccess(
        request: HttpServletRequest,
        httpServletResponse: HttpServletResponse,
        authentication: Authentication,
    ) {
        val userInfo = authentication.principal as OAuth2UserInfo

        // 기존에 있던 회원인지 조회
        val existedUser = UserService.existUser(OAuth2Provider.valueOf(userInfo.provider), userInfo.providerId)

        // 있으면 검색한 정보로 토큰 발급하고
        if (existedUser) {

            val user = UserService.findUser(
                OAuth2Provider.valueOf(userInfo.provider),
                userInfo.providerId,
                userInfo.email
            )
            val accessToken = jwtHelper.generateAccessToken(
                user.providerId, user.nickname, user.email, httpServletResponse
            )
            httpServletResponse.addHeader("Authorization", "Bearer $accessToken") // 헤더에 담는 걸로 바꿈
            httpServletResponse.contentType = MediaType.APPLICATION_JSON_VALUE
            httpServletResponse.sendRedirect("/") // 로그인 후 홈으로 안 가게 하려면 이거 끄면 돼요


            // 없으면 가져온 정보 응답함
        } else {
            httpServletResponse.contentType = MediaType.APPLICATION_JSON_VALUE
            httpServletResponse.writer.write(
                jacksonObjectMapper().writeValueAsString(
                    SocialLoginResponse.of(
                        userInfo.email,
                        userInfo.provider,
                        userInfo.providerId,
                        userInfo.nickname,
                    ),
                ),
            )
        }
    }
}

 

이걸 만들었는데

e: file:///C:/Users/asdf/IdeaProjects/Board/src/main/kotlin/domain/user/service/OAuth2LoginSuccessHandler.kt:36:17 Too many arguments for public abstract fun findUser(provider: OAuth2Provider, providerId: String): User defined in org.example.domain.user.service.UserService

 

이걸 해결하려면

UserService의 findUser 함수를 수정하여 세 번째 인자로 email을 받을 수 있도록 합니다. 이 방법은 UserService와 그 구현체에 접근할 수 있어야 가능합니다.

OAuth2LoginSuccessHandler에서 findUser 함수를 호출할 때 email 인자를 제거합니다. 이 방법은 현재 findUser 함수의 정의를 변경하지 않고도 적용할 수 있습니다.

 

이라고 한다 그래서

fun findUser(provider: OAuth2Provider, providerId: String, email: String): User

 

이렇게  findUser 에 이메일을 넣었다.