본문 바로가기
Dart/Dart Server

[중급] Dart 서버 인증 및 보안/ OAuth 2.0을 활용한 외부 서비스 인증 연동

by Maccrey Coding 2024. 9. 12.
반응형

 

OAuth 2.0은 많은 애플리케이션에서 외부 서비스(예: Google, Facebook, GitHub 등)를 통해 안전하게 사용자 인증을 처리할 수 있는 표준 프로토콜입니다.

이 블로그에서는 OAuth 2.0을 Dart 서버에서 구현하고, 외부 서비스를 통해 사용자 인증을 연동하는 방법을 초보자가 이해하기 쉽게 설명하겠습니다.

1. OAuth 2.0이란 무엇인가?

OAuth 2.0은 사용자가 애플리케이션에 자신의 비밀번호를 제공하지 않고도 외부 서비스(예: Google, Facebook 등)를 통해 인증할 수 있게 해주는 프로토콜입니다.

사용자가 외부 서비스에 로그인을 하고 애플리케이션에 권한을 부여하면, 애플리케이션은 해당 사용자의 정보를 안전하게 가져올 수 있습니다.

OAuth 2.0의 주요 개념

  • Resource Owner: 보호된 리소스(사용자 정보 등)의 소유자, 즉 사용자입니다.
  • Client: 사용자 정보를 요청하는 애플리케이션(우리가 구축할 Dart 서버).
  • Authorization Server: 사용자 인증을 처리하는 외부 서비스(예: Google, Facebook).
  • Access Token: 외부 서비스에서 발급한 인증 토큰으로, 이를 사용해 사용자 정보를 가져올 수 있습니다.

OAuth 2.0 흐름

  1. 사용자가 Dart 서버를 통해 로그인 요청을 보냅니다.
  2. Dart 서버는 외부 서비스의 로그인 페이지로 리디렉션합니다.
  3. 사용자는 외부 서비스에 로그인하고, Dart 서버에 권한을 부여합니다.
  4. Dart 서버는 외부 서비스에서 Access Token을 받습니다.
  5. Dart 서버는 Access Token을 사용해 사용자의 정보를 가져오고, 이를 기반으로 자체적인 세션을 관리합니다.

2. OAuth 2.0을 Dart 서버에 구현하기

이제 Dart로 OAuth 2.0을 사용한 외부 서비스 인증 연동을 구현해보겠습니다.

여기서는 Google OAuth 2.0을 예시로 설명하지만, 다른 서비스도 유사한 방식으로 구현할 수 있습니다.

2.1 프로젝트 설정

먼저 Dart 프로젝트를 생성하고, 필요한 패키지를 추가해야 합니다.

dart create dart_oauth_auth
cd dart_oauth_auth

pubspec.yaml 파일에 shelf와 OAuth 2.0 관련 패키지를 추가합니다.

dependencies:
  shelf: ^1.2.0
  shelf_router: ^1.0.0
  http: ^0.13.4  # HTTP 요청을 보내기 위한 패키지

패키지를 설치하려면 아래 명령어를 실행하세요.

dart pub get

2.2 Google OAuth 2.0 클라이언트 ID 생성

OAuth 2.0을 사용하려면 Google API 콘솔에서 클라이언트 ID와 클라이언트 시크릿을 생성해야 합니다.

  1. Google API 콘솔에 접속: https://console.developers.google.com/
  2. 새로운 프로젝트를 생성하거나 기존 프로젝트를 선택.
  3. OAuth 2.0 클라이언트 ID 생성: OAuth 동의 화면을 설정하고, 웹 애플리케이션용 클라이언트 ID와 시크릿을 생성합니다.
  4. 리디렉션 URI를 설정 (예: http://localhost:8080/oauth/callback).

2.3 Dart 서버에서 OAuth 2.0 요청 처리

bin/main.dart 파일을 열고 서버를 구현합니다. 우선 Google로 리디렉션하는 경로를 설정하겠습니다.

import 'dart:convert';
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_router/shelf_router.dart';
import 'package:http/http.dart' as http;

final clientId = 'YOUR_CLIENT_ID';
final clientSecret = 'YOUR_CLIENT_SECRET';
final redirectUri = 'http://localhost:8080/oauth/callback';

void main() async {
  final router = Router();

  // Google OAuth 로그인 페이지로 리디렉션
  router.get('/login', (Request request) {
    final authUri = Uri.https('accounts.google.com', '/o/oauth2/v2/auth', {
      'response_type': 'code',
      'client_id': clientId,
      'redirect_uri': redirectUri,
      'scope': 'email profile',  // 사용자 이메일과 프로필 정보 요청
      'access_type': 'offline',
    });

    return Response.found(authUri.toString());  // 로그인 페이지로 리디렉션
  });

  // OAuth 콜백 처리
  router.get('/oauth/callback', (Request request) async {
    final queryParams = request.url.queryParameters;
    final authCode = queryParams['code'];

    if (authCode == null) {
      return Response(400, body: 'Authorization code not found');
    }

    // Authorization Code를 Access Token으로 교환
    final tokenUri = Uri.https('oauth2.googleapis.com', '/token');
    final response = await http.post(tokenUri, body: {
      'code': authCode,
      'client_id': clientId,
      'client_secret': clientSecret,
      'redirect_uri': redirectUri,
      'grant_type': 'authorization_code',
    });

    final tokenData = json.decode(response.body);
    final accessToken = tokenData['access_token'];

    if (accessToken == null) {
      return Response(400, body: 'Failed to retrieve access token');
    }

    // Access Token을 사용해 사용자 정보 요청
    final userInfoUri =
        Uri.https('www.googleapis.com', '/oauth2/v2/userinfo', {'alt': 'json'});
    final userInfoResponse = await http.get(userInfoUri,
        headers: {'Authorization': 'Bearer $accessToken'});

    final userInfo = json.decode(userInfoResponse.body);
    return Response.ok('User info: ${userInfo.toString()}');
  });

  final handler = Pipeline().addMiddleware(logRequests()).addHandler(router);

  final server = await io.serve(handler, InternetAddress.anyIPv4, 8080);
  print('Server listening on port ${server.port}');
}

2.4 코드 설명

  1. /login 경로
    • 사용자가 로그인하려고 하면, 이 경로로 요청을 보냅니다.
    • 서버는 Google의 OAuth 2.0 인증 페이지로 리디렉션합니다.
    • 사용자는 Google 계정으로 로그인한 후 애플리케이션에 접근 권한을 부여합니다.
  2. /oauth/callback 경로
    • 사용자가 Google 로그인을 완료하면, Google은 code라는 인증 코드를 서버에 전달합니다.
    • 서버는 이 코드를 이용해 Google로부터 Access Token을 요청합니다.
    • Access Token을 받은 후, 이를 사용하여 Google에서 사용자의 프로필 정보를 가져옵니다.

2.5 서버 실행 및 테스트

1. 서버를 실행합니다.

dart run bin/main.dart

2. 브라우저에서 http://localhost:8080/login으로 접속합니다.

  • Google 로그인 페이지로 리디렉션됩니다.
  • 로그인 후 애플리케이션에 권한을 부여하면, 서버는 사용자의 정보를 Google에서 가져와 표시합니다.

3. OAuth 2.0 보안 고려사항

OAuth 2.0을 사용하는 애플리케이션에서는 보안을 강화하기 위해 몇 가지 고려사항이 필요합니다.

3.1 HTTPS 사용

  • OAuth 2.0 인증 과정에서 민감한 정보(예: 인증 코드, 액세스 토큰 등)가 주고받아집니다.
  • 이 과정에서 데이터가 도청되지 않도록 HTTPS를 반드시 사용해야 합니다.

3.2 Access Token의 만료 및 갱신

  • Access Token은 일반적으로 일정 기간이 지나면 만료됩니다.
  • Refresh Token을 사용하여 새로운 Access Token을 요청할 수 있습니다.

3.3 권한 범위 최소화

  • OAuth 2.0에서 사용자가 부여하는 권한의 범위를 최소화해야 합니다. 필요한 정보만 요청하도록 권한(스코프)을 설정해야 합니다.
    • 예: 이메일과 프로필 정보만 필요하다면 email profile 권한만 요청합니다.

3.4 세션 관리

  • Access Token을 서버가 저장하거나 클라이언트가 저장하도록 선택할 수 있습니다.
  • 세션을 관리할 때는 토큰을 안전하게 저장하고 전달해야 합니다.

 

이 블로그에서는 OAuth 2.0을 Dart 서버에서 사용하여 Google 인증을 구현하는 방법을 알아보았습니다.

OAuth 2.0은 Dart뿐만 아니라 다양한 프로그래밍 언어와 프레임워크에서 널리 사용되며, 사용자 인증을 안전하게 처리할 수 있습니다.

Dart로 웹 서버를 구현하면서 OAuth 2.0을 활용하면, 외부 서비스와 손쉽게 연동할 수 있습니다.

 

OAuth 2.0을 사용한 인증은 사용자 비밀번호를 애플리케이션에 직접 전달하지 않아도 되므로 보안 측면에서 매우 유리합니다.

이번 예제를 기반으로 다른 외부 서비스(Facebook, GitHub 등)와도 인증 연동을 시도해볼 수 있습니다.

구독!! 공감과 댓글은 저에게 큰 힘이 됩니다.

Starting Google Play App Distribution! "Tester Share" for Recruiting 20 Testers for a Closed Test.

 

Tester Share [테스터쉐어] - Google Play 앱

Tester Share로 Google Play 앱 등록을 단순화하세요.

play.google.com

 

 

반응형