Kotlin으로 카카오 로그인 구현하기-1

문지은·2021년 9월 27일
0

카카오 로그인 flow


출처 : 카카오 로그인 공식 문서

  1. 인증 코드 주고 받기
  2. 토큰 받기
  3. API 호출시마다 토큰으로 유효성 확인

위 그림은 어플리케이션과 카카오 서버간의 관계만 나타낸 것이다. 하지만 내가 만들 코틀린 서버는 클라이언트와도 소통해야 하므로 이를 바탕으로 다시 플로우를 나타내면 다음과 같다.

---클라이언트와 카카오 서버---
1. 클라이언트가 카카오 서버에 로그인
2. 카카오 서버는 클라이언트에게 authorization code 전송
3. 클라이언트가 authorization code를 카카오 서버에 전송
4. 카카오 서버는 클라이언트에게 access token, refresh token 전송
---클라이언트와 코틀린 서버---
5. 클라이언트가 코틀린 서버에 카카오 access token 전송
6. 코틀린 서버가 카카오 access token 유효성 검증 후 jwt token 생성해서 return

카카오 로그인 api 등록

카카오 개발자 사이트
카카오 개발자 사이트 접속 - 내 애플리케이션 - 애플리케이션 추가하기 이용해서 애플리케이션 등록
추가한 애플리케이션 들어가서 플랫폼 등록 - web 플랫폼 등록 - http://localhost:8080 등록

redirect uri 등록하러 가기 - 활성화 설정 on - Redirect URI http://localhost:8080/oauth 등록

web security 관련 설정

WebSecurityConfigurerAdapter를 상속받아서 WebSecurityConfig 클래스를 작성한다.

// EnableWebSecurity으로 등록
@EnableWebSecurity
class WebSecurityConfig(private val jwtProvider: JwtProvider) : WebSecurityConfigurerAdapter() {

// 필터를 안 타는 url들 설정
    override fun configure(web: WebSecurity) {
        web.ignoring().antMatchers(
            "/h2-console/**",
            "/sign-up",
            "/sign-in",
            "/don't-pass-filter"
        )
    }

// http 관련 설정
    override fun configure(http: HttpSecurity) {
        http.httpBasic().disable()
        http.cors().disable()
        http.csrf().disable()
        http.formLogin().disable()
        http.addFilterBefore(JwtAuthenticationFilter(jwtProvider), UsernamePasswordAuthenticationFilter::class.java)
    }
}

http 관련 설정에서 추가 설명

http.httpBasic().disable() : 로그인 인증창 비활성화
http.formLogin().disable() : 로그인 창 비활성화
http.addFilterBefore 이용해서 JwtAuthenticationFilter 등록

cors란?
cross origin resource sharing
한 출처에서 실행중인 웹 애플리케이션이 다른 출처의 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제

csrf란?
cross script resources forgery
사용자가 의도하지 않은 요청을 수행하게 하는 취약점
REST API는 stateless하기 때문에 사용자의 정보를 세션에 저장하지 않기 때문에 안전.
Thymeleaf, JSP 등을 사용하여 서버측에서 HTML을 직접 생성할 때는 세션이나 쿠키에 저장된 정보를 이용하여 공격하기 때문에 위험.

JWT 필터

위의 web security에서 필터를 등록했다. configure 메서드에서 제외한 url을 제외한 모든 해당 서버에 대한 요청은 필터를 거치게 된다. 해당 필터는 토큰의 유효성을 확인하여 통과 가능한지 불가능한지 판단하는 역할을 한다.

// GenericFilterBean을 상속받은 필터
class JwtAuthenticationFilter(
    private val jwtProvider: JwtProvider
    ): GenericFilterBean() {
    // doFilter override해서 토큰의 유효성 검증
    override fun doFilter(request: ServletRequest?, response: ServletResponse?, chain: FilterChain?) {
        try {

            val token: String = jwtProvider.getTokenFromHeader(request as HttpServletRequest, response as HttpServletResponse)
            jwtProvider.decodeToken(token)

        } catch (e: ExpiredJwtException) {
            jwtFailureTask(response as HttpServletResponse, ErrorCode.EXPIRED_TOKEN_ERROR)
            return
        } catch (e: NullPointerException) {
            jwtFailureTask(response as HttpServletResponse, ErrorCode.NO_TOKEN_ERROR)
            return
        } catch (e: Exception) {
            jwtFailureTask(response as HttpServletResponse, ErrorCode.INVALID_TOKEN_ERROR)
            return
        }

        chain!!.doFilter(request, response)
    }

// 토큰의 유효성 검증을 통과하지 못할 경우 에러 코드 반환 
    fun jwtFailureTask(
        response: HttpServletResponse
        , e: ErrorCode
    ){
        HandlerResponseUtil.doResponse(response, ErrorResponse.error(e), e.status)
    }
}


여기서 아ㅏㅏㅏㅏㅏ주 많은 시간을 보냈다.😢 왜냐하면 이 프로젝트에는 custom exception과 전역 exception handler가 등록되어 있었다. 그래서 처음엔 try catch문에서 예외를 잡은 뒤 custom exception을 throw했다. 하지만 테스트 결과 custom exception은 던져지지만 전역 exception handler가 작동하지 않았다. 이것이 예외 처리 문제인 줄 알고 exception handler와 관련된 코드를 모두 뜯어봤다. (덕분에 exception handler 공부도 했다,,ㅎ)
알고보니 전역 exception handler 코드에 사용된 @RestControllerAdvice 는 컨트롤러에서 발생한 예외만 핸들링할 수 있다고 한다. 그래서 filter 단에서 발생한 예외에 대해서는 response를 위한 util 클래스를 작성하여 바로 response를 던질 수 있게 했다. (나중에 코틀린에 맞게 extension으로 바꿔보면 좋을 듯)

토큰 생성이나 토큰 decode 등 토큰에 대한 작업을 진행하는 메서드는 JwtProvider class에서 정의했다.

이제 카카오 관련 코드를 작성할 차례다.
다음에 계쏙 🎈

profile
백엔드 개발자입니다.

0개의 댓글