사이드프로젝트(withPet) #2 - 통신 세팅 및 jwt 토큰 인증 구현

김민태·2024년 6월 16일

withPet

목록 보기
2/6
post-thumbnail

디자인이 확정되지 않아 XML액티비티에서 최소한의 레이아웃만 개발한 후, 서버 통신 부분을 먼저 구현하였습니다.


NetworkService 소스 먼저 보기


우선 간단한 GET 방식의 API부터 연결해보았습니다.

백엔드 쪽에서 포스트맨(Postman)을 이용해 테스트할 수 있는 API를 정의했고, 이를 통해 테스트를 진행했습니다.
해당 API는 버전(version) 값을 응답하며, 이는 스플래시 화면에서 조회 후 강제 업데이트버전 관리 기능에 사용될 예정입니다.

API 테스트

1. 통신 세팅

서버 통신에 사용할 엔드포인트와 요청 메소드를 정의한 인터페이스 작성

interface ApiService {
    @GET("api/v1/version")
    fun getVersion(): Call<ApiResponse<VersionPayload>>
    // 생략
}

API 응답 데이터를 체계적으로 관리하기 위해, 응답에서 특정 필드를 추출할 수 있도록 데이터 클래스를 정의

data class VersionPayload(
    val id: Number,
    val version: String
)

API 호출에 필요한 Retrofit 객체를 전역에서 쉽게 사용할 수 있도록 이를 다른 파일에서 선언하기 위해 NetworkService 객체를 생성

object NetworkService {
    fun getService(): ApiService = retrofit.create(ApiService::class.java)
    
    private val retrofit =
        Retrofit.Builder()
            .baseUrl(SERVER_URL)
            .client(provideOkHttpClient())
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    // 생략
}

소스 코드 설명:

  • getService() 메서드를 통해 ApiService 인터페이스에서 정의된 엔드포인트를 가져옵니다.
  • Retrofit 객체를 초기화하고, 미리 정의된 URLOkHttpClient를 설정합니다.
  • JSON 파싱을 위해 GsonConverterFactory를 추가하였습니다.

provideOkHttpClient() 함수에는 타임아웃 설정Interceptor를 추가해, 헤더에서 토큰 추출 등의 기능을 구현했습니다.

2. 네트워크 요청 처리 함수

서버에서 받아온 데이터를 처리할 수 있도록 네트워크 요청을 처리하는 객체를 만들어, 응답 결과에 따라 성공 또는 실패를 처리할 수 있도록 구성했습니다.

object CommonRepo {
    fun getVersion(
        networkFail: (String) -> Unit,
        success: (ApiResponse<VersionPayload>) -> Unit,
        failure: (Throwable) -> Unit
    ) {
        NetworkService.getService().getVersion()
            .enqueue(object : Callback<ApiResponse<VersionPayload>> {
                override fun onResponse(
                    call: Call<ApiResponse<VersionPayload>>,
                    response: Response<ApiResponse<VersionPayload>>
                ) {
                    if (response.isSuccessful) {
                        val data = response.body() ?: return
                        success(data)
                    } else {
                        networkFail(response.code().toString())
                    }
                }

                override fun onFailure(call: Call<ApiResponse<VersionPayload>>, t: Throwable) {
                    failure(t)
                }
            })
    }
}

소스 코드 설명:

  • getService()를 통해 getVersion() 메서드를 호출하여 실제 API 요청을 실행합니다.
  • enqueue를 통해 비동기 요청을 수행하며, 결과에 따라 onResponse 또는 onFailure 콜백을 받습니다.
  • onResponse에서 성공 시 응답 데이터(response.body())를 success()로 전달하고, 실패 시에는 networkFail()로 처리합니다.

VersionPayload 데이터 클래스를 통해 response.body()에서 값을 바로 사용할 수 있으며, it.payload.version과 같이 데이터를 쉽게 추출할 수 있습니다.

3. JWT 토큰 Interceptor 설정

JWT 토큰을 자동으로 처리하고, 서버와의 보안 통신을 원활하게 하기 위해 Interceptor 설정을 추가했습니다.

NetworkService 소스에서 Retrofit 객체를 설정할 때 사용한 provideOkHttpClient() 함수는 OkHttpClient를 구성하고 다양한 설정을 추가합니다. 그 중 주요 Interceptor는 다음과 같습니다:

  1. ReceiveInterceptor: 응답에서 토큰을 추출.
  2. AddInterceptor: 요청에 토큰을 추가.
  3. HTTP 로그 인터셉터: 로깅 수준 선택.
  4. 타임아웃 설정: 연결, 읽기, 쓰기 제한 시간 설정.

3-1. ReceiveInterceptor

서버에서 받은 응답에서 토큰을 추출하여 저장할 수 있도록 ReceiveInterceptor를 작성했습니다.

class ReceiveInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val pref = Constants.context?.getSharedPreferences("dataPreferences", Context.MODE_PRIVATE)
        val original = chain.proceed(chain.request())

        try {
            Log.d(
                "응답 값", GsonBuilder()
                    .setPrettyPrinting()
                    .create()
                    .toJson(Gson().fromJson(original.peekBody(Long.MAX_VALUE).string(), JsonObject::class.java))
            )
        } catch (e: Exception) {
            Log.d("응답 에러", e.message ?: "")
        }

        val jwtAccessToken = original.headers("X-ACCESS-TOKEN").getOrNull(0)
        val jwtRefreshToken = original.headers("X-REFRESH-TOKEN").getOrNull(0)
        val jwtTokenTime = original.headers("X-TOKEN-TIME").getOrNull(0)

        jwtAccessToken?.let { pref?.edit()?.putString("access-token", it)?.apply() }
        jwtRefreshToken?.let { pref?.edit()?.putString("refresh-token", it)?.apply() }
        jwtTokenTime?.let { pref?.edit()?.putString("token-time", it)?.apply() }

        return original
    }
}

3-2. AddInterceptor

저장된 토큰을 API 요청에 자동으로 추가할 수 있도록 AddInterceptor를 구성했습니다.

class AddInterceptor : Interceptor {
    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response = with(chain) {
        val pref = Constants.context?.getSharedPreferences("dataPreferences", Context.MODE_PRIVATE)
        val accessToken = pref?.getString("access-token", "")
        val builder = request().newBuilder()

        if (DataProvider.isLogin) {
            builder.addHeader("X-ACCESS-TOKEN", accessToken ?: "")
        }
        builder.addHeader("User-Agent", "aos")

        proceed(builder.build())
    }
}

소스 코드 설명:

  • ReceiveInterceptor에서 저장된 accessToken을 가져와, 요청에 헤더로 추가합니다.
  • DataProvider.isLogin는 로그인 여부를 체크하는 전역 변수입니다. 로그인 성공true로 변경되며, 이후의 요청부터는 accessToken이 자동으로 첨부됩니다.

3줄 요약

  1. RetrofitOkHttpClient를 사용하여 서버 통신을 구축하고, 버전 관리 API를 연동했습니다.
  2. JWT 토큰 처리를 위해 Interceptor를 사용하여 자동으로 토큰을 추출하고 요청에 추가했습니다.
  3. 이를 통해 서버와의 통신을 효율적으로 구현하고, 추후 더 복잡한 통신 기능을 추가할 수 있는 기반을 마련했습니다.

0개의 댓글