이번에 진행하는 프로젝트에서는 학생들의 과제 제출시간을 한국시간으로 저장하고 체크하기 위해, 아래와 같은 테스트코드를 사용했었다.
@Test
fun testValidTime(){
val koreaTime = LocalDateTime.now(ZoneId.of("Asia/Seoul"))
val curTime = LocalDateTime.now()
assertTrue(koreaTime.minusMinutes(5).isBefore(curTime))
assertTrue(koreaTime.plusMinutes(5).isAfter(curTime))
}
이렇게 Asia/Seoul의 시간과 현재 스프링부트의 시간을 가져온 후 앞뒤로 5분 차이가 나지 않는지 확인하는 것이다.
(만약 시간이 다르다면 무조건 1시간 이상 차이가 날 테니까)
당연히 로컬에서는 에러가 없었지만, DEV 서버에 올려 테스트를 해보니 통과하지 못하는 문제가 발생했다.
당연히 시간이 안 맞는 것일거고
로그를 찍어서 확인해보니
이렇게 스프링부트의 시간이 UTC로 설정이 되어 있었다.
처음에는 당연히 스프링부트가 우분투의 시간을 따른다고 생각하고 우분투의 시간을 한국시간으로 맞춰주었다.
interface OauthService {
fun getOauth(code: String, isRemote: Boolean): Oauth
fun getAccessToken(code: String, isRemote: Boolean): String
}
Oauth dataclass
@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
))
}
}
}
이번에도 저번과 마찬가지로 토큰 받아오기, 사용자 정보 받아오기를 구현하며 인터페이스도 저번에 사용했던
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
))
}
}
}
private final val naverUserInfoServer = "https://openapi.naver.com/v1/nid/me"
override fun getOauth(code: String, isRemote: Boolean): Oauth {
val accessToken = getAccessToken(code, isRemote)
val restTemplate = RestTemplate()
val naverUserInfo = restTemplate.exchange(
RequestEntity<Any>(this.getHttpHeadersWithAuthorization(accessToken), HttpMethod.GET, URI.create(naverUserInfoServer)),
NaverUserInfo::class.java
).body
val naverUserId = naverUserInfo?.response?.id ?: ""
return this.getOauthFromOauthIdAndOauthType(
oauthId = naverUserId,
oauthTypeEnum = OauthTypeEnum.NAVER,
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 NaverUserInfo(
@JsonProperty("resultcode") val resultCode: String,
@JsonProperty("message") val message: String,
@JsonProperty("response") val response: Response,
)
private data class Response(
@JsonProperty("id") val id: String,
@JsonProperty("nickname") val nickname: String?,
@JsonProperty("name") val name: String?,
@JsonProperty("email") val email: String?,
@JsonProperty("gender") val gender: String?,
@JsonProperty("age") val age: String?,
@JsonProperty("birthday") val birthday: String?,
@JsonProperty("profile_image") val profileImage: String?,
@JsonProperty("birthyear") val birthyear: String?,
@JsonProperty("mobile") val mobile: String?
)
하지만 이번은 Post가 아니라 Get이기 때문에 다른 방법을 사용한다.
저렇게 헤더에 Bearer로 토큰만 넣어주면 응답값이 나온다.
응답의 결과는 아래의 data class와 같지만, 어차피 id만 사용하기 때문에 id만 가져오도록 만들었다.
이렇게해서 네이버 로그인으로 로그인한 사용자를 고유번호를 통해 식별할 수 있게 되었다.
아래는 사용한 전체 코드이다.
package cobo.auth.service.oauth.impl
import cobo.auth.data.entity.Oauth
import cobo.auth.data.enums.OauthTypeEnum
import cobo.auth.repository.OauthRepository
import cobo.auth.service.oauth.OauthService
import com.fasterxml.jackson.annotation.JsonProperty
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod
import org.springframework.http.RequestEntity
import org.springframework.stereotype.Service
import org.springframework.util.LinkedMultiValueMap
import org.springframework.web.client.RestTemplate
import java.net.URI
@Service
class NaverOauthServiceImpl(
@Value("\${naver.auth.client_id}")
private val clientId: String,
@Value("\${naver.auth.client_secret}")
private val clientSecret: String,
private val oauthRepository: OauthRepository
): OauthService, OauthServiceImpl(oauthRepository) {
private final val naverAccessTokenServer = "https://nid.naver.com/oauth2.0/token"
private final val naverUserInfoServer = "https://openapi.naver.com/v1/nid/me"
override fun getOauth(code: String, isRemote: Boolean): Oauth {
val accessToken = getAccessToken(code, isRemote)
val restTemplate = RestTemplate()
val naverUserInfo = restTemplate.exchange(
RequestEntity<Any>(this.getHttpHeadersWithAuthorization(accessToken), HttpMethod.GET, URI.create(naverUserInfoServer)),
NaverUserInfo::class.java
).body
val naverUserId = naverUserInfo?.response?.id ?: ""
return this.getOauthFromOauthIdAndOauthType(
oauthId = naverUserId,
oauthTypeEnum = OauthTypeEnum.NAVER,
accessToken = accessToken)
}
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("client_id", clientId)
httpBody.add("client_secret", clientSecret)
httpBody.add("code", code)
return restTemplate.postForObject(naverAccessTokenServer, this.getHttpEntity(httpBody), NaverAccessToken::class.java)?.accessToken ?: ""
}
private data class NaverAccessToken(
@JsonProperty("access_token") val accessToken: String,
@JsonProperty("state") val state: String?,
@JsonProperty("error") val error: String?,
@JsonProperty("error_description") val errorDescription: String?
)
private data class NaverUserInfo(
@JsonProperty("resultcode") val resultCode: String,
@JsonProperty("message") val message: String,
@JsonProperty("response") val response: Response,
)
private data class Response(
@JsonProperty("id") val id: String,
@JsonProperty("nickname") val nickname: String?,
@JsonProperty("name") val name: String?,
@JsonProperty("email") val email: String?,
@JsonProperty("gender") val gender: String?,
@JsonProperty("age") val age: String?,
@JsonProperty("birthday") val birthday: String?,
@JsonProperty("profile_image") val profileImage: String?,
@JsonProperty("birthyear") val birthyear: String?,
@JsonProperty("mobile") val mobile: String?
)
}
package cobo.auth.service.oauth.impl
import cobo.auth.data.entity.Oauth
import cobo.auth.data.enums.OauthTypeEnum
import cobo.auth.repository.OauthRepository
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
import org.springframework.util.LinkedMultiValueMap
import java.util.concurrent.CompletableFuture
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
))
}
}
}
override fun patchAdmin(routineId: Int, newAdminId: Int, authentication: Authentication): ResponseEntity<CoBoResponseDto<CoBoResponseStatus>> {
val user = userRepository.findById(authentication.name.toInt())
.orElseThrow{throw NoSuchElementException("일치하는 사용자가 없습니다.")}
val newAdmin = userRepository.findById(newAdminId)
.orElseThrow{throw NoSuchElementException("일치하는 사용자가 없습니다.")}
val routine = routineRepository.findById(routineId)
.orElseThrow{throw NoSuchElementException("일치하는 루틴이 없습니다.")}
if (routine.admin != user)
throw IllegalAccessException("수정 권한이 없습니다.")
if (!participationRepository.existsByUserAndRoutine(newAdmin, routine))
throw NoSuchElementException("참여 정보가 없는 사용자입니다.")
routine.admin = newAdmin
routineRepository.save(routine)
return CoBoResponse<CoBoResponseStatus>(CoBoResponseStatus.SUCCESS).getResponseEntity()
}
해당 코드이다.
무려 데이터베이스에 5번 접근하게 된다.
마지막에 save하는 부분은 데이터베이스에서 데이터를 가져온 후 호출해야 하기 때문에 마지막에 실행해야 하지만, 위의 4개의 접근은 의존성이 존재하지 않기 때문에 4개를 각각 다른 쓰레드에서 비동기적으로 실행이 가능하다.
if (!participationRepository.existsByUserAndRoutine(newAdmin, routine))
throw NoSuchElementException("참여 정보가 없는 사용자입니다.")
현재 이 코드는 의존성이 존재하지만, JPA가 아닌 QueryDsl을 사용하여 리펙토링해서 의존성을 없애도록 하였다.