저번 네이버 로그인에 이어 이번에는 구글 로그인이다.
이번에도 저번과 마찬가지로 토큰 받아오기, 사용자 정보 받아오기를 구현하며 인터페이스도 저번에 사용했던
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
위의 주소에 값을 넣어서 요청하면 된다.
'크무톡톡 프로젝트' 카테고리의 다른 글
JUnit을 Kotlin으로 테스트 할 때 beforeAll, AfterAll (0) | 2024.07.26 |
---|---|
카카오 Oauth 로그인, SpringBoot (0) | 2024.07.25 |
네이버 Oauth 로그인, SpringBoot (2) | 2024.07.23 |
Nginx로 Swagger Proxy_pass (1) | 2024.07.22 |
CompletableFuture 적용으로 성능 튜닝 (0) | 2024.03.09 |