gofillus 기술·운영 저널

Keycloak은 인증만, 사업장 권한은 영차가 판단한다

영차에서 Keycloak을 사용자 인증과 토큰 발급에 집중시키고, 사업장별 권한은 애플리케이션이 다시 확인하도록 나눈 이유를 정리했다. 멀티테넌트 서비스에서 JWT의 역할과 한계, 모바일 로그인 흐름, 토큰 갱신 기준도 함께 다룬다.

배경 — 로그인한 사람이 무엇이든 할 수 있는 것은 아니다

인증과 권한은 비슷해 보이지만 확인하는 질문이 다르다.

  • 인증: 이 요청을 보낸 사용자가 누구인가
  • 인가: 이 사용자가 지금 이 사업장에서 이 작업을 할 수 있는가

Keycloak은 첫 번째 질문에 잘 맞는다. 사용자를 확인하고 토큰을 발급하며, 서비스는 공개키로 토큰 서명을 검증한다.

하지만 영차에서는 한 사용자가 여러 사업장에 소속될 수 있다. 같은 사용자가 한 사업장에서는 OWNER이고, 다른 사업장에서는 MANAGER일 수 있다. 이 권한을 Keycloak의 전역 Role 하나로 표현하면 현재 선택한 사업장을 구분할 수 없다.

그래서 역할을 두 층으로 나눴다.

구분담당판단 기준
신원과 플랫폼 권한Keycloak로그인한 사용자, 토큰의 기본 Role
사업장 업무 권한영차 BE현재 사업장과 사용자의 소속·등급

Keycloak을 덜 쓰는 설계가 목적은 아니다. 인증 서버가 잘하는 일과 업무 서비스가 알아야 하는 일을 분리하는 것이 목적이다.

선택 — Keycloak Role에 사업장 권한을 모두 넣지 않은 이유

가능한 방법은 세 가지였다.

  1. Keycloak Role에 모든 업무 권한을 넣는다.
  2. 매 요청마다 Keycloak 관리 API에 권한을 물어본다.
  3. Keycloak은 인증을 맡기고, 업무 권한은 영차가 판정한다.

첫 번째 방법은 사업장 수가 늘어날수록 Role이 사용자의 소속 상태를 따라가야 한다. 한 사용자가 사업장을 전환할 때 토큰을 다시 발급해야 하고, 토큰 안의 권한과 실제 소속 정보가 어긋날 수 있다.

두 번째 방법은 요청마다 인증 서버에 의존한다. 인증 서버의 지연이나 장애가 업무 API의 지연과 장애로 이어진다. 권한 확인을 위해 외부 관리 API를 호출하는 것도 인증 경계를 넓히는 일이다.

세 번째 방법은 JWT로 사용자를 확인한 뒤, 활성 사업장과 소속 정보를 애플리케이션이 확인한다. 사업장 권한의 정본이 업무 데이터 안에 남고, 서비스가 업무 규칙을 직접 적용할 수 있다.

적용 — Keycloak은 신원 토큰을 발급한다

영차의 모바일 로그인 흐름은 앱이 Keycloak 화면을 직접 여는 방식이 아니다. 모바일 앱은 인증 전용 BFF에 로그인 요청을 보내고, BFF가 Keycloak과 통신한다.

모바일 앱
   │ 로그인·갱신 요청
   ▼
인증 BFF
   │ 사용자 인증·토큰 발급
   ▼
Keycloak
   │ access token·refresh token
   ▼
인증 BFF → 모바일 앱

앱은 refresh token을 운영체제 보안 저장소에 보관하고, access token은 API 요청에 사용한다. 인증 BFF는 민감한 클라이언트 자격을 앱에 넣지 않는다. 외부 브라우저 기반 로그인 대신 앱의 로그인 화면과 BFF를 연결한 이유도 이 경계를 단순하게 유지하기 위해서다.

BE는 JWT를 직접 검증한다

게이트웨이와 내부 BE는 매 요청마다 Keycloak에 토큰을 보내 유효성을 묻지 않는다. Keycloak이 공개한 JWKS를 사용해 JWT 서명을 자체 검증하고, 검증된 사용자를 보안 컨텍스트에 넣는다.

요청
  │ Authorization: Bearer <JWT>
  ▼
게이트웨이·BE
  ├─ 서명 검증
  ├─ 만료 시각 확인
  ├─ 발급자·대상 확인
  └─ 사용자 식별자와 기본 Role 추출

이 방식은 인증 서버에 요청마다 왕복하지 않는다. 공개키 캐시가 잠시 남더라도 토큰의 만료와 서명 검증은 각 서비스가 수행한다. 내부 서비스는 외부 공개 경계보다 긴 토큰 유예 시간을 사용할 수 있지만, 사용자 요청을 받는 외부 경계에서는 만료 기준을 엄격하게 적용한다.

업무 권한은 활성 사업장으로 다시 확인한다

JWT에서 사용자를 식별한 뒤, 요청의 활성 사업장과 해당 사용자의 소속을 확인한다.

JWT의 사용자 식별자
        +
요청의 활성 사업장
        │
        ▼
현재 소속 조회
        │
        ├─ 소속 없음       → 거부
        ├─ 사업장 불일치   → 거부
        ├─ 등급 부족       → 거부
        └─ 조건 만족       → 업무 처리

화면에서 관리자 메뉴를 숨기는 것은 사용성을 위한 처리다. 실제 보호 장치는 서버의 권한 검사다. 앱이 위조된 요청을 보내거나 API를 직접 호출해도 서버가 현재 소속과 등급을 다시 확인해야 한다.

사업장 전환도 같은 원칙을 따른다. 앱이 활성 사업장을 바꾸면 이후 요청에 그 사업장 정보가 전달되고, 서버는 JWT의 사용자와 요청의 사업장을 조합해 다시 소속을 확인한다. 토큰 안에 모든 사업장 권한을 오래 넣어 두지 않는 이유다.

토큰 수명과 로그아웃

access token은 짧게 사용하고 refresh token으로 새 access token을 발급한다. refresh token은 긴 수명을 가지지만 한 번 발급된 값을 영원히 신뢰하지 않는다.

  • refresh token rotation을 사용한다.
  • 재사용된 refresh token을 감지한다.
  • 갱신 실패 시 앱의 인증 상태를 초기화한다.
  • 로그아웃 때 앱의 토큰을 지우고 인증 서버의 세션도 종료한다.

access token을 길게 유지하면 탈취된 토큰의 사용 시간이 늘어난다. 반대로 너무 짧게 만들면 갱신 요청이 자주 발생한다. 모바일 앱의 네트워크 환경과 사용 흐름을 기준으로 두 토큰의 수명을 분리했다.

회원가입과 계정 변경의 경계

영차의 회원가입은 Keycloak 가입 화면을 그대로 노출하는 흐름이 아니다. 앱의 가입 절차가 먼저 동작하고, 서버가 입력값·인증번호·중복 여부·사업장 규칙을 확인한 뒤 Keycloak 계정을 만든다.

전화번호나 이메일 변경도 같은 경계를 지킨다. Keycloak의 사용자 속성을 바로 수정하는 것이 아니라 영차 서버가 재인증과 중복 검사를 통과시킨 뒤 인증 서버에 반영한다.

이렇게 해야 다음 규칙을 한 곳에서 지킬 수 있다.

  • 인증번호의 용도와 만료 시간
  • 연락처 중복 여부
  • 탈퇴 계정의 재사용 정책
  • 애플리케이션 사용자 정보와 인증 계정의 연결

인증 서버를 정본으로 삼으면 이런 업무 규칙을 Keycloak 설정과 확장 코드에 다시 넣어야 한다. 현재 구조에서는 애플리케이션이 업무 데이터의 정본이고, Keycloak은 로그인에 필요한 자격을 보관한다.

실수한 것들

전역 Role로 사업장 등급까지 표현하려 했다

처음에는 JWT의 Role만 보면 관리자인지 알 수 있다고 생각하기 쉽다. 그러나 사용자가 여러 사업장에 속하면 Role은 어느 사업장의 관리자라는 뜻을 담지 못한다.

전역 Role은 플랫폼 수준의 권한에만 사용하고, 사업장 등급은 활성 사업장과 소속 정보로 판정하는 규칙을 분리했다.

매 요청마다 Keycloak에 권한을 물어보려 했다

인증 서버가 살아 있는지 확인할 수 있다는 장점은 있지만, 모든 업무 요청이 인증 서버의 지연과 장애에 묶인다. 애플리케이션이 이미 보유한 소속 정보를 다시 원격으로 확인하는 셈이기도 하다.

BE가 JWT 서명을 직접 검증하고, 업무 권한은 로컬 소속 정보로 확인하는 방식으로 바꿨다. 인증 서버는 로그인과 토큰 발급 경로에 집중한다.

클라이언트의 메뉴 제어를 권한 검사로 착각했다

관리자 메뉴가 보이지 않으면 안전하다고 생각하기 쉽다. 하지만 모바일 앱은 수정할 수 있고 API 요청은 직접 만들 수 있다.

화면 제어는 편의 기능으로만 두고, 모든 보호 API에는 서버 권한 검사를 둔다. 인증된 사용자라도 현재 사업장과 등급이 맞지 않으면 처리하지 않는다.

인증 서버와 애플리케이션의 계정을 따로 관리했다

가입·탈퇴·연락처 변경을 두 시스템에서 각각 처리하면 한쪽만 성공하는 구간이 생긴다. 이후 로그인은 되지만 애플리케이션 사용자는 없거나, 애플리케이션에서는 탈퇴했는데 인증 계정이 남는 문제가 생긴다.

계정 변경의 진입점을 영차 서버로 모으고, 인증 서버 변경은 그 흐름 안에서 수행한다. 탈퇴처럼 실패를 남기면 안 되는 작업은 별도의 재시도와 정리 기준도 함께 둔다.

결과

항목단일 인증·권한 모델영차의 분리 모델
사용자 인증인증 서버Keycloak
토큰 검증인증 서버 왕복 가능각 BE가 JWT 자체 검증
사업장 권한전역 Role에 의존활성 사업장과 소속으로 판정
모바일 로그인앱이 인증 서버와 직접 결합인증 BFF가 중계
화면 권한메뉴 숨김 중심화면 제어 + 서버 재검증
계정 변경인증 서버와 앱에서 분산 처리영차 서버가 업무 규칙을 통제

남는 것

Keycloak은 인증을 대신해 주지만, 서비스의 모든 권한 모델을 대신 설계해 주지는 않는다. 특히 멀티테넌트 서비스에서는 “누구인가”와 “어느 사업장에서 무엇을 할 수 있는가”를 분리해야 한다.

영차에서 정한 기준은 단순하다.

  1. Keycloak은 신원 확인과 토큰 발급을 맡긴다.
  2. BE는 토큰을 직접 검증해 인증 서버 왕복을 줄인다.
  3. 사업장 권한은 활성 사업장과 애플리케이션 소속 정보로 판정한다.
  4. 클라이언트의 화면 제어를 서버 권한 검사의 대체물로 쓰지 않는다.

인증 서버를 중심에 두되, 업무 권한까지 인증 서버에 몰아넣지는 않는다. 이 경계를 지키면 사업장과 모듈이 늘어도 권한 모델을 다시 만들지 않고 확장할 수 있다.


← 목록으로