반응형

마지막으로 카카오 로그인이다.

 

저번에도 사용한 

 

OauthService interface

interface OauthService {

    fun getOauth(code: String, isRemote: Boolean): Oauth
    fun getAccessToken(code: String, isRemote: Boolean): String
}

 

Oauth data class

@Entity
@Table(
    indexes = [
        Index(name = "idx_type_id", columnList = "oauthType, oauthId")
    ]
)
data class Oauth(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Int?,
    @ManyToOne
    var user: User?,
    @Column(length = 100)
    var oauthId: String,
    @Enumerated(EnumType.ORDINAL)
    var oauthType: OauthTypeEnum
)

 

그대로 사용한다.

 

우선 마찬가지로 카카오 애플리케이션 등록을 해야한다.

 

그 후에는 또 마찬가지로 redirect_uri를 지정해야 한다.

 

 

카카오 로그인을 ON으로 해주고

 

아래에 있는

 

본인이 사용할 redirect_uri를 지정해준다.

 

또한 앱 키 페이지에 있는 Rest API키를 가져와서 스프링부트의 환경변수에 넣어둔다.

 

  • AccessToken 받아오기

바로 AccessToken을 받아오도록 해보자.

 

마찬가지로 사용자가 넘겨주는 code를 post로 요청해 토큰을 가져오는 것이다.

 

    private final val kakaoAccessTokenServer = "https://kauth.kakao.com/oauth/token"
    
    override fun getAccessToken(code: String, isRemote: Boolean): String {

        val restTemplate = RestTemplate()

        val httpHeaders = HttpHeaders()

        httpHeaders.add("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")

        val httpBody = LinkedMultiValueMap<String, String>()

        httpBody.add("redirect_uri", if (isRemote) redirectUri else localRedirectUri)
        httpBody.add("client_id", clientId)
        httpBody.add("code",code)

        return restTemplate.postForObject(kakaoAccessTokenServer, this.getHttpEntity(httpBody), KakaoAccessToken::class.java)?.accessToken ?: ""
    }
    
    private data class KakaoAccessToken(
        @JsonProperty("access_token")
        val accessToken: String
    )
    
    fun getHttpEntity(httpBody: LinkedMultiValueMap<String, String>): HttpEntity<LinkedMultiValueMap<String, String>> {
        val httpHeaders = HttpHeaders()
        httpHeaders.add("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")

        httpBody.add("grant_type", "authorization_code")

        return HttpEntity(httpBody, httpHeaders)
    }

 

이렇게 해당 주소로 redirect_uri, client_id(Rest API키), code, grant_type을 넣어 post 요청하면 Body로 access_token이 하나 나오게 된다.

 

 

  • 사용자 정보 가져오기

이제 사용자의 정보를 가져오자.

 

마찬가지로 응답받은 AccessToken을 그대로 넣어 get 요청하면 된다.

 

    private final val kakaoUserInfoServer =  "https://kapi.kakao.com/v2/user/me"

    override fun getOauth(code: String, isRemote: Boolean): Oauth {

        val accessToken = getAccessToken(code, isRemote)

        val restTemplate = RestTemplate()

        val kakaoUserInfo = restTemplate.exchange(
            RequestEntity<Any>(this.getHttpHeadersWithAuthorization(accessToken), HttpMethod.POST, URI.create(kakaoUserInfoServer)),
            KakaoUserInfo::class.java
        ).body

        val kakaoUserId = kakaoUserInfo?.id ?: ""

        return this.getOauthFromOauthIdAndOauthType(
            oauthId = kakaoUserId,
            oauthTypeEnum = OauthTypeEnum.KAKAO)
    }
    
    private data class KakaoUserInfo(
        @JsonProperty("id") val id: String,
        @JsonProperty("connected_at") val connectedAt: String,
        @JsonProperty("properties") val properties: Properties,
        @JsonProperty("kakao_account") val kakaoAccount: KakaoAccount
    )

    private data class Properties(
        @JsonProperty("nickname") val nickname: String
    )

    private data class KakaoAccount(
        @JsonProperty("profile_nickname_needs_agreement") val profileNicknameNeedsAgreement: Boolean,
        @JsonProperty("profile") val profile: Profile
    )

    private data class Profile(
        @JsonProperty("nickname") val nickname: String,
        @JsonProperty("is_default_nickname") val isDefaultNickname: Boolean,
        @JsonProperty("profile_image_url") val profileImageUrl:String?
    )
    
    fun getHttpHeadersWithAuthorization(accessToken: String): HttpHeaders{
        val httpHeaders = HttpHeaders()
        httpHeaders.add("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
        httpHeaders.add("Authorization", "Bearer $accessToken")

        return httpHeaders
    }

 

저번처럼 요청을 하며, 응답받은 json 형식도 위와 같다.

이 중에서는 카카오의 동의항목에 따라 오지 않는 데이터가 있을 수도 있다.

 

그리고 여기서도 id를 통해 로그인한 사용자를 고유번호를 통하여 식별할 수 있다.

 

아래는 사용한 전체의 코드이다.

 

@Service
class KakaoOauthServiceImpl(
    @Value("\${kakao.auth.client_id}")
    private val clientId: String,
    @Value("\${kakao.auth.redirect_uri}")
    private val redirectUri: String,
    @Value("\${kakao.auth.local_redirect_uri}")
    private val localRedirectUri: String,
    private val oauthRepository: OauthRepository
) : OauthService, OauthServiceImpl(oauthRepository) {

    private final val kakaoAccessTokenServer = "https://kauth.kakao.com/oauth/token"
    private final val kakaoUserInfoServer =  "https://kapi.kakao.com/v2/user/me"

    override fun getOauth(code: String, isRemote: Boolean): Oauth {

        val accessToken = getAccessToken(code, isRemote)

        val restTemplate = RestTemplate()

        val kakaoUserInfo = restTemplate.exchange(
            RequestEntity<Any>(this.getHttpHeadersWithAuthorization(accessToken), HttpMethod.POST, URI.create(kakaoUserInfoServer)),
            KakaoUserInfo::class.java
        ).body

        val kakaoUserId = kakaoUserInfo?.id ?: ""

        return this.getOauthFromOauthIdAndOauthType(
            oauthId = kakaoUserId,
            oauthTypeEnum = OauthTypeEnum.KAKAO)
    }

    override fun getAccessToken(code: String, isRemote: Boolean): String {

        val restTemplate = RestTemplate()

        val httpHeaders = HttpHeaders()

        httpHeaders.add("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")

        val httpBody = LinkedMultiValueMap<String, String>()

        httpBody.add("redirect_uri", if (isRemote) redirectUri else localRedirectUri)
        httpBody.add("client_id", clientId)
        httpBody.add("code",code)

        return restTemplate.postForObject(kakaoAccessTokenServer, this.getHttpEntity(httpBody), KakaoAccessToken::class.java)?.accessToken ?: ""
    }

    private data class KakaoAccessToken(
        @JsonProperty("access_token")
        val accessToken: String
    )

    private data class KakaoUserInfo(
        @JsonProperty("id") val id: String,
        @JsonProperty("connected_at") val connectedAt: String,
        @JsonProperty("properties") val properties: Properties,
        @JsonProperty("kakao_account") val kakaoAccount: KakaoAccount
    )

    private data class Properties(
        @JsonProperty("nickname") val nickname: String
    )

    private data class KakaoAccount(
        @JsonProperty("profile_nickname_needs_agreement") val profileNicknameNeedsAgreement: Boolean,
        @JsonProperty("profile") val profile: Profile
    )

    private data class Profile(
        @JsonProperty("nickname") val nickname: String,
        @JsonProperty("is_default_nickname") val isDefaultNickname: Boolean,
        @JsonProperty("profile_image_url") val profileImageUrl:String?
    )
}

 

open class OauthServiceImpl(
    private val oauthRepository: OauthRepository
) {

    fun getHttpEntity(httpBody: LinkedMultiValueMap<String, String>): HttpEntity<LinkedMultiValueMap<String, String>> {
        val httpHeaders = HttpHeaders()
        httpHeaders.add("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")

        httpBody.add("grant_type", "authorization_code")

        return HttpEntity(httpBody, httpHeaders)
    }

    fun getHttpHeadersWithAuthorization(accessToken: String): HttpHeaders{
        val httpHeaders = HttpHeaders()
        httpHeaders.add("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
        httpHeaders.add("Authorization", "Bearer $accessToken")

        return httpHeaders
    }

    fun getOauthFromOauthIdAndOauthType(oauthId: String, oauthTypeEnum: OauthTypeEnum): Oauth {
        val optionalOauth = oauthRepository.findByOauthIdAndOauthType(oauthId, oauthTypeEnum)

        if (optionalOauth.isPresent) {
            CompletableFuture.supplyAsync{
                optionalOauth.get()
            }.thenApply {
                oauthRepository.save(it)
            }
            return optionalOauth.get()
        }
        else{
            return oauthRepository.save(Oauth(
                id = null,
                user = null,
                oauthId = oauthId,
                oauthType = oauthTypeEnum
            ))
        }
    }
}

 

이렇게 네이버, 구글, 카카오의 3가지 소셜로그인에 대하여 알아보았다.

 

다음에는 어떤 내용으로 글을 작성할지 고민해보도록 하겠다.

+ Recent posts