[이웃사이] 사용자 인증 방식 - 개념과 구현 과정

아양시·2022년 9월 15일
1

이웃사이

목록 보기
4/6

📚 사전


1. HTTP 쿠키

  • 웹 브라우저를 통해 사용자의 컴퓨터에 저장되는 작은 기록 정보 파일
  • 웹 사이트는 저장된 사용자의 정보를 컴퓨터에 남겨서 필요할 때마다 재사옹한다.
  • 구조 : {이름, 값, 0개 이상의 속성} (속성 - 만료 기간, 도메인, 플래그)
  • 일반적으로 웹 서버에 의해 설정되지만 스크립트 언어를 사용하여 클라이언트에 의해 설정 가능하다.
  • Set-Cookie HTTP 헤더를 사용하여 설정되며 웹 서버의 HTTP 응답을 통해 송신된다.
  @PostMapping("")
  public ApiSuccessResult<SignInAccountResponse> signInAccount (...) {

      ...

      ResponseCookie cookie = ResponseCookie.from(JwtProvider.ACCOUNT_TOKEN_NAME, refreshToken)
                  .httpOnly(true)
                  .path("/")
                  .secure(true)
                  .sameSite("None")
                  .maxAge(COOKIE_VALIDATION_SECOND)
                  .build();
      res.addHeader("Set-Cookie", cookie.toString());

      return ApiUtil
                  .success(new SignInAccountResponse(account, accessToken, refreshToken));
  }	

2. Session

  • 웹 사이트의 여러 페이지에 걸쳐 사용되는 사용자 정보를 저장하는 방법
  • 사용자가 브라우저를 닫아 서버와의 견결을 끝내는 시점까지를 세션이라고 한다.
  • 서버에 데이터를 저장하고, 세션의 키값만을 클라이언트 측에 남겨둔다.
  • 브라우저는 필요할 때마다 키값을 사용해서 서버에 저장된 데이터를 사용한다.
  • 보안에 취약한 쿠키를 보완해주는 역할을 하고 있다.
  • UUID 생성 등으로 키값을 랜덤으로 생성해서 세션 저장소에 저장 후 쿠키에 담아 응답을 보내는 방식으로 구현할 수 있다.

3. JWT

  • 정보를 안전하게 전송하기 위한 방법을 정의하는 서명된 토큰으로서의 JSON 객체
  • 일반적으로 서버의 비공개 키에 의해 서명 가능하고, 서버는 해당 토큰이 유효한지 확인할 수 있다.
  • 구조 : {헤더, 페이로드, 서명} - 점(.)을 사용해 연결된다.
  • 페이로드는 일련의 등록된 클레임 또는 사용자 지정 클레임을 포함한다.
  • 헤더, 페이로드, 서명의 데이터와 시크릿 키는 토큰을 생성한다.
String token = JWT.create()
                .withIssuedAt(new Date(System.currentTimeMillis()))
                .withExpiresAt(new Date(System.currentTimeMillis() + expireTime))
                .withPayload(payload)
                .withIssuer(ISSUER)
                .sign(getSigningKey(SECRET_KEY));
  • 사용자가 로그인 시 JWT가 반환되며, 로컬이나 세션 저장소 또는 쿠키에 저장되어야 한다.
  • 클라이언트 측에서 보호된 루트나 자원에 접근하고 싶은 경우 일반적으로 Bearer 스키마를 사용하여 Authorization 헤더 안에 JWT를 보내야 한다.

4. CSRF(사이트 간 요청 위조)

  • 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 서버에 요청하게 하는 공격을 말한다.
  • 특정 웹 서비스가 사용자의 웹 브라우저를 신용하는 상태를 노린 것으로, 사용자가 웹 서비스에 로그인한 상테에서 CSRF 공격 코드가 삽입된 페이지를 열면, 해당 공격 명령을 서버가 신용하게 되어 공격에 노출된다.
  • 서버가 쿠키를 통한 단순한 사용자 확인 과정만 거친다면 공격 피해 위험이 있다.

5. XSS(사이트 간 스크립팅)

  • 웹 사이트 관리자가 아닌 사람이 웹 페이지에 악성 스크립트를 삽입할 수 있는 취약점이다.
  • 주로 웹 서비스의 게시판 등에 악성 스크립트를 게시하는 형태로 공격된다.
  • 웹 어플리케이션이 사용자로부터 입력 받은 값을 제대로 검사하지 않고 사용할 경우 나타난다.
  • 해커가 사용자의 정보(쿠키, 세션 등)를 탈취하거나, 자동으로 비정상적인 기능을 수행할 수 있게 한다.

6. CORS(교차 출처 자원 공유)

  • 한 출처에서 실행 중인 웹 어플리케이션이 다른 출처의 보호된 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 정책
  • 브라우저와 서버 간의 안전한 교차 출처 요청 및 데이터 전송을 지원한다.
  • CORS를 활성화하지 않으면 기본적으로 브라우저는 보안 상의 이유로 스크립트에서 시작한 교차 출처 HTTP 요청을 제한한다. (동일 출처 정책)

7. Redis

  • 키-값 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터베이스 관리 시스템
  • 다양한 자료 구조의 값을 가질 수 있다.
  • 서버의 주 메모리에 상주하기 때문에 매우 빠르다.
  • 캐싱, 세션 관리, 실시간 순위표, 대기열, 채팅 및 메시징 등에 사용할 수 있다.



👩‍💻 [이웃사이] 사용자 인증 방식


🤓 기존의 인증 방식 시나리오

  1. 사용자 로그인
  2. 서버에서 필요한 클레임을 포함하는 페이로드와 시크릿 키를 이용해 JWT(Access Token) 생성
  3. 생성한 Access Token을 쿠키에 담아 클라이언트에 응답 전송
  4. 클라이언트는 쿠키를 저장하고, 필요한 서비스 요청 시에 쿠키에서 Access Token을 추출해 Authorization 헤더에 제출

👿 문제

  기존의 방식에서는 서버에 영향력 있는 요청을 허가받을 수 있는 access token이 쿠키에 그대로 저장된다.

  이 경우, CSRF나 XSS 공격 등에 노출되면 서버는 그대로 위조된 요청을 받을 위기에 처하게 된다.

  공격에 대비하기 위한 여러 보안 기법들이 존재하지만, 서버는 기본적으로 사용자 정보를 보호하는 시스템을 구축 해야할 것이다. 인식된 취약점을 그대로 둘 수는 없다는 것이다.

✅ 해결 방안

  쿠키에 Access Token을 저장하지 말자.
대신 Refresh Token을 쿠키에 넣어주고, 클라이언트 측에서 브라우저 렌더링 시마다 Refresh Token으로 Access Token을 재발급 받아 쿠키 등에 저장하지 않고 사용할 수 있도록 하자.

  또한, Access Token을 발급할 때는 HTTP Response를 통해서 발급함으로써 CORS 정책에 의해 외부에서는 사용 불가하도록 하자.

  이 경우, 공격을 당해도 Refresh Token만 노출되기 때문에 그것만 가지고는 서버에 위조 요청을 보낼 수가 없다.

  기본적으로 스크립트 언어로 쿠키를 참조할 수 없도록 httpOnly 쿠키를 발행해서 Refresh Token도 노출되지 않도록 하자.

  그리고 서비스 사용 중에 인증이 만료되지 않도록 Access Token 재발급 시 Refresh Token 유효기간의 1/2가 지났다면 Refresh Token도 쿠키를 통해 재발급 하자.

😊 개선된 인증 방식 시나리오

  1. 사용자 로그인
  2. 서버에서 필요한 클레임을 포함하는 페이로드와 시크릿 키를 이용해 Access Token과 Refresh Token 생성
  3. Refresh Token은 쿠키에 저장해서 전달하고, 별도로 Access Token과 Refresh Token을 HTTP Response를 통해 클라이언트에 전달한다.
  4. 클라이언트는 전달받은 Access Token을 local variable에 저장해서 필요한 서버 요청 시에 사용하고, 브라우저 렌더링 시 쿠키의 Refresh Token을 통해 Access Token을 재발급 받는다.
  5. 클라이언트에서 Access Token 재발급 요청이 오면 서버에서는 쿠키에서 Refresh Token을 추출해 유효성을 확인한 후, Access Token을 재발급해 HTTP Response를 통해 전달한다.

🥸 무지막지한 Access Token 재생성

  바꾼 인증 방식은 보안의 측면에서 개선되었지만, 렌더링 시마다 새로운 Access Token을 발급한다는 overload가 있다. 사용자 수가 크게 확대되고, 각 사용자가 렌더링을 할수록 부하가 커질 것이다.

  따라서 Access Token 재발급 시마다 토큰을 새로 생성하는 기존 방식을 개선하기 위해 Redis를 이용하여 토큰을 캐싱해서 사용해보기로 했다.

😎 완성된 Access Token 캐싱 시나리오

  1. 사용자 로그인 시, Refresh Token과 Access Token을 발급한다.
  2. 발급한 Refresh Token을 키로, Access Token을 값으로 Redis 캐시에 저장한다.
  3. 클라이언트가 Access Token 재발급을 요청하면, 쿠키에서 Refresh Token을 추출해 유효한지 확인한다.
    3-1. Refresh Token이 유효한 경우, 캐싱된 값이 있으면 꺼내고, 없으면 새로 생성해서 HTTP Response를 통해 발급해준다.
    3-2. Refresh Token이 만료된 경우, 캐시에서 만료된 Refresh Token 값을 삭제하고, Refresh Token과 Access Token을 새로 생성해서 캐싱한 뒤 Refresh Token은 쿠키, Access Token은 HTTP Response를 통해 발급해준다.



📜 참고 문헌

profile
BE Developer

0개의 댓글