Go web 9. OAuth2.0

OAuth

앱이나 홈페이지,게임에 회원가입을 할 때 외부 유명 사이트를 통해 회원 정보를 가져 오는 것을 OAuth라고 한다.

OAuth가 필요한 이유

  1. 개인정보를 보관하는데 있어서 관련 법들이 많아지고, 인력과 장비를 투자하는 것이 작은 회사는 쉽지 않다.

  2. 고객들도 작은 사이트를 이용 할 때마다 하나하나 회원가입을 해야하는 불편함을 덜어준다.

  3. 큰회사들도 OAuth를 제공함에 따라 해당 회사에 가입한 회원수를 늘리고 입지를 강화 할 수 있다.


OAuth의 동작 원리

  • Client: 자원을 이용하는 사용자(user)
  • Resource Owner: OAuth 사용자
  • Authorization Server: OAuth 인증 서버
  • Resource Server(API server): 자원을 호스팅하는 서버

링크텍스트



OAuth 사용해보기

1. console.developers.google.com에 접속 후 인증키 받기

1) 왼쪽 메뉴에서 OAuth 동의화면을 클릭

2) 오른쪽에 프로젝트 만들기 클릭

3) 프로젝트 이름 설정 후 만들기 클릭

4) 왼쪽 메뉴에서 사용자 인증 정보로 들어간뒤 상단에 +사용자 인증 정보 만들기 클릭

5) 이름 설정하고 URI 입력

6) 승인된 리디렉션 URI에 위와같이 입력 후 만들기

7) 클라이언트 ID와 보안비밀번호 카피 해두기

8) 시스템 속성에서 환경 변수 설정하기

9) 새로 만들기로 구글 클라이언트 ID와 보안비밀번호를 각각 등록해 준다.


2. Go를 이용해 웹 페이지 생성

ex) index.html

<html>
    <head>
        <title>Go Oauth2.0 Test</title>
    </head>
    <body>
        <p><a href='./auth/google/login'>Google Login</a></p>
    </body>
</html>

ex) main.go

package main

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/gorilla/pat"
	"github.com/urfave/negroni"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
)

// oauth2 패키지를 이용해 config 생성
var googleOauthConfig = oauth2.Config{
	RedirectURL: "http://localhost:3000/auth/google/callback",
	//요청한 콜백을 받을 주소 등록
	ClientID: os.Getenv("GOOGLE_CLIENT_ID"),
	// 환경 변수에서 클라이언트 아이디 가져오기
	ClientSecret: os.Getenv("GOOGLE_SECRET_KEY"),
	// 환경 변수에서 비밀번호 가져오기
	Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"},
	// userinfo.email에 억세스 하는 권한 요청하기
	Endpoint: google.Endpoint,
	// 구글 로그인 요청을 받으면 config를 이용해서
	// 구글의 어떤 경로로 보내야 되는지 (Endpoint)가 나옴
}

// 유저가 EndPoint에 직접 접근해서 로그인 할 수 있게 리다이렉트 해줘야됨
func googleLoginHandler(w http.ResponseWriter, r *http.Request) {
	state := generateStateOauthCookie(w)
	// 경로가 만들어지고 리다이렉트로 calback이 왔을때 키가 맞는지 확이 할 수 있어야됨
	// 유저의 브라우저 쿠키에다가 tmep키를 심고 리다이렉트로 calback이 왔을때 쿠키를 비교하는 방식
	url := googleOauthConfig.AuthCodeURL(state)
	// 어떤 경로로 보내야 되는지 알려줌
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
	// w, r , 리다이렉트 경로 , 왜리다이렉트하는지
	// 유저가 해당경로로 리다이렉트 되고 구글 로그인 폼이 뜬다.
}

func generateStateOauthCookie(w http.ResponseWriter) string {
	expiration := time.Now().Add(1 * 24 * time.Hour)
	// 쿠키 만료시간 설정
	// 현재 시간으로 부터 하루 뒤에 만료
	b := make([]byte, 16) //16바이트 어레이
	rand.Read(b)          // 런덤 숫자로 어레이 채우기
	state := base64.URLEncoding.EncodeToString(b)
	// html 인코딩 하여 스트링으로 바꾸기
	cookie := &http.Cookie{Name: "oauthstate", Value: state, Expires: expiration}
	// 쿠키에 인코딩한 값을 넣어줌
	http.SetCookie(w, cookie)
	// 쿠키를 w에 넘겨줌
	return state
}

func googleAuthCallback(w http.ResponseWriter, r *http.Request) {
	oauthstate, _ := r.Cookie("oauthstate") //쿠키를 읽어옴

	if r.FormValue("state") != oauthstate.Value {
		// 쿠키 값과 state의 값이 다를 경우
		// 잘못된 요청
		log.Printf("invalid google oauth state cookie: %s state:%s\n", oauthstate.Value, r.FormValue("state"))
		// 로그 남기기
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		// 기본 경로로 리다이렉트
	}
	data, err := getGoogleUserInfo(r.FormValue("code"))
	// 코드를 구글에 요청해서 userinfo를 가져온다.
	if err != nil {
		log.Println(err.Error()) // log와 에러를 찍음
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		// 기본 경로로 리다이렉트
		return
	}
	fmt.Fprint(w, string(data))
	// err가 없을경우 유저 정보 보내기
}

// 유저정보를 리퀘스트 하는 경로
const oauthGoogleUrlAPI = "http://www.googleapis.com/oauth2/v2/userinfo?access_token="

func getGoogleUserInfo(code string) ([]byte, error) {
	token, err := googleOauthConfig.Exchange(context.Background(), code)
	//config를 통해 토큰 받아오기
	//Exchange로 토큰과 코드 교환
	//context = 쓰래드간에 데이터를 주고받는 쓰래드세이프한 저장소
	if err != nil {
		return nil, fmt.Errorf("Failed to Exchange %s\n", err.Error())
		// 문자열로 에러를 만듦
		// 데이터를 못받았기 때문에 nil을 리턴
	}
	resp, err := http.Get(oauthGoogleUrlAPI + token.AccessToken)
	// 유저정보를 리퀘스트하는 경로에 엑세스 토큰을 붙임
	// GET을 통해 요청
	if err != nil {
		return nil, fmt.Errorf("Failed to Get UserInfo %s\n", err.Error())
	}
	return ioutil.ReadAll(resp.Body)
	// resp의 데이터 반환
}

func main() {
	mux := pat.New()
	mux.HandleFunc("/auth/google/login", googleLoginHandler)
	mux.HandleFunc("/auth/google/callback", googleAuthCallback)

	n := negroni.Classic()
	n.UseHandler(mux)
	http.ListenAndServe(":3000", n)

}

로그인을 클릭하면 구글 로그인 창이 뜬다.

로그인 했을 때

유저 정보를 얻어옴

id만 DB에 저장해서 KEY값으로 사용하고 나머지 정보는 구글이나 페이스북 처럼 큰 회사에 요청해서 여러 정보를 가져와서 사용할 수 있다.

좋은 웹페이지 즐겨찾기