반응형

저번 네이버 로그인에 이어 이번에는 구글 로그인이다.

 

이번에도 저번과 마찬가지로 토큰 받아오기, 사용자 정보 받아오기를 구현하며 인터페이스도 저번에 사용했던

 

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
)

 

해당 코드들을 그대로 사용한다.

 

그러면 이번에는 구글 Oauth를 통해서 oauthId를 받아오도록 해보자.

 

우선 구글 애플리케이션을 등록해야 한다.

GCP에서 애플리케이션을 등록하고, API 인증 및 서비스 항목의 사용자 인증정보 페이지에서 리디렉션 URI를 다음과 같이 등록해주자.

 

http://localhost:8080/api/auth/google-login

 

 

 

 

그 다음에는 구글에 가져올 정보와 테스트 계정을 등록해줘야 한다.

 

앱 등록 수정에서

가져오는 정보를 다음과 같이 email, profile, openid를 등록해준다.

그리고 다음페이지에서 테스트 계정도 등록해둔다.

 

이러면 애플리케이션 등록에서는 할 일이 끝났다.

 

  • AccessToken 받아오기

저번과 마찬가지로 우선 애플리케이션에서 해당 사용자에 대한 AccessToken을 받아오는 것부터 시작이다.

저번과 비슷하다.

POST 방식으로 구글 인증 주소에 요청해서 토큰을 받아오는 것이다.

 

    private final val googleAccessTokenServer = "https://oauth2.googleapis.com/token"

	override fun getAccessToken(code: String, isRemote: Boolean): String {
        val restTemplate = RestTemplate()

        val httpBody = LinkedMultiValueMap<String, String>()

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

        val result =  restTemplate.postForObject(googleAccessTokenServer, this.getHttpEntity(httpBody), GoogleAccessToken::class.java)?.accessToken ?: ""

        return result
    }
    
    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)
    }
    
    private data class GoogleAccessToken(
        @JsonProperty("access_token") val accessToken: String,
        @JsonProperty("expires_in") val expiresIn: Int,
        @JsonProperty("token_type") val tokenType: String,
        @JsonProperty("scope") val scope: String,
        @JsonProperty("id_token") val idToken: String,
    )

 

넣는 정보는 다음과 같다.

구글 애플리케이션에서 가져오는 client_id, client_secret을 가져오고, 본인이 등록한 redirect_uri를 넣는다.

그리고 해당 로그인으로부터 가져오는 code와 가져오는 type을 authorization으로 넣어 post요청하면 해당 유저의 AccessToken을 발급해준다.

 

client_id와 client_secret, redirect_uri는 환경변수에 넣어두고 가져오도록 만들면 된다.

 

  • 사용자 정보 가져오기

이제 사용자의 고유 id를 가져오자.

 

응답받은 AccessToken을 그대로 넣어 요청하면 끝난다.

    private final val googleUserInfoServer = "https://www.googleapis.com/oauth2/v2/userinfo"
    
	override fun getOauth(code: String, isRemote: Boolean): Oauth {

        val accessToken = getAccessToken(code, isRemote)

        val restTemplate = RestTemplate()

        val googleUserInfo = restTemplate.exchange(
            RequestEntity<Any>(this.getHttpHeadersWithAuthorization(accessToken), HttpMethod.GET, URI.create(googleUserInfoServer)),
            GoogleUserInfo::class.java
        ).body

        val googleUserId = googleUserInfo?.id ?: ""

        return this.getOauthFromOauthIdAndOauthType(
            oauthId = googleUserId,
            oauthTypeEnum = OauthTypeEnum.GOOGLE,
            accessToken = accessToken)
    }
    
    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
    }
    
    private data class GoogleUserInfo(
        @JsonProperty("id") val id: String?,
        @JsonProperty("email") val email: String?,
        @JsonProperty("verified_email") val verifiedEmail: Boolean?,
        @JsonProperty("name") val name: String?,
        @JsonProperty("given_name") val givenName: String?,
        @JsonProperty("family_name") val familyName: String?,
        @JsonProperty("picture") val picture: String?,
        @JsonProperty("locale") val locale: String?
    )

 

해당 token을 헤더에 넣고 주소로 get 요청을 보낸다.

응답받는 Response 형식은 다음과 같다.

 

여기서 Id를 통해 구글로 로그인한 사용자를 고유번호를 통해 식별할 수 있게 되었다.

 

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

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

    private final val googleAccessTokenServer = "https://oauth2.googleapis.com/token"
    private final val googleUserInfoServer = "https://www.googleapis.com/oauth2/v2/userinfo"

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

        val accessToken = getAccessToken(code, isRemote)

        val restTemplate = RestTemplate()

        val googleUserInfo = restTemplate.exchange(
            RequestEntity<Any>(this.getHttpHeadersWithAuthorization(accessToken), HttpMethod.GET, URI.create(googleUserInfoServer)),
            GoogleUserInfo::class.java
        ).body

        val googleUserId = googleUserInfo?.id ?: ""

        return this.getOauthFromOauthIdAndOauthType(
            oauthId = googleUserId,
            oauthTypeEnum = OauthTypeEnum.GOOGLE,
            accessToken = accessToken)
    }

    override fun getAccessToken(code: String, isRemote: Boolean): String {
        val restTemplate = RestTemplate()

        val httpBody = LinkedMultiValueMap<String, String>()

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

        val result =  restTemplate.postForObject(googleAccessTokenServer, this.getHttpEntity(httpBody), GoogleAccessToken::class.java)?.accessToken ?: ""

        return result
    }

    private data class GoogleAccessToken(
        @JsonProperty("access_token") val accessToken: String,
        @JsonProperty("expires_in") val expiresIn: Int,
        @JsonProperty("token_type") val tokenType: String,
        @JsonProperty("scope") val scope: String,
        @JsonProperty("id_token") val idToken: String,
    )

    private data class GoogleUserInfo(
        @JsonProperty("id") val id: String?,
        @JsonProperty("email") val email: String?,
        @JsonProperty("verified_email") val verifiedEmail: Boolean?,
        @JsonProperty("name") val name: String?,
        @JsonProperty("given_name") val givenName: String?,
        @JsonProperty("family_name") val familyName: String?,
        @JsonProperty("picture") val picture: String?,
        @JsonProperty("locale") val locale: 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, accessToken: String): 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
            ))
        }
    }
}

 

이렇게 작성하여 요청하면 되는데, 구글은 로그인의 주소가 좀 어렵다.

https://accounts.google.com/o/oauth2/v2/auth?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&scope=https://www.googleapis.com/auth/userinfo.email

 

위의 주소에 값을 넣어서 요청하면 된다.

+ Recent posts