1. 개요


분산된 프로젝트 별로 통합 로그인 기능을 제공하고자 고민한 내용, 필요한 인증 관련 지식과 실제 구현 방법에 대해서 기술한다.

2. 배경


2.1. 인증(Authentication)인가(Authorization)

흔히 혼동되는 개념이다. 아래 내용을 통해서 이해하도록 하자. 

  • 인증: 사용자의 존재 여부를 확인하는 작업
    사용자가 입력한 계정 정보를 기반으로 실제 존재하는 계정인지 판단하는 과정을 예로 들 수 있다.

 

  • 인가: 사용자의 권한 소유 여부 확인하는 작업
    사용자가 특정 리소스에 접근하기 위해서 반드시 필요한 권한을 서버에 "요청"하는 과정을 예로 들 수 있다.

2.2. 기존 로그인 방법(세션)

기존에는 세션(Session)이라는 개념을 두어 로그인 등 사용자 기능이 구현되었다.

DB에 세션 정보를 넣어두고, 해당 세션이 실제 on/off 인지를 확인을 하는 형태이다.

 

2.3. 분산 환경에 적합한 로그인 방법

그런데 최근에는 쿠버네티스 기반의 MSA(Micro Service Architecture)가 적용된 시스템이라든지,
여러 종류의 서비스에 대해서 통합 로그인 서비스를 제공해야 되는 경우에는 위와 같은 세션 방식의 로그인이 적절하지 않을 수 있다.

(1) 너무 많은 서비스로부터의 요청량으로 인해 과부하가 발생할 수 있다.

(2) 클라이언트 디바이스 및 브라우저 등의 종류가 다양하여 일관된 포맷의 요청에 비해서 로직의 복잡도가 상승한다.

위와 같은 한계점을 극복하고자, JWT, OAuth 2.0 등 다양한 종류의 해결 방안이 강구되었다.

본 환경에서는 JWT를 적용하였다.

2.4. JWT(Json Web Token)

2.4.1. 개념

JWT란 Json 형식으로 된 특정 포맷의 토큰을 말한다.

세가지 종류의 데이터가 내장되어 있고, 특정 알고리즘과 key 값을 기반으로 암호화된 특징이 있다.

세가지 종류는 아래와 같다.

  • Header
    암호화 알고리즘 값과 타입이 지정되어 있다.
  • Payload
    인증(UserId?), 인가(UserRole?)에 관하여 필요한 정보를 내포하고 있는 부분이다.
  • Verify Signature
    암호화의 배경이되는 key 값이 저장되어있는 부분이다.
    이 부분에 저장된 secret key 값이, 인증 서버(Server)에서 보유하고 있는 값과 합치하는지 여부를 기준으로
    유효한 토큰인지 검증하게된다.

2.4.2. 장단점

  • 장점
    JWT 방식의 인증은 모든 세션 정보를 인증 서버에서 보관할 필요가 없으므로, 상대적으로 인증 서비스 운영이 가볍다는 이점이 있다.

 

  • 단점
    토큰을 탈취하면 클라이언트를 사칭할 수 있다. 토큰만 있으면 secret key 값을 제외하고는 조회가 가능하기 때문에, payload에 저장되는 정보는 노출될 수 있다.
    패스워드나 주민번호와 같은 민감 데이터는 가급적이면 payload에 담지 않는 것이 좋다.
    (payload는 단순히 암호화 알고리즘으로 해독가능?)

  • 보완책
    토큰의 만료 기한을 부여하고, refresh token을 발행한다. 

2.4.3. 참고

JWT에 대한 추가 정보 및 검증은 다음 사이트를 활용할 수 있다.

3. 설계


 

3.1. 서비스 프로세스

다음과 같은 프로세스로 구성된다.

(1) <<FE>> 로그인 서비스 UI를 이용하여 로그인한다. → 로그인 서비스 백엔드로 요청(/login) 발생

(2) <<BE>> 로그인 서비스 백엔드에서는 요청 받은 ID와 PW가 적합한지 확인한 후 결과 응답한다.

(3) <<BE>> 적합한 경우, 백엔드에서 사전에 정의된 secret key과 알고리즘을 기준으로 JWT를 발급한다.

(4) <<FE>> 사용자는 발급받은 JWT를 저장해두고, 연동 서비스 요청(Http Request Header에 Bear <Token> 형식)에 탑재하여 요청한다.

(5) <<BE>> 서비스 백엔드에서는  JWT가 정확한지 검증한다.

(6) 검증 결과 문제 없는 경우, 요청에 대한 응답을 정상적으로 제공한다.

(6)-1 검증 결과 문제 있는 경우, 요청에 대한 응답을 제공하지 않고 401 에러를 발생시킨다.

(7) <<FE>> 발급 기한이 초과하면 다시 로그인 서비스 UI를 이용하도록 리디렉션된다.

 

3.2. 필요 요소

JWT 인증을 가능케 하려면 다음 요소가 필요하다.

  • 로그인 서비스(백엔드)
    • ID, PW 관리 기능
    • JWT 발급 기능
    • JWT 검증 기능

  • 연동 대상 서비스
    • JWT 검증 요청 기능 (로그인 시 응답 받은 Token을 저장해두고, 요청 Header에 Bear 토큰 추가)

 

3.3. 구성도

4. 구현

4.1. 로그인 서비스

jsonwebtoken이라는 js 모듈을 사용하면 간단하게 구현가능하다.

npm i jsonwebtoken --save

 

해당 모듈의 가이드를 참조하여 아래의 함수들을 이용하였다.

var jwt = require('jsonwebtoken');

// jwt 발급 (서버내 jwt_secret은 환경변수로 관리할 것!)
var token = jwt.sign({ foo: 'bar' }, 'shhhhh');
 
var privateKey = fs.readFileSync('private.key');
var token = jwt.sign({ foo: 'bar' }, privateKey, { algorithm: 'RS256'}); // foo는 payload에 해당
 
jwt.sign({
exp: Math.floor(Date.now() / 1000) + (60 * 60),
data: 'foobar'
}, 'secret');

// jwt 검증
jwt.verify(token, 'shhhhh', function(err, decoded) {
console.log(decoded.foo) // bar
// payload 데이터 사용 가능
});
 

 

함수별 상세 스펙:

  • sign
    exp: 만료 기한
    data: payload
    'secret' : jwt secret key(기밀 중요)

  • verify
    token: jwt 값
    'shhhhh': secret key data 예시

4.2. 기존 서비스 - 검증 요청 기능

기존 서비스는 Java SpringBoot로 구현되어있어 아래와 같은 MVC 패턴 구현체 가이드를 참조하였다.

요구사항은 검증 요청 기능이 모든 api 요청에 대해서 적용이되어야한다는 점이었다.

filter, interceptor 두 가지 요소를 활용할 수 있었다.

이때 filter를 이용하는 것보다는 interceptor를 이용하면 경로 단위로 제어할 수 있다는 장점이 있기 때문에 Interceptor를 채택하였다.

따라서 interceptor를 구현하고, @prehandle 어노테이션을 부여하여 메소드를 구현할 수 있다.

 

 

 

5.  Troubleshooting


 

오히려 Json 관련된 부분보다는 브라우저와 Rest API에 관한 에러가 많았다.

(1) CORS Error

도메인 A로부터 로그인 서비스의 도메인으로 접근하다보니, 서로 다른 도메인에 대한 접근이기때문에 CORS Error를 자주 만날 수 있었다.

로그인 서비스의 백엔드에 cors handler module을 추가하고, 오리진을 허용함으로써 해결할 수 있었다.

 

(2) CORS Error 2

특정 케이스들은 origin을 허용하더라도 안되는 경우가 있었다.

Origin을 와일드카드를 이용하여 모두 오픈하는 경우에는, 쿠키와 같은 주요 정보를 다루는 복잡한 요청/응답을 브라우저에서 허용하지 않는다.

따라서, Origin을 명확하게 지정할 필요가 있다.

 

(3) Header Configuration

origin을 다 오픈을 하였는데도, 발급받은 JWT가 클라이언트단에서 자동 적용되지 않는 문제점이 발견되었다.

만약 내가 발급받은 JWT가 자동으로 브라우저 단에서 쿠키로 설정해줄 수 있도록 허용하려면, Set-Cookie 헤더를 요청 시 추가해야 한다.

 

(4) CORS Error 3

REST API 중 Get type의 요청에 대해서는 적용에 문제가 없음에도, Post, Put, Delete 등의 타입에 대해서는 동작되지 않는 경우가 있을 수 있다.

이 경우에는, Options method에 대한 cors 요청이 허용되지 않은 경우일 수 있으므로, 백엔드 단에서 cross-origin-allowed-methods 옵션에 "Options" 타입을 추가해줘야 한다

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기