google OAuth 2.0 인증 정보 만들기
만든 후 인증 JSON 파일 다운로드
{
"web": {
"client_id": "718453155317-***.apps.googleusercontent.com",
"project_id": "sammy-oauth",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "GOCSPX-***",
"redirect_uris": ["http://localhost:3000/auth/google/callback"],
"javascript_origins": ["http://localhost:3000"]
}
}
env 파일에 client_id 와 client_secret 설정
GOOGLE_CLIENT_ID=718453155317-***.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-***
go 프로젝트 생성
googleLoginHandler 구현
a. API 엔드포인트는 http://localhost:3000/auth/google/login 로 설정
b. Cookie 에 state 값 셋팅
- 랜덤한 숫자로 state 값을 만드는데 해당 값은 리다이렉트 될 때 검증하기 위해 한번 쓰고 버려지는 임의의 문자열 key이다. (csrf 공격을 방지하기 위함)
- state 값을 oauthstate 라는 이름으로 24시간 유효기간의 cookie 셋팅
c. https://accounts.google.com/o/oauth2/auth 로 리다이렉트
googleAuthCallback Handler 구현
a. API 엔드포인트는 http://localhost:3000/auth/google/callback으로 설정
b. state 값과 쿠키에 담긴 oauthstate 값이 일치하는지 확인
c. Form 에 담긴 code값으로 Access Token Exchange
- 함수 내에서 https://oauth2.googleapis.com/token 호출
d. Access Token 값으로 api 호출해서 userinfo 가져온다.
- https://www.googleapis.com/oauth2/v2/userinfo?access_token={acceess_token}
localhost:3000 에 접근해서 Google Login 버튼 클릭
→ https://accounts.google.com/o/oauth2/auth 요청 query parameter
client_id: 718453155317-***.apps.googleusercontent.com
redirect_uri: http://localhost:3000/auth/google/callback
response_type: code
scope: https://www.googleapis.com/auth/userinfo.email
state: lWbHTRADfE17uwQH0eLGSQ==
계정 선택
계속 클릭
http://localhost:3000/auth/google/callback 리다이렉트 되고 아래 정보를 받아온다.
state: lWbHTRADfE17uwQH0eLGSQ==
code: 4/0AeaYSHAUlE2KqtavTS1VHBhC2G_Wv2In580sXAhFb5DZudYbp_c_ME1BOYL2H8365Oy-ng
scope: email https://www.googleapis.com/auth/userinfo.email openid
state 값을 검증하고 code 값으로 https://oauth2.googleapis.com/token 호출하여 access token 을 받아온다.
그리고 해당 토큰값으로 https://www.googleapis.com/oauth2/v2/userinfo?access_token=${access_token} 호출하여 userinfo 를 받아온다.
var (
googleOAuthConfig = oauth2.Config{
ClientID: os.Getenv("GOOGLE_CLIENT_ID"),
ClientSecret: os.Getenv("GOOGLE_CLIENT_SECRET"),
RedirectURL: "http://localhost:3000/auth/google/callback",
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"},
Endpoint: google.Endpoint,
}
)
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)
}
func googleAuthCallback(w http.ResponseWriter, r *http.Request) {
oauthstate, _ := r.Cookie("oauthstate")
if r.FormValue("state") != oauthstate.Value {
log.Printf("invalid google oauth state! Cookie: %s, State: %s \n", oauthstate.Value, r.FormValue("state"))
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
data, err := getGoogleUserInfo(r.FormValue("code"))
if err != nil {
log.Println(err.Error())
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
fmt.Fprintf(w, string(data))
}
var oauthGoogleUrlAPI = "https://www.googleapis.com/oauth2/v2/userinfo?access_token="
func getGoogleUserInfo(code string) ([]byte, error) {
token, err := googleOAuthConfig.Exchange(context.Background(), code)
if err != nil {
return nil, fmt.Errorf("Failure to Exchange %s \n", err.Error())
}
resp, err := http.Get(oauthGoogleUrlAPI + token.AccessToken)
if err != nil {
return nil, fmt.Errorf("Failed to Get UserInfo %s \n", err.Error())
}
return ioutil.ReadAll(resp.Body)
}
func googleLoginHandler(w http.ResponseWriter, r *http.Request) {
state := generateStateOauthCookie(w)
url := googleOAuthConfig.AuthCodeURL(state) // 리다이렉트 될 때 검증하는 한번 쓰고 버려지는 key, csrf 방지를 위함.
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func generateStateOauthCookie(w http.ResponseWriter) string {
expiration := time.Now().Add(24 * time.Hour)
b := make([]byte, 16)
rand.Read(b)
state := base64.URLEncoding.EncodeToString(b)
cookie := &http.Cookie{
Name: "oauthstate",
Value: state,
Expires: expiration,
}
http.SetCookie(w, cookie)
return state
}
++ 추가 공부
client_id
client_secret