백엔드 개발

[Tech] 인증과 인가에 대한 이해

Razelo 2023. 4. 12. 23:19

인증, 인가에 대해서 간략하게 정리해보고자 한다. 

 

내용이 꽤 많고 사용해본 지 시간이 꽤 흘러서 간단하게 정리해보겠다. 


인증과 인가

  • 시스템의 자원을 적절하고 유효한 사용자에게 전달하고 공개하는 방법이다. 
  • 인증은 Authentication 즉 로그인이다. 
  • 인가는 Authorization 즉 인증 작업 이후 행해지는 작업이다. 인증된 사용자에 대한 자원에 대한 접근 확인 절차를 의미한다. 
  • 유저는 관리자 페이지에 접속할 수 없다. 왜냐면 관리자 페이지에 인가되어 있지 않기 때문이다. 

 

세션 기반 인증

세션 기반 인증은 쿠키와 세션과 관련된 내용이다. 

https://hackernoon.com/using-session-cookies-vs-jwt-for-authentication-sd2v3vci

세션 기반 인가는 사용자 인증 정보가 서버 세션 저장소에 저장된다. 

 

사용자가 로그인하면 인증 정보를 서버 세션  저장소에 저장하고 사용자에게 저장된 세션 정보인 Session ID를 발급한다. 발급된 Session ID는 브라우저에 쿠키 형태로 저장되지만 인증 정보는 서버에 저장되있다. 브라우저는 이후 요청마다 HTTP Cookie 헤더에 Session ID를 함께 서버로 전송한다. 서버는 해당 요청을 받고 Session ID에 해당하는 세션 정보가 세션 저장소에 존재하면 인증된 사용자로 판단한다. 

 

토큰 기반 인증 

https://hackernoon.com/using-session-cookies-vs-jwt-for-authentication-sd2v3vci

토큰 기반 인증은 인증 정보를 클라이언트가 직접 들고 있다. HTTP의 Authorization헤더에 토큰을 넣어서 보낸다. 

 

세션 방식 vs 토큰 방식

  • 세션은 Cookie 헤더에 세션 ID만 실어서 보내면 되서 트래픽이 적다. 
  • 토큰 방식인 JWT는 사용자 인증 정보, 토큰 발급 시간, 만료 시각, 토큰 ID등이 있어서 트래픽이 크다. 
  • 세션이 더 안전하다. 모든 인증 정보를 서버에서 관리하기 때문에 세션 ID가 탈취되면 서버측에서 해당 세션을 무효 처리하면 된다. 
  • 토큰은 한번 탈취되면 막기 어렵다. 따라서 상황에 맞게 적절하게 사용하면 된다. 

 

최근 어플리케이션들은 왜 토큰 기반 인증을 쓸까? 

확장성 때문에 그렇다. 웹 어플리케이션의 서버 확장 방식은 수평 확장을 주로 쓴다. 

이때 세션 기반 인증 방식은 세션 불일치 문제를 겪는다. 그래서 이것 때문에 Sticky Session, Session Clustering, 세션 스토리지 외부 분리 등 작업을 해야 한다. 그런데 토큰 방식은 이 세션 불일치 문제랑 관련이 없다. 그래서 확장성이 높다고 말한다. 또한 세션 기반 인증 방식은 세션 데이터가 많아질 수록 서버 부담이 증가한다. 

 

다중 서버 환경에서 세션 불일치란? 

기본적으로 세션은 서버 메모리에 저장된다. 다중 서버 환경에서는 세션 불일치 문제가 발생할 수 있다.

세션 불일치 문제 해결책 

Sticky Session 

클라이언트 요청이 항상 해당 클라이언트 세션이 저장된 서버로 향하게 만든다. 특정 서버로 고정시키는 방법은 쿠키를 사용하거나 클라이언트의 IP로 판단할 수 있다. 그런데 이건 문제점이 있다. 특정 서버에 트래픽이 집중될 위험성이 있다. 즉 로드밸런서의 원래 목적이 훼손된다. 한 서버가 장애가 발생하면 해당 서버의 세션 정보가 모두 유실되서 해당 서버에 Sticky된 유저는 다시 로그인해야 한다. 

 

Session Clustering 

특정 서버에 세션이 생성될 때마다 다른 서버로 세션을 전파해서 복제한다. 비효율적인 메모리 관리 문제가 있다. 세션 생성 및 전파, 복제 과정에서 Sticky Session보다 많은 트래픽을 사용한다. 세션 전파 복제 과정에서 조차 시간차 때문에 세션 불일치 이슈가 생길 수 있다. 

 

세션 스토리지 분리 

세션 스토리지를 외부로 분리한다. Disk Based DB는 느려서 잘 쓰이지 않는다고 한다. 그래서 In Memory DB를 주로 쓴다. 왜냐면 세션 정보는 영속성을 보장할 필요가 없어서 그렇다. Sticky Session 문제인 부하 몰림 문제, 가용성 문제가 해결되고 Session Clustering의 메모리 관리, 트래픽 증가, 시간차 세션 불일치 문제도 해결하지만 하나 뿐인 세션 스토리지에 장애 발생하면 모든 서버에서 세션 데이터를 쓸 수 없다. 


서버 기반 인증 

  • 세션 생성 기반
  • 서버에서 관리하기 때문에 단일 장애 지점을 갖게 된다. 
  • 여러 대의 서버를 사용하더라도 세션 데이터 불일치 문제가 발생할 수 있다. 그래서 유저가 서비스 사용 중에 로그인이 풀리는 경험을 겪을 수 있다. 
  • 운용중인 모든 서버 컴퓨터에서 유저의 세션 ID를 모두 공유해야 한다. 
  • 서버 메모리 위에 저장된다. 
  • 메모리에 저장하는 이유는 빠른 액세스가 가능하고 간단한 방법이기 때문이다. 
  • 하지만 메모리에 저장하면 확장성 이슈를 겪을 수 있다고 한다. in memory storage는 확장성에 좋지 않다고 한다. 
  • persistence 측면에서 좋지 않을 수 있다. 
  • Disk에 저장하는 경우는 반대의 장단점을 갖고 있다. 
  • Redis의 경우는 in-memory이면서도 persistence를 제공하기 때문에 많이 쓰이는 것이다. 

토큰 기반 인증

  • 유저 정보를 서버에 저장하지 않는다. 
  • 유저가 로그인 성공하면 클라이언트로 토큰을 발급한다. 
  • 클라는 토큰을 저장하고 서버에 요청할 때 HTTP header에 실어서 전송한다.
  • 서버는 이를 검증한다.
  • 이후 유저를 인가한다. 
  • 서버는 발급, 검증 외에는 정보를 갖지 않는다. 
  • 수평 확장 환경에서 여러 대 서버가 유저에 대한 정보를 기억할 필요 없다는 뜻이다. 
  • JWT에는 토큰 자체에 정보가 저장될 수 있기 때문에 절대 민감한 정보를 담으면 안된다. 
  • 토큰은 세션 ID에 비해서 사이즈가 크다. 그래서 오버헤드가 있다는 걸 알고 써야한다. 
  • JWT대신 랜덤 문자열 토큰 기반 인증도 가능하지만 만료 시각 정보를 담을 수가 없다. 즉 만료시킬 수 없다. 
  • JWT는 데이터를 직접 갖는 Clain 기반 토큰이라서 만료를 구현할 수 있다. 

JWT

  • 헤더, 페이로드, 서명으로 구분된다.
  • "헤더.페이로드.서명" 이런 형식으로 JSON으로 표현한다. 
  • 최종적으로 Base64로 인코딩한다. 

헤더 

  • 토큰 유형, 알고리즘을 담음 
  • alg는 주로 HMAC SHA256, RSA가 사용된다. 이 alg가 이후 서명에 사용된다. 

페이로드 

  • 사용자 정보, 데이터 속성 나타내는 클레임이라는 정보 단위로 구성된다. 
  • 클레임에는 3가지 구분이 있음 

등록된 클레임 (Registerd Claim)

  • 7개 클레임 정의 
  • 선택적임
  • 토큰 사이즈를 작게 하려고 이름이 3글자로 축약됨 
  • iss: Issuer: 토큰 발급자 
  • sub: Subject: 토큰 제목
  • aud: Audience: 토큰 대상자 
  • exp: Expiration Time. 토큰 만료 시각 
  • nbf: Not Before. 토큰 활성 시각 
  • iat: issued At. 토큰 발급된 시각 
  • jti: JWT ID, 즉 JWT 식별자 

공개 클레임 (Public Claim)

  • JWT 사용하는 사람들에 의해 정의되는 클레임, 
  • 단순 서버 클라 사용자 인증 용도면 신경안써도 됨 

비공개 클레임 (Private Claim)

  • 서버 클라 사이에서만 협의된 클레임 

서명 

  • 암호화 알고리즘 사용해서 Base64 인코딩된 헤더와 Base64 인코딩된 페이로드, 비밀키를 이용해서 암호화 
  • 서버는 서명을 통해 헤더, 페이로드가 변조되었는지 무결성 검증함 

 

JWT 사용 시 주의사항

  • JWT는 stateless 
  • 토큰 자체 만료일을 토큰 자신이 가짐
  • 토큰이 탈취되어도 서버에서는 할 수 있는 조치가 없음
  • 이미 발행된 토큰에 대해서 서버는 아무런 제어를 할 수 없음 
  • 그래서 Access Token과 Refresh Token을 함께 사용하면서 Access Token의 만료 기간을 매우 짧게 가져감 
  • 그러나 Refresh Token도 탈취되면 추가적인 조치가 필요함

OAuth2

연동되는 외부 웹 어플리케이션에서 Google, kakao, facebook등이 제공하는 기능을 간편하게 쓰기 위함이다.

OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는 접근 위임을 위한 개방평 표준이다. 

구분 설명
Resource Owner  서비스를 이용하려는 유저 
리소스는 구글 캘린더 정보, 페이스북 친구 목록 등이 될 수 있다. 
Cllient 애플리케이션 서버 (개인 혹은 회사가 만든 애플리케이션)
Resource Server의 자원을 이용하고자 하는 서비스다. 

Authorization Server 권한을 부여해주는 서버 
사용자는 이 서버로 ID, PW를 넘겨 Authorization code를 발급 받음 
Client는 이 서버로 Authorization Code를 넘겨서 Token을 발급 받음 
Resource Server  사용자의 개인 정보를 가지고 있는 애플리케이션 회사 서버 
(Google, Kakao, Naver)
Client는 Token을 이 서버로 넘겨서 개인 정보를 응답 받음 
Access Token  자원에 대한 접근 권한을 Resource Onwer가 인가했음을 나타내는 자격증명 
Refresh Token  Client는 Authorization Server로부터 Access Token과 Refresh Token을 부여받음
refresh token을 통해 access token을 재발급 받아서 로그인 할 필요없게 만듬 

 

 

소셜 미디어로 로그인하면 Client는 Resource Owner 대신해서 로그인한다. 이때 필요한 정보를 Resource Server에서 얻어서 비교해서 유효성을 판단한다. 

Client가 유저의 (로그인)정보/자원(resource)를 Resource Server에 요청해서 대신 로그인한다. 

 

왜 Authorization Code가 필요할까? 

Authorization Code를 발급하지 않고 곧장 Client에게 Access Token을 발급해주면 왜 안될까? 

Redirect URI를 통해 Authorization Code를 발급하는 과정이 생략되면 Authorization Server가 Access Token을 Client에 전달하기 위해 Redirect URI를 통해서 줘야 한다. 이떄 Redirect URI를 통해서 데이터를 전달할 수 있는 방법은 URL 자체에 데이터를 실어서 전달하는 방법밖에는 없어서 브라우저를 통해 데이터가 노출되기 때문이다. 

Access Token은 민감한 데이터기 때문에 그래선 안된다고 한다. 

Authorization Code는 Client가 Token을 획득하기 위해 사용하는 임시 코드이다. 그래서 일반적으로 1~10분 정도로 수명이 짧다. 

Access Tokne은 유출되어선 안되기에 HTTPS 연결을 통해서만 사용 가능하다. 

 

OAuth1.0 

  • OAuth2.0은 1.0에 비해 클라이언트 복잡성을 간소화했다. 
  • OAuth1.0에서는 암호학적 기법을 강조했었다. 이를 위해 서명할 원문이 필요했다. 
  • URI 등 파라미터, 파라미터 순서 혹은 뭘 넣을지 등등 어떤 순서로 정렬할지에 대한 정확한 분류가 필요했다. 
  • 이 문제를 OAuth2.0은 Bearer Token + TLS를 통해 해결했다. 
  • 암호학적 보호등을 배제하고 토큰을 소유하고 있는 것만으로도 즉  토큰에 대한 사용 권한이 있음을 인정해주는 토큰을 Bearer Token이라고 부른다. 또한 2.0은 TLS 즉 HTTPS를 강제한다. 

 

OAuth1.0의 역할 

  • 리소스 오너 인증
  • 인가 토큰 발급
  • 보호된 리소스 관리

마지막 역할이 좀 애매했다. 

그래서 2.0에서는 Oauth2.0 authz server가 리소스 오너 인증, 인가 토큰 발급, OAuth2.0 resource server가 보호된 리소스를 관리하도록 역할을 분리해서 아키텍쳐를 개선했다. 

 

1.0에서는 토큰 유효기간이 길었다. 6개월 ~ 1년이었다. 즉 탈취되면 지속적으로 어뷰징이 가능하다는 뜻이었다. 그래서 2.0은 리프레시 토큰을 도입했다. 리소스 접근할때는 access token을 사용하지만 유효기간이 짧았다. 대신 리프레시 토큰으로 새로운 access token을 받는 방법이었다. 즉 access token이 탈취되도 짧은 기간 밖에 어뷰징을 할 수 없기 때문이다. 

 

1.0은 웹 환경에서만 동작할 수 있었다. 2.0은 grant라는 개념을 추가했다. 여러 사용환경에 대한 플로우를 나타낼 수 있게 한 것이다. 

grant로는 authorization code, implicit, resource owner password credentials, client credentials가 있다. 

authorization code은 1.0과 같은 플로우다. 서버 to 서버 통신이다.

새로 추가된 implicit grant는 웹브라우저의 js기반 클라와 같이 리소스

 resource owner password credentials는 직접 유저의 ID와 비밀번호를 받아서 클라가 요청을 하는 플로우다.  이건 유저 기기 os와 같이 믿을 수 있는 환경에서만 사용한다.  

 

OAuth2.1

  • 2.0이 인기를 끌어서 클라이언트 애플리케이션들이 많이 늘어났다 
  • 금융권, 의료계, 증권 시장 등 민감한 클라이언트가 많이 늘어났다. 
  • 그래서 이런 클라이언트의 다양성을 충족하기 위해 RFC 등을 추가하기 시작해서 스펙이 엄청 늘어났다. 
  • 그래서 이런 스펙의 통합과 모범 적용 사례 표준화가 2.1이다. 
  • 2.1에는 device authorization grant가 추가되어 IoT 디바이스에도 대응할 수 있게 나왔다. 
  • refresh token이 1회성으로 변했다.
  • refresh token으로 access token을 받으면 refresh token도 새로 받는다. 

Google OAuth2 사용법

  1. GCP에서 새로운 프로젝트 생성
  2. 프로젝트 대시보드로 들어간다. 
  3. 대시모드 좌측의 API 및 서비스 클릭 
  4. 메뉴에서 OAuth 동의화면 클릭
  5.  앱 정보, 앱 도메인, 개발자 연락처 정보 등 기입 
  6. 저장 후 계속을 누른다. 
  7. 이후 범위에 대해 명시해준다. email을 받아올 건지, 공개된 개인 정보를 받아올 건지 등등 설정해준다. 
  8. 저장 후 계속을 누른다. 
  9. 테스트 사용자 설정 화면이 나온다. (게시 상태를 테스트로 했을때 테스트 사용자에 추가된 사람만 접근 가능, 주로 개발 팀원을 등록하면 된다.)
  10. 완료되었으면 좌측에서 사용자 인증 정보를 누른다. 
  11. 이후 사용자 인증 정보 만들기에서 OAuth2 클라이언트 ID만들기를 누른다. 
  12. 리다이렉트 URL 등을 입력해주고 만들어준다. 

GNAP

  • 인증/인가의 미래로 언급 
  • OAuth2.0 기반이 전혀 아님
  • 하위호환성 고려 X 
  • OAuth2.0 스코프보다 더욱 정밀한 접근 범위 정밀 제어가 가능
  • DID와 같은 아직 표준화되지 않은 미래 기술에 대한 확장된 스펙을 염두 

아래 링크에서 많은 도움을 얻었습니다. 

 

감사합니다. 

 

Oauth2 

https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-OAuth-20-%EA%B0%9C%EB%85%90-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC

https://www.youtube.com/watch?v=-YbqW-pqt3w 

https://hudi.blog/oauth-2.0/

jwt 

https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-JWTjson-web-token-%EB%9E%80-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC

https://hudi.blog/self-made-jwt/

GCP에서 google oauth2 설정하기 

https://velog.io/@tkdfo93/%EA%B5%AC%EA%B8%80-OAuth2.0-Final-Project

최신 postman 테스트 (작동 O)

https://hmh.engineering/how-to-get-oauth-access-token-and-retrieve-data-from-google-apis-using-postman-9a95ffe030ae

세션 기반 인증과 토큰 기반 인증 

https://hudi.blog/session-based-auth-vs-token-based-auth/

왜 JWT가 별로일까? 

https://developer.okta.com/blog/2017/08/17/why-jwts-suck-as-session-tokens

다중 서버 환경에서 세션 불일치 문제와 해결방법

https://hudi.blog/session-consistency-issue/

access token 문제점 & Refresh Token 

https://hudi.blog/refresh-token/

google api scope 

https://developers.google.com/identity/protocols/oauth2/scopes?hl=ko 

OAuth2 취약점 

https://meetup.nhncloud.com/posts/105

OAuth2의 미래 

https://www.youtube.com/watch?v=DQFv0AxTEgM 

https://forward.nhn.com/2022/sessions/17 

https://datatracker.ietf.org/doc/html/rfc6749

kotlin + springboot + google oauth2 
https://kingofbackend.tistory.com/227

구현

https://forward.nhn.com/2022/sessions/17

반응형