SSO(Single Sign-On) - Go Auto 2.0 Authorization Code Flow(GitHub 활용)

beluga000·2024년 9월 10일
0
post-thumbnail

SSO (Single Sign-On)

SSO는 사용자가 한 번의 로그인만으로 여러 시스템이나 서비스에 접근할 수 있게 해주는 인증 방법입니다. 예를 들어, 사용자가 한 번 로그인을 하면 Gmail, Youtube, Gooble Drive 같은 여러 구글 서비스에 별도의 로그인 없이 접근할 수 있습니다.

SSO 주요 특징

1. 사용자 편의성 향상

한 번 로그인으로 여러 시스템에 접근할 수 있어 비밀번호를 여러 번 입력하는 불편을 줄입니다.

2. 보안 강화

사용자는 여러 계정과 비밀번호를 기억할 필요가 없기 때문에, 비밀번호를 재사용하거나 보안이 낮은 비밀번호를 설정하는 위험을 줄일 수 있습니다. 또한 SSO를 통해 중앙에서 보안 관리 및 모니터링이 가능해집니다.

3. 관리 효율성

기업에서는 사용자의 계정 관리를 중앙 집중적으로 할 수 있어, 사용자 계정 생성, 변경, 삭제 등의 작업을 쉽게 수행할 수 있습니다.

4. 다양한 인증 방식 지원

SSO는 OAuth, SAML(Security Assertion Markup Language), OpenID Connect와 같은 프로토콜을 사용하여 구현될 수 있습니다. 이들 프로토콜은 서로 다른 애플리케이션이나 시스템 간의 인증 정보를 안전하게 주고받는 방식을 정의합니다.

SSO 구현

SSO(Single Sign-On)을 구현하기 위해서는 두 가지 요소가 필요합니다. SSO 서버와 클라이언트 애플리케이션 이를 통해 사용자가 한 번 로그인하면 여러 애플리케이션에서 인증이 유지됩니다.

SSO 아키텍처

SSO를 구현하가 위해 선택할 수 있는 인증 프로토콜이 몇가지 있습니다.

1. OAuth 2.0

권한 위임 프로토콜로, 주로 API 및 서드파티 애플리케이션에서 많이 사용됩니다.

2. OpenID Connect(OIDC)

OAuth 2.0의 확장으로, 인증 기능을 제공하며 로그인 처리를 쉽게 구현할 수 있습니다.

3. SAML(Security Assertion Markup Language)

XML 기반의 프로토콜로, 주로 엔터프라이즈 환경에서 많이 사용됩니다.

SSO 서버 설정

SSO 서버는 인증과 관련된 모든 로직을 처리하며, 일반적으로 사용자 인증 토큰을 발급하고 이를 클라이언트가 사용할 수 있도록 해줍니다. 아래의 SSO 서버 설정 방법은 OAuth 2.0과 OpenID Connect에 기반을 둡니다.

1. SSO 서버 선택 : 직접 구현하거나, 이미 개발된 SSO 솔루션을 사용

  • Keycloak : 오픈 소스 ID 및 인증 관리 솔루션
  • Auth0 : 상용 서비스로, OAuth 2.0 및 OpenID Connect를 쉽게 설정할 수 있음
  • Okta : 사용자 관리 및 SSO 솔루션

2. SSO 서버 설정

  • SSO 서버에서 클라이언트 애플리케이션(웹, 모바일 등)을 등록합니다. 이 과정에서 클라이언트 ID 및 비밀 키가 발급됩니다.
  • 리다이렉트 URL을 설정합니다. 사용자가 인증에 성공하면 이 URL로 리다이렉트되어 애플리케이션으로 돌아옵니다.

3. OAuth 2.0 / OpenID Connect 플로우 설정

  • Authorization Code Flow : 가장 많이 사용되는 플로우로 클라이언트가 인증 코드르 받고, 서버에서 토큰을 교환

  • SSO 서버는 인증 후 사용자에게 JWT(JSON Web Token) 또는 액세스 토큰을 발급합니다.

클라이언트 애플리케이션 구현

1. 로그인 페이지 구현

사용자가 로그인 버튼을 누르면 클라이언트는 SSO 서버로 Authorization Code를 요청하는 리퀘스트를 보냅니다.

GET https://sso-server.com/auth?client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&response_type=code&scope=openid profile email

2. Authorization Code 수신 및 토큰 요청

사용자가 SSO 서버에서 인증에 성공하면 Authorization Code가 리다이렉트 URL을 통해 클라이언트 애플리케이션에 전달됩니다.

클라이언트는 이 코드를 사용해 SSO 서버에 Access Token과 ID Token을 요청합니다.

POST https://sso-server.com/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
code={AUTHORIZATION_CODE}
redirect_uri={REDIRECT_URI}
client_id={CLIENT_ID}
client_secret={CLIENT_SECRET}

3. 토큰 검증

SSO 서버는 클라이언트에게 Access Token과 ID Token(JWT)를 발급합니다. 클라이언트는 JWT를 디코딩하고 검증하여 사용자 정보를 확인할 수 있습니다.

예를 들어 JWT 토큰을 파싱하고, 사용자 프로필 정보를 가져와 세션을 관리할 수 있습니다.

4. 세션 유지 및 로그아웃 처리

클라이언트 애플리케이션은 발급된 Access Token을 사용해 보호된 API에 접근합니다.

로그아웃을 구현하려면 SSO 서버와 통신하여 세션을 무효화해야 합니다.

GET https://sso-server.com/logout?redirect_uri={REDIRECT_URI}

5. SSO 클라이언트 연동

다양한 클라이언트 애플리케이션에서 SSO를 연동할 수 있습니다. 대부분의 프로그래밍 언어에서 OAuth 2.0 및 OpenID Connect를 지원하는 라이브러리들이 제공됩니다.

  • JavaScript : oidc-client, passport.js
  • Java : Spring Security OAuth
  • Python : Anthlib, Django OAuth Toolkit
  • Go : golang.org/x/oauth2

예시 Keycloak을 이용한 SSO 구현

1. Keycloak 설정

Keycloak 관리 콘솔에서 새로운 클라이언트를 등록하고, 클라이언트 ID,리다이렉트 URL을 설정합니다.

2. 클라이언트 애플리케이션에서 Keycloak을 통한 인증 요청

const keycloak = new Keycloak({
    url: 'https://keycloak-server.com',
    realm: 'myrealm',
    clientId: 'myclient',
});

keycloak.init({ onLoad: 'login-required' }).success(function() {
    console.log('Logged in');
}).error(function() {
    console.log('Failed to login');
});

예시 Go에서 SSO 구현(OAuth 2.0 Authorization Code Flow)

1. 필요 패키지 설치 - Go에서는 위에서 언급한 것처럼
golang.org/x/oauth2 패키지를 사용

go get golang.org/x/oauth2
go get golang.org/x/oauth2/github

2. 환경설정

OAuth 2.0 클라이언트를 사용하기 위해서는 서비스 제공자(GitHub, Google 등)에서 클라이언트 ID와 클라이언트 비밀키를 발급받아야 합니다. GitHub의 경우 GitHub OAuth 앱 설정 페이지에서 애플리케이션을 등록할 수 있습니다.

아래 링크에 접속하여 새로운 OAuth application 등록을 진행합니다.
https://github.com/settings/applications/new

위 화면에서 Homepage URL은 테스트 환경이라면 http://localhost:8080
Authorization callback URLhttp://localhost:8080/callback 으로 작성합니다.

애플리케이션을 정상 등록하면 위 화면에서 클라이언트 ID와 비밀키를 확인할 수 있습니다.

.env 파일 내 아래 처럼 작성

GITHUB_CLIENT_ID=your_client_id
GITHUB_CLIENT_SECRET=your_client_secret

3. OAuth 2.0 클라이언트 구현

아래 코드를 main.go 파일에 작성

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/joho/godotenv"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/github"
)

var (
	// GitHub OAuth 2.0 설정
	githubOauthConfig *oauth2.Config

	// 무작위 문자열 생성, CSRF 방지용
	oauthStateString = "random"
)

func init() {
	// .env 파일을 로드하여 환경 변수를 설정
	err := godotenv.Load()
	if err != nil {
		log.Fatalf("Error loading .env file")
	}

	// 환경 변수 값 로그 출력
	clientID := os.Getenv("GITHUB_CLIENT_ID")
	clientSecret := os.Getenv("GITHUB_CLIENT_SECRET")
	log.Print("env loaded - CLIENT_ID: ", clientID)
	log.Print("env loaded - CLIENT_SECRET: ", clientSecret)

	// OAuth 설정 초기화
	githubOauthConfig = &oauth2.Config{
		ClientID:     clientID,
		ClientSecret: clientSecret,
		RedirectURL:  "http://localhost:8080/callback", // GitHub OAuth 앱에 설정된 리다이렉트 URL과 일치해야 함
		Scopes:       []string{"user:email"},
		Endpoint:     github.Endpoint,
	}

	// OAuth 설정 로그 출력
	log.Printf("OAuth ClientID: %s\n", githubOauthConfig.ClientID)
	log.Printf("OAuth ClientSecret: %s\n", githubOauthConfig.ClientSecret)
}

// 메인 핸들러
func handleMain(w http.ResponseWriter, r *http.Request) {
	var htmlIndex = `<html><body><a href="/login">GitHub로 로그인하기</a></body></html>`
	fmt.Fprintf(w, htmlIndex)
}

// 로그인 핸들러
func handleGitHubLogin(w http.ResponseWriter, r *http.Request) {
	// GitHub로 리다이렉트 (OAuth 2.0 Authorization Code Flow 시작)
	url := githubOauthConfig.AuthCodeURL(oauthStateString)
	log.Printf("Redirecting to: %s\n", url) // 리다이렉트 URL 로그로 출력
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

// 콜백 핸들러
func handleGitHubCallback(w http.ResponseWriter, r *http.Request) {
	// CSRF 방지용 state 체크
	state := r.FormValue("state")
	if state != oauthStateString {
		log.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	// Authorization Code 수신
	code := r.FormValue("code")
	token, err := githubOauthConfig.Exchange(context.Background(), code)
	if err != nil {
		log.Printf("oauthConf.Exchange() failed with '%s'\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	// GitHub API를 사용하여 사용자 정보 가져오기
	response, err := http.Get("https://api.github.com/user?access_token=" + token.AccessToken)
	if err != nil {
		log.Printf("Get: %s\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}
	defer response.Body.Close()

	// 사용자 정보 디코딩 및 출력
	var user map[string]interface{}
	json.NewDecoder(response.Body).Decode(&user)
	fmt.Fprintf(w, "UserInfo: %v\n", user)
}

func main() {
	http.HandleFunc("/", handleMain)
	http.HandleFunc("/login", handleGitHubLogin)
	http.HandleFunc("/callback", handleGitHubCallback)

	fmt.Println("Started running on http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

이후 서버를 실행해서 테스트 할 수 있습니다.

go run main.go

1. 브라우저에서 http://localhost:8080에 접속

2. GitHub로 로그인하기 링크를 클릭하면 GitHub 로그인 페이지로 리다이렉트됩니다.

3. 로그인에 성공하면, GitHub에서 사용자 정보를 받아와 브라우저에 출력합니다.

이러한 패턴으로 SSO 구현 테스트를 진행할 수 있습니다.

GitHub로 로그인 하기 버튼 클릭 후 GitHub 페이지에서 404 화면이 나온다면 발급받은 Client_id와 비밀키를 다시 확인하시기 바랍니다.

profile
Developer

0개의 댓글