2. 고난의 시작 로그인 프로세스

선종우·2023년 8월 14일
0

1. 로그인 기술의 결정

  • 회원 정보 기반의 서비스이므로 무조건 로그인이 필요했다. 아직 디자인이나 기획이 진행되지는 않은 상태였지만, 소셜로그인은 도입하기로 합의가 되었다.
  • 그래서 우선은 스프링 시큐리티 OAuth를 이용해 로그인을 진행하고자 하였다.

2. 스프링 OAuth2.0 사용 그리고 문제

2.1. 스프링 OAuth를 사용

  • 기존에 Spring Security OAuth에 대해서는 알고 있었으나 실제 사용해 본적은 없었다. 또 OAuth에 대해 정확한 개념을 모르다보니 우선 OAuth의 매커니즘에 대해 이해하고 스프링 시큐리티 기본 기능을 이용해 로그인 기능 구현을 최대한 간단히 하고자 하였다. -> 나중에 큰 고통을 받음

  • 프로젝트 초반에 먼저 OAuth2.0의 개념을 확실히 잡고자 하였다. OAuth2.0(Open Authorization 2.0)의 핵심은 Third-party application(이 경우 나의 애플리케이션)이 자원 소유자(사용자)의 자원에 필요한 만큰만 접근할 수 있게 하는 것이다.

    • 그렇다면 OAuth 이전에는?
      • Third-party applications are required to store the resource
        owner's credentials for future use, typically a password in
        clear-text.
      • Third-party applications gain overly broad access to the resource
        owner's protected resources, leaving resource owners without any
        ability to restrict duration or access to a limited subset of
        resources
  • OAuth2.0의 흐름

    1. 사용자는 내 로그인 페이지에 접속한다.
    2. 내 서버는 사용자를 Authorization Server(카카오)로 리다이렉트 한다.
    3. 사용자는 로그인 및 자원에 대한 동의를 한다음 Authorization Server에게 보낸다.
    4. Authorization Server는 사용자 응답을 확인하고 문제가 없다면 이에 대한 Auhtorization Token을 발급해 사용자 브라우저로 반환한다.
    5. 사용자 브라우저는 Authorization Token과 함께 다시 내 서버로 redirect된다.
    6. 내 서버는 redirect request에서 Authorization Token을 추출한 다음 이에 대한 Access Token을 요청한다.`
    7. Authorization Server는 Authorization Token을 검증하고 이상이 없다면 Access Token, Refresh Token을 내 서버로 반환한다.
    8. 내 서버는 Access Token을 가지고 자원 서버(카카오 프로필, 이름 등이 저장된 서버)에 접근 해서 필요한 자원을 얻어온다.
  • 스프링 시큐리티에서는 1, 2 과정을 OAuth2AuthorizationRequestRedirectFilter가 처리하며 이때 기본으로 세팅된 url은 /oauth2/authorization/{registrationId}이다.

  • 이후 과정은 OAuth2LoginAuthenticationFilter 가 처리하는데, 이때 6번에 사용되는 redirect url은 기본적으로 /login/oauth2/code/{registrationId}이다.

  • 기존에 Spring Security를 어느정도 사용해봤기 때문에 OAuth2UserService, OAuth2User만 구현하면 된다고 생각했으나....

2.2. 문제점(프론트엔드와 백엔드 분리)

  • 문제는 이번 프로젝트에서 프론트엔드와 백엔드 서버를 분리한다는 점이었다.
  • 인터넷에서 관련 글을 찾아보니 취할 수 있는 전략은 크게 3가지 였다.
    1. 백엔드에서 Authorization을 모두 진행한다.
    2. 프론트엔드에서 Authorization Code를 받아와 백엔드에 전송하면 백엔드에서는 이를 이용해 Access Token을 받아온다.
    3. 프론트에서 모든 인증과정을 수행하고 백엔드에서는 프론트엔드의 코드를 받아 서비스의 자체 token을 발행한다.
  • 최대한 로그인 프로세스 구현에 힘을 들이고 싶지 않아, 2번 전략을 취하되 프론트에서 redirect url로 /login/oauth2/code/{registrationId}을 지정하면 되지 않을까 싶었으나...
  • 스프링 시큐리티 내부적으로 1 ~ 8 프로세스 모두를 수행하지 않으면 정상적으로 인증처리가 진행되지 않았다. -> 이 문제를 찾는데 시간을 엄청 써버렸다.
  • 스프링 Oauth를 사용한 이유가 최대한 로그인 구현비용을 줄이기 위함이었는데, Spring Security스펙에 맞춰 Custom Oauth Filter를 개발하는 건 배보다 배꼽이 더 큰 느낌이었다.
  • 그렇다고 프론트가 중간에서 사용자와 백엔드 사이에서 redirect url을 주고받게 하는 것도 모양새가 좋지 않아 3번 프로세스로 가기로 하였다.

2.3. 세션이냐 토큰이냐

  • 서버에서 사용자 정보를 관리함에 있어 세션을 사용할지 jwt token을 사용할지도 고민이었다.
  • 이번 개발의 목표는 최소 자원으로 개발하는 것이었기에 별도 jwt token filter를 개발하고 싶지 않았고, 무엇보다 scale out의 가능성이 크지 않았기에 굳이 token을 사용할 이유가 없다고 생각했다.
  • 또 초창기에는 https를 사용할 생각이 없었기에 중복로그인 방지, session 고정 공격 방지 처리를 쉽게 할 수 있는 Session방식으로 가려고 했다.
  • 그러나 여기서 또 문제가 발생했으니.... 브라우저에서 Javascript로 api콜을 하는데 이때 Cookie에 저장된 JsessionId를 불러올 수 없었다.
  • 문제는 http-only 속성 및 브라우저의 각종 보안기능이었다. 기본적으로 내장 톰캣은 JsessionId를 보낼 때 http-only속성을 넣어서 보낸다. 그렇다보니 브라우저가 백엔드로 Javascript api콜을 보낼 때 Cookie에 있는 JessionId에 접근할 수가 없었다.
  • 이를 우회하고자 본문에 JsessionId에 넣어도 보았지만 브라우저 단에서 크로스사이트 요청에 대해서 cookie를 전송하는 걸 막아버린다는 것이었다.(GPT의 답변)

    Before Chrome 80, cookies were sent with cross-site requests by default, regardless of whether the request was made over HTTP or HTTPS. This could potentially lead to security vulnerabilities and cross-site request forgery (CSRF) attacks.

    • SameSite=None; Secure: If a cookie has the SameSite=None; Secure attribute, it will be sent with cross-site requests made from both HTTP and HTTPS origins. This is usually used for cookies that are intended to be used by third-party services or widgets embedded on other sites.
    • SameSite=Lax: If a cookie has the SameSite=Lax attribute, it will be sent with cross-site requests made from HTTPS origins, but not from HTTP origins. This is a more secure default behavior that helps prevent certain types of CSRF attacks.
    • SameSite=Strict: If a cookie has the SameSite=Strict attribute, it will not be sent with any cross-site requests.
  • 위의 설정을 Cookie에 넣어주면 될 것 같긴 했지만 더이상 테스트에 시간을 쓸 수도 없었고, 프론트엔드 개발자에게 이거저거 테스트 요청을 하기도 무리였다.
  • 그래서 Spring Security의 기능을 최대한 이용해보려고 애를 썼지만 결국은 JwtToken 기반 인증필터를 구현해 인증/인가 프로세스를 진행하게 되었다.

0개의 댓글