인증과 인가

WinG·2024년 7월 4일
0
post-thumbnail

인증 & 인가

인증 : 사용자의 신원을 확인하는 절차

인가 : 사용자의 권한을 확인하고 허가하는 절차

HTTP의 무상태성

Stateless(무상태성), 말 그대로 상태를 유지하지 않는다는 말이다. 즉, 서버는 클라이언트의 상태를 저장하지 않는다. 때문에 서버측에서는 클라이언트의 이전의 상태에 대해서는 관리하고 있지 않기 때문에 해당 요청을 보낸 사용자가 누구인지 알 수 없다. 즉, 인증이 유지가 되지 않아 다시 인증을 해야하는 문제가 발생한다. 이러한 문제를 해결하기 위해 인증을 유지하는 방법으로 세션과 토큰 등이 있다.

토큰 기반 인증의 이해

토큰 기반 인증은 서버 내에 유저 정보 등을 저장하는 것이 아닌 클라이언트가 직접 자신에 해당하는 정보를 저장하는 방식이다.

JWT란?

JWT란 JSON Web Token의 줄임말로 JSON 포맷을 이용하여 사용자에 대한 속성(위에서의 유저 정보)을 저장하는 Claim 기반의 Web Token이다.

JWT 구조

🍀 JWT = Header + Payload + Signature

 JSON 형태인 각 부분은 Base64로 인코딩 되어 표현되고 구분자 '.'를 사용하여 구분된다.

이때 Base64로 인코딩된 문자열은 인코딩 전 같은 JSON 형태에 대해 항상 같은 인코딩 문자열을 반환(Header+Payload 부분)하므로(Signature 부분은 매번 변한다) 비밀번호와 같은 주요 정보를 인코딩시켜 보내게 된다면 보안에 매우 취약하다.

  1. Header

    토큰의 헤더는 alg과 typ 두 가지 정보로 구성된다

  • alg
    알고리즘 방식을 지정하며, 서명(Signature) 및 토큰 검증에 사용
  • typ
    토큰의 타입을 지정
  1. Payload

    토큰의 페이로드에는 토큰에서 사용할 정보의 조각들인 클레임(Claim)이 담겨 있다. 클레임은 총 3가지로 나누어지며, Json(Key/Value) 형태로 다수의 정보를 넣을 수 있다.

    Claim 1. 등록된 클레임(Registered Claim)

    등록된 클레임은 토큰 정보를 표현하기 위해 이미 정해진 종류의 데이터들로, 모두 선택적으로 작성이 가능하며 사용할 것을 권장한다. 또한 JWT를 간결하게 하기 위해 key는 모두 길이 3의 String이다. 여기서 subject로는 unique한 값을 사용하는데, 사용자 이메일을 주로 사용한다.

  • iss: 토큰 발급자(issuer)

  • sub: 토큰 제목(subject)

  • aud: 토큰 대상자(audience)

  • exp: 토큰 만료 시간(expiration), NumericDate 형식으로 되어 있어야 함 ex) 1480849147370

  • nbf: 토큰 활성 날짜(not before), 이 날이 지나기 전의 토큰은 활성화되지 않음

  • iat: 토큰 발급 시간(issued at), 토큰 발급 이후의 경과 시간을 알 수 있음

  • jti: JWT 토큰 식별자(JWT ID), 중복 방지를 위해 사용하며, 일회용 토큰(Access Token) 등에 사용

    Claim 2. 공개 클레임(Public Claim)

    공개 클레임은 사용자 정의 클레임으로, 공개용 정보를 위해 사용된다. 충돌 방지를 위해 URI 포맷을 이용하며한다.

    Claim 3. 비공개 클레임(Private Claim)

    비공개 클레임은 사용자 정의 클레임으로, 서버와 클라이언트 사이에 임의로 지정한 정보를 저장한다. 아래의 예시와 같다.

{
    "token_type": access
}
  1. Signature

    Signature는 토큰을 인코딩하거나 유효성 검증을 할 때 사용하는 고유한 암호화 코드이다.

    💡 헤더 & 페이로드 인코딩 → 인코딩한 값을 해싱 → 해싱한 값을 다시 인코딩 → signature 완성

Signature는 위에서 만든 헤더(Header)와 페이로드(Payload)의 값을 각각 base64로 인코딩하고, 인코딩한 값을 비밀 키를 이용해 헤더(Header)에서 정의한 알고리즘으로 해싱을 하고, 이 값을 다시 Base64로 인코딩하여 생성한다.

JWT 프로세스

  1. 사용자가 서비스에 로그인을 하면 Refresh Token과 Access Token을 발급 받는다.
  2. Access Token이 만료가 되면 클라이언트가 저장하고 있는 Refresh Token으로 요청을 보내 Access Token을 재발급 받는다.
  3. Refresh Token이 만료가 되면 다시 로그인을 진행해 Refresh Token과 Access Token을 발급 받는다.

Refresh Token

Refresh Token은 Access Token의 보안 취약성을 보완하기 위해 등장한 개념이다.
일반적으로 AccessToken의 만료 기한보다 길게 설정하며, AccessToken이 만료되면 Refresh Token으로 요청을 보내 다시 AccessToken을 발급받는 방식으로 작동한다.

Spring Security+ JWT를 활용한 소셜 로그인

OAuth 2.0

🌱 OAuth
웹, 모바일, 데스크 톱 어플리케이션에서의 간단하고 표준적인 방법으로 보안 인가를 허용하기 위한 개방형 표준 프로토콜

구글, 페이스북, 트위터와 같은 다양한 플랫폼의 특정한 사용자 데이터에 접근하기 위해 제3자 클라이언트(우리 서비스)가 사용자의 접근 권한을 위임(Delegated Authorization)받을 수 있는 표준 프로토콜이다.
쉽게 말하자면, 우리의 서비스가 우리 서비스를 이용하는 유저의 타사 플랫폼 정보에 접근하기 위해서 권한을 타사 플랫폼으로부터 위임 받는 것 이다.

OAuth 2.0 주체

OAuth에서는 동작에 참여하는 각각의 대상을 부르는 명칭이 존재한다.

Resource Owner

말 그대로 리소스 소유자. 우리 서비스의 사용자이자 구글, 카카오 등의 플랫폼에서 리소스를 소유하고 있는 사용자이다. 즉, 인증을 수행하는 주체이다.

Client

리소스 소유자 대신 위임 받은 권한으로 Resource Server의 자원을 이용하고자 하는 서비스. 보통 우리가 개발하려는 서비스로 권한을 위임받는 주체이다.

Authorization Server

리소스 소유자(Resource Owner)를 인증하고 클라이언트(Client)에 엑세스 토큰을 발급하는 서버이다. 즉, 인증을 검증하고 권한을 부여하는 주체이다.

Resource Server

엑세스 토큰을 사용하여 리소스 요청을 수락하고 응답할 수 있는 리소스를 가지고 있는 서버. 다시 말해, 애플, 카카오와 같이 리소스를 가지고 있는 서버로 인가를 수행하고 리소스를 제공하는 주체이다.

애플리케이션 등록

OAuth 2.0 서비스를 이용하기 위해서는 Client를 Resource Server에 등록해야하는 작업이 선행되어야 한다.

Redirect URI

Redirect URI는 사용자가 OAuth 2.0 서비스에서 인증을 마치고(ex. 카카오 로그인 페이지에서 로그인을 마쳤을 때) 사용자를 리디렉션시킬 위치이다. 즉, Redirect URIAuthorization Code를 전달받을 주소이다. OAuth 2.0 서비스는 인증이 성공한 사용자를 사전에 등록된 Redirect URI로만 리디렉션 시킨다. 승인되지 않은 URI로 리디렉션 될 경우, Authorization Code를 중간에 탈취당할 위험성이 있기 때문이다. Redirect URI는 기본적으로 보안을 위해 https만 허용하지만 예외적으로 localhost는 http가 허용된다.

Authorization Code가 뭔가요?
Resource Server가 발급하는 임시 코드로 Client가 자신의 자원을 사용할 수 있는 Access Token을 발급받기 위해 사용한다.

Client ID, Client Secret

등록과정을 마치면, Client ID와 Client Secret를 얻을 수 있다. Client ID클라이언트 웹 어플리케이션을 구별할 수 있는 식별자이고 Clinet SecretClient ID에 대한 비밀키이다. 이때, Client ID는 공개되어도 상관없지만, Client Secret은 절대 유출되어서는 안된다. 발급된 Client ID와 Client Secret은 액세스 토큰을 획득하는데 사용된다.

OAuth 2.0의 동작 메커니즘

1 ~ 2. 로그인 요청

Resource Owner가 로그인을 요청하면 Client는 OAuth 프로세스를 시작하기 위해 사용자의 브라우저를 Authorization Server로 보내야한다. 이때 Client는 Authorization Server가 제공하는 Authorization URL에 Response Type , Client Id , Redirect URI , Scope 등의 매개변수를 쿼리 스트링으로 포함하여 보낸다.

  • response_type 
    반드시 code로 값을 설정해야한다. 인증이 성공할 경우 클라이언트는 Authorization Code를 받을 수 있다.
  • client_id : 애플리케이션을 생성했을 때 발급받은 Client ID
  • redirect_uri : 애플리케이션을 생성할 때 등록한 Redirect URI
  • scope : 클라이언트가 부여받은 리소스 접근 권한

3 ~ 4. 로그인 페이지 제공, ID/PW 제공

Authorization URL로 이동된 Resource Owner는 제공된 로그인 페이지에서 ID와 PW를 입력하여 인증한다.

5 ~ 6. Authorization Code 발급, Redirect URI로 리디렉트

인증이 성공되었다면, Authorization Server는 제공된 Redirect URI로 사용자를 리디렉션시킨다. 이때, Redirect URI에 Authorization Code를 포함하여 사용자를 리디렉션 시킨다.

Authorization Code 사용 이점
Authorization Code는 access token을 직접 전달하는 대신 사용되므로, 중간에 탈취될 위험을 줄여 보안상의 이점이 존재한다.

7 ~ 8. Authorization Code와 Access Token 교환

Client는 Authorization Server에 Authorization Code를 전달하고, Access Token을 응답받는다. Client는 발급받은 Resource Owner의 Access Token을 저장하고, 이후 Resource Server에서 Resource Owner의 리소스에 접근하기 위해 Access Token(절대 유출되어서는 안된다)을 사용한다.

Authorization Code와 Access Token 교환은 token 엔드포인트에서 이루어지며 application/x-www-form-urlencoded 의 형식에 맞춰 전달해야한다.

application/x-www-form-urlencoded 타입이란?
html form을 통한 POST 전송 방식 중 가장 기본이 되는 Content-Type으로 보내는 데이터를 url 인코딩 후 웹 서버에 보내는 방식이다.
요청 데이터를 키(key)와 값(value)의 쌍으로 구성한다. 각 쌍은 '='로 키와 값이 연결되며, 여러 개의 key-value 쌍은 '&'로 구분함으로써 여러 개의 데이터를 한 번에 전송할 수 있다.

POST /oauth/token HTTP/1.1
Host: authorization-server.com  # OAuth 토큰을 발급하는 서버의 도메인 이름

grant_type=authorization_code
&code=xxxxxxxxxxx
&redirect_uri=https://example-app.com/redirect
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx
  • grant_type : 항상 authorization_code로 설정되어야 한다.
  • code : 발급받은 Authorization Code
  • redirect_uri : Redirect URI
  • client_id : Client ID
  • client_secret : RFC 표준상 필수는 아니지만, Client Secret이 발급된 경우 포함하여 요청해야 한다.

9. 로그인 성공

위 과정을 성공적으로 마치면 Client는 Resource Owner에게 로그인이 성공하였음을 알린다.

10 ~ 13. Access Token으로 리소스 접근

이후 Resource Owner가 Resource Server의 리소스가 필요한 기능을 Client에 요청한다. Client는 위 과정에서 발급받고 저장해둔 Resource Owner의 Access Token을 사용하여 제한된 리소스에 접근하고, Resource Owner에게 자사의 서비스를 제공한다.

Spring Security

🌱 Spring Security
Spring 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크

동작원리

  • Security 의존성이 없는 경우

클라이언트 요청은 서버 컴퓨터의 WAS(톰캣)의 필터들을 통과한 뒤 스프링 컨테이너의 컨트롤러에 도달한다.

  • Security 의존성 추가 → 사용자의 요청을 감시

WAS의 필터단에서 요청을 가로챈 후 시큐리티의 역할을 수행한다.

  • WAS의 필터에 하나의 필터를 만들어서 넣고 해당 필터(Delegating Filter Proxy)에서 요청을 가로챔
  • 해당 요청은 스프링 컨테이너 내부에 구현되어 있는 스프링 시큐리티 감시 로직을 거침
  • 시큐리티 로직을 마친 후 다시 WAS의 다음 필터로 복귀

내부 구조

SecurityContext 💾

SecurityFilterChain 내부에 존재하는 각각의 필터는 시큐리티 관련 작업을 진행한다. 이때, 모든 작업은 기능 단위로 분업하여 진행하기 때문에 앞에서 한 작업을 뒤의 필터가 알기 위한 저장소 개념이 필요하다. 예를 들어, 인가 필터가 작업을 하려면 유저의 ROLE 정보가 필요한데, 앞단의 필터에서 유저에게 ROLE 값을 부여한 결과를 인가 필터까지 공유해야만 인가 작업을 수행할 수 있다. 여기서 등장한 것이 SecurityContext 개념이다.

🌱 SecurityContext란, 필터를 거쳐 인증된 객체(Authentication)가 저장되는 저장소이다.

각 filter를 거치면서 도출된 인증 관련 정보는 Authentication 객체에 담긴다.

Authentication 객체

  • Principal : 유저 정보를 담은 객체 (커스텀 가능)
  • Credentials : Principal이 올바르다고 입증하는 객체 (비밀번호, 토큰 저장)
  • Authorities : 인증된 유저의 권한(ROLE) 목록을 저장

Authentication 객체는 SecurityContext에 포함되어 관리되며 N개의 SecurityContext는 SecurityContextHolder에 의해서 관리된다. SecurityContextHolder는 SecurityContext를 감싸고 있는 wrapper 클래스며, 실제 Authentication객체에 접근할 땐 SecurityContext객체를 통해 꺼내올 수 있다.
그 예시로 SecurityContextHolder.getContext().getAuthentication().getAuthorities() 코드를 통해 Authentication객체의 Authorities에 접근할 수 있다.

FilterChain ⛓️

  1. DelegatingFilterProxy
    WAS의 filter단에서 스프링 Bean을 찾아 요청을 넘겨주는 서블릿 필터
    ⇒ Servlet Container와 Spring Container를 연결해주는 필터

  2. FilterChainProxy
    DelegatingFilterProxy에 의해 호출되는 SecurityFilterChain들을 들고 있는 Bean

  3. SecurityContextHolderFilter
    접근한 유저에 대해 SecurityContext를 관리

  4. LogoutFilter
    로그아웃에 대한 처리를 담당하는 필터로 사용자가 로그아웃 요청을 했을 경우에만 적용되는 필터
    세션 무효화, 인증 토큰 삭제, SecurityContext에서 해당 토큰 삭제 등 로그아웃시 필요한 다양한 기능 제공

  5. UsernamePasswordAuthenticationFilter

    Form Based Authentication(폼 기반 인증)을 위한 인증을 진행할 때, 아이디와 패스워드 데이터를 파싱하여 인증 요청을 위임하는 필터

    Form Based Authentication이란?
    사용자가 입력한 인증 정보인 username과 password를 통해 인증을 하는 방식


    현재에는 API 기반 인증 방식이 가장 많이 사용되고 있고 우리의 프로젝트에서도 Rest API로 토큰 기반 인증 방식을 사용할 것이기 때문에 폼 기반 인증을 비활성화해야 한다.

    http.formLogin().disable() 은 폼 기반 로그인 방식을 비활성화 한다는 뜻으로, 다른 로그인 방식을 사용하겠다는 것을 의미한다.

  6. ExceptionTranslationFilter
    FilterChain을 거치면서 발생하는 인증 및 접근 예외에 대한 처리를 하기 위한 용도의 필터

동작 과정

  1. 로그인 과정

  1. 사용자가 인증 정보(ID, PW)로 인증을 요청한다.
  2. 요청을 받은 AuthenticationFilter가 UsernamePasswordAuthenticationToken(인증용 객체)을 생성한다.
    → 사용자가 보낸 아이디와 비번을 AuthenticationFilter가 받고 아이디와 비번을 토큰에 담는다.
  3. AuthenticationManger는 AuthenticationFilter로부터 인증용 객체를 전달받는다.
    → AuthenticationManger에게 ‘아이디와 비번이 담긴 토큰을 보낼테니 이걸로 회원가입을 한 유저인지 인증해줘’라고 떠넘기는 것
  4. AuthenticationManager는 List 형태로 AuthenticationProvider들을 가지고 있는데, 토큰을 처리할 수 있는 AuthenticationProvider를 선택하여 인증용 객체를 다시 위임한다.

  1. 인증 절차
    선택된 AuthenticationProvider는 DB에 있는 사용자 정보와 인증용 객체에 담긴 정보와 비교한다.
    1. supports() 메서드를 통해 실행 가능한지 확인한다.
    2. authenticate() 메서드를 통해 DB에 저장된 회원 정보와 입력한 사용자 인증 정보를 비교한다.
    1. DB 회원 정보: UserDetailsService의 loadUserByUsername() 메서드를 통해 불러온다.
    2. 입력한 사용자 정보: Authentication 객체
    3. 일치하는 경우 Authentication 객체를 반환한다.
  2. 인증 성공
    인증에 성공하게 되면 AuthenticationProvider에서 인증된 인증용 객체를 Authentication객체에 담아 AuthenticationManager를 거쳐 AuthenticationFilter로 전달한다.

  1. AuthenticationFilter는 전달받은 Authentication 객체를 SecurityContextHolder라는 곳에 담은 후 AuthenticationSuccessHandler을 실행한다. 인증 성공 시 AuthenticationSuccessHandler, 인증 실패 시 AuthenticationFailureHandler가 실행된다.
    → SecurityContextHolder는 1차캐시 역할을 담당한다. 이후 인증된 사용자가 다시 요청을 보내게 되면, 먼저 SecurityContext객체 안에 Authentication이 있는지 확인 후 있다면 바로 인과처리로 넘어감으로 인해 효율적인 처리가 된다.
profile
공부하는 감자😎

0개의 댓글