Token Authentication vs. OAuth 2.0

토큰 인증(Token Authentication)과 OAuth 2.0은 모두 사용자 인증 및 권한 부여를 처리하는 기술이지만, 목적과 구현 방식에 있어 중요한 차이점을 가지고 있다.

토큰 인증(Token Authentication)

토큰 인증은 사용자가 자격 증명(일반적으로 사용자 이름과 비밀번호)을 한 번 제공한 후, 서버가 생성한 토큰을 사용하여 이후의 요청에서 인증을 수행하는 방식이다.

기본 개념

토큰 인증의 핵심 아이디어는 사용자의 자격 증명을 한 번만 확인한 후, 서버가 서명된 토큰을 발급하여 클라이언트가 이 토큰을 사용해 자신을 인증하도록 하는 것이다. 가장 널리 사용되는 토큰 형식은 JWT(JSON Web Token)이다.

작동 방식

  1. 로그인 및 토큰 발급:
    • 사용자가 사용자 이름과 비밀번호를 제출한다.
    • 서버는 이 자격 증명을 검증하고, 유효할 경우 토큰을 생성한다.
    • 서버는 토큰을 클라이언트에게 반환한다.
  2. 요청 인증:
    • 클라이언트는 토큰을 로컬에 저장한다(로컬 스토리지, 쿠키 등).
    • 이후 요청 시 클라이언트는 HTTP 헤더(일반적으로 Authorization 헤더)에 토큰을 포함시킨다.
    • 서버는 토큰을 검증하고, 유효할 경우 요청을 처리한다.

특징

  • 무상태(Stateless): 서버는 클라이언트 상태를 저장할 필요가 없다.
  • 확장성: 여러 서버 간에 인증 정보를 공유할 필요가 없다.
  • 자가 포함적(Self-contained): 토큰 자체에 필요한 정보가 포함될 수 있다.
  • 간단한 구현: 기본적인 토큰 인증은 구현이 상대적으로 간단하다.

OAuth 2.0

OAuth 2.0은 인터넷 사용자가 자신의 정보에 대한 접근 권한을 제3자 애플리케이션에 부여할 수 있도록 하는 권한 부여 프레임워크이다.

기본 개념

OAuth 2.0의 핵심 아이디어는 사용자(리소스 소유자)가 비밀번호를 공유하지 않고도 제3자 애플리케이션(클라이언트)에게 자신의 데이터에 대한 특정 접근 권한을 부여할 수 있도록 하는 것이다.

작동 방식

OAuth 2.0은 여러 권한 부여 흐름을 지원하며, 가장 일반적인 것은 권한 부여 코드 흐름(Authorization Code Flow)이다:

  1. 초기 요청 및 인증:
    • 클라이언트 애플리케이션은 사용자를 권한 부여 서버로 리디렉션한다.
    • 사용자는 권한 부여 서버에 로그인하고 요청된 권한을 승인한다.
  2. 권한 부여 코드 발급:
    • 권한 부여 서버는 권한 부여 코드와 함께 사용자를 클라이언트로 리디렉션한다.
  3. 토큰 교환:
    • 클라이언트는 권한 부여 코드를 권한 부여 서버에 제출하여 접근 토큰(및 선택적으로 리프레시 토큰)을 받는다.
  4. 자원 접근:
    • 클라이언트는 접근 토큰을 사용하여 리소스 서버의 보호된 리소스에 접근한다.

특징

  • 위임된 권한 부여: 사용자는 제3자 애플리케이션에 제한된 접근 권한을 부여한다.
  • 범위(Scopes): 접근 권한의 세분화된 제어를 제공한다.
  • 여러 흐름: 다양한 클라이언트 유형에 맞는 여러 권한 부여 흐름을 지원한다.
  • 표준화: 널리 채택된 개방형 표준.

심층 분석

주요 차이점

  1. 목적과 용도:
    • 토큰 인증은 주로 사용자를 인증하는 데 중점을 둔다.
    • OAuth 2.0은 제3자 애플리케이션에 특정 리소스에 대한 접근 권한을 부여하는 데 중점을 둔다.
  2. 접근 방식:
    • 토큰 인증은 단순히 “이 사용자가 누구인지” 확인한다.
    • OAuth 2.0은 “이 애플리케이션이 특정 사용자 데이터에 접근할 수 있는지"를 결정한다.
  3. 복잡성과 오버헤드:
    • 토큰 인증은 일반적으로 구현이 간단하고 오버헤드가 적다.
    • OAuth 2.0은 더 복잡하며, 여러 참여자와 단계가 포함되어 있다.

토큰 인증의 장단점

장점:

  • 구현이 상대적으로 단순하다.
  • 서버 측 세션 상태를 유지할 필요가 없어 확장성이 좋다.
  • 개발 오버헤드가 적다.
  • 단일 서비스 인증에 효율적이다.

단점:

  • 제3자 애플리케이션 통합에 적합하지 않을 수 있다.
  • 세밀한 권한 제어가 기본적으로 포함되어 있지 않다.
  • 토큰 취소 메커니즘이 표준화되어 있지 않다.
  • 보안 구현이 개발자에게 더 많이 의존한다.

OAuth 2.0의 장단점

장점:

  • 사용자 비밀번호 공유 없이 제3자 애플리케이션에 접근 권한을 부여할 수 있다.
  • 세밀한 권한 범위(scopes)를 통해 최소 권한 원칙을 적용할 수 있다.
  • 표준화되어 있어 다양한 서비스 간 호환성이 좋다.
  • 강력한 보안 기능이 내장되어 있다.

단점:

  • 구현 복잡성이 높다.
  • 설정 및 유지 관리에 더 많은 노력이 필요하다.
  • 단순한 인증 요구사항에는 과도할 수 있다.
  • 사용자 경험에 추가 단계가 필요하다(권한 동의 화면 등).

사용 시나리오

토큰 인증이 적합한 경우

  • 단일 서비스 내 사용자 인증
  • 간단한 모바일 애플리케이션 API
  • 마이크로서비스 간 간단한 인증
  • 개발 시간과 복잡성을 최소화해야 하는 프로젝트

OAuth 2.0이 적합한 경우

  • 소셜 로그인 구현(“구글로 로그인”, “페이스북으로 로그인” 등)
  • 제3자 애플리케이션에 제한된 접근 권한 부여
  • API 마켓플레이스 및 개발자 플랫폼
  • 다양한 서비스와 통합이 필요한 엔터프라이즈 환경
  • 사용자 데이터에 대한 세밀한 접근 제어가 필요한 경우

하이브리드 접근법

실제 애플리케이션에서는 두 방식을 조합하여 사용하는 경우가 많다:

  1. OAuth 2.0으로 인증 후 토큰 인증 사용:
    • OAuth 2.0을 통해 사용자를 인증(소셜 로그인 등)
    • 인증 후 서비스 자체의 토큰(JWT 등)을 발급하여 내부 API 호출에 사용
  2. 내부 서비스에는 토큰 인증, 외부 API에는 OAuth 2.0:
    • 자체 서비스 내에서는 간단한 토큰 인증 사용
    • 외부 서비스 API 호출 시 OAuth 2.0 사용
  3. OAuth 2.0 + JWT:
    • OAuth 2.0 프로토콜을 사용하되 JWT 형식의 접근 토큰 발급
    • 토큰 인증의 자체 포함적 특성과 OAuth의 권한 부여 기능 결합

보안 고려사항

토큰 인증 보안

  1. 안전한 토큰 관리:
    • HTTPS 사용
    • 토큰 만료 시간 설정
    • 안전한 저장소 사용(HttpOnly 쿠키 등)
  2. 취약점 방지:
    • CSRF 공격 대응
    • XSS 공격 방지
    • 토큰 탈취 위험 최소화

OAuth 2.0 보안

  1. 올바른 흐름 선택:
    • 클라이언트 유형에 맞는 권한 부여 흐름 사용
    • 공개 클라이언트는 PKCE(Proof Key for Code Exchange) 사용
  2. 보안 매개변수:
    • 상태 파라미터 사용(CSRF 방지)
    • 리디렉션 URI 검증
    • 범위(scopes) 제한
  3. 토큰 보안:
    • 짧은 접근 토큰 수명
    • 리프레시 토큰 안전한 저장
    • 토큰 교환 시 클라이언트 인증

최신 동향

  1. 토큰 인증 발전:
    • JWT 보안 강화
    • 토큰 바인딩(Token Binding)
    • DPoP(Demonstration of Proof-of-Possession)
  2. OAuth 2.0 발전:
    • OAuth 2.1 초안
    • FAPI(Financial-grade API)
    • 리소스 지시자(Resource Indicators)
  3. 통합 솔루션:
    • OpenID Connect(OAuth 2.0 위에 구축된 인증 계층)
    • 자체 발급 OpenID Provider(SIOP)
    • 디바이스 권한 부여 흐름

토큰 인증 vs. OAuth 2.0 비교

특성토큰 인증OAuth 2.0
주요 목적사용자 인증(Authentication)권한 부여(Authorization)
사용 사례단일 서비스 내 사용자 인증제3자 애플리케이션에 권한 부여
참여자주로 두 당사자(클라이언트와 서버)최소 세 당사자(클라이언트, 권한 부여 서버, 리소스 서버) + 사용자
복잡성상대적으로 단순함더 복잡함(여러 흐름, 역할, 단계)
상태 관리대부분 무상태(stateless)일부 상태 유지 가능(리프레시 토큰 등)
토큰 유형주로 단일 토큰(JWT 등)여러 토큰(접근 토큰, 리프레시 토큰 등)
표준화특정 표준이 없음(JWT는 표준화됨)RFC 6749로 표준화됨
통합주로 자체 서비스 내서로 다른 서비스/애플리케이션 간
구현 복잡성낮음 ~ 중간중간 ~ 높음
인증 흐름단순 직접 교환여러 흐름(권한 부여 코드, 암시적, 클라이언트 자격 증명 등)
자격 증명 공유클라이언트가 자격 증명 직접 제공자격 증명 공유 없음(권한 위임)
권한 제어일반적으로 포괄적세밀한 권한 범위(scopes)
토큰 수명구현에 따라 다양함일반적으로 접근 토큰(짧음), 리프레시 토큰(김)
인증 주체서비스 자체신뢰할 수 있는 제3자
사용자 경험단순한 로그인 절차동의 화면, 권한 승인 등 추가 단계
보안구현에 따라 다양함여러 보안 계층 내장
취소 방법구현에 따라 다양함표준화된 토큰 취소 메커니즘
적합한 용도단일 시스템, 모바일 앱, API소셜 로그인, 제3자 API 통합, SSO

실제 구현 예시

토큰 인증 구현 (JWT 사용)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Node.js 예시

// 로그인 및 토큰 발급
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  
  // 사용자 검증 (실제 구현에서는 데이터베이스 조회 등으로 처리)
  if (authenticateUser(username, password)) {
    // JWT 토큰 생성
    const token = jwt.sign(
      { userId: user.id, username: user.username },
      'your-secret-key',  // 실제 환경에서는 안전하게 관리해야 함
      { expiresIn: '1h' }
    );
    
    return res.json({ token });
  }
  
  return res.status(401).json({ message: '인증 실패' });
});

// 토큰 검증 미들웨어
function verifyToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  
  if (!token) return res.status(401).json({ message: '토큰 없음' });
  
  jwt.verify(token, 'your-secret-key', (err, user) => {
    if (err) return res.status(403).json({ message: '토큰 무효' });
    
    req.user = user;
    next();
  });
}

// 보호된 리소스 접근
app.get('/api/protected', verifyToken, (req, res) => {
  res.json({ message: '보호된 데이터', user: req.user });
});

OAuth 2.0 구현 (권한 부여 코드 흐름)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// Node.js 예시 (간소화됨)

// 클라이언트 설정
const config = {
  client_id: 'your-client-id',
  client_secret: 'your-client-secret',
  redirect_uri: 'http://your-app.com/callback',
  authorization_endpoint: 'https://auth-server.com/authorize',
  token_endpoint: 'https://auth-server.com/token'
};

// 인증 시작
app.get('/login', (req, res) => {
  // 상태 파라미터 생성 (CSRF 방지)
  const state = crypto.randomBytes(16).toString('hex');
  req.session.oauth_state = state;
  
  // 권한 부여 서버로 리디렉션
  const authUrl = `${config.authorization_endpoint}?` +
    `client_id=${config.client_id}&` +
    `redirect_uri=${encodeURIComponent(config.redirect_uri)}&` +
    `response_type=code&` +
    `state=${state}&` +
    `scope=profile email`;
  
  res.redirect(authUrl);
});

// 콜백 처리
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;
  
  // 상태 검증
  if (state !== req.session.oauth_state) {
    return res.status(403).send('상태 불일치, CSRF 공격 가능성');
  }
  
  try {
    // 권한 부여 코드를 토큰으로 교환
    const tokenResponse = await axios.post(config.token_endpoint, {
      grant_type: 'authorization_code',
      code,
      redirect_uri: config.redirect_uri,
      client_id: config.client_id,
      client_secret: config.client_secret
    });
    
    const { access_token, refresh_token } = tokenResponse.data;
    
    // 토큰 저장
    req.session.access_token = access_token;
    req.session.refresh_token = refresh_token;
    
    res.redirect('/dashboard');
  } catch (error) {
    res.status(500).send('토큰 교환 오류');
  }
});

// 보호된 API 호출
app.get('/api/userinfo', async (req, res) => {
  if (!req.session.access_token) {
    return res.status(401).send('인증 필요');
  }
  
  try {
    const userResponse = await axios.get('https://api-server.com/userinfo', {
      headers: {
        'Authorization': `Bearer ${req.session.access_token}`
      }
    });
    
    res.json(userResponse.data);
  } catch (error) {
    if (error.response && error.response.status === 401) {
      // 토큰 갱신 로직 구현 가능
      return res.status(401).send('토큰 만료');
    }
    res.status(500).send('API 호출 오류');
  }
});

용어 정리

용어설명

참고 및 출처