Google OAuth2.0 을 사용한 OpenID Connect

기회는 언제나 작은 일로 생긴다

slack-google-integration slack 에서 google signin으로 1-click login이 가능하다는 내용을 알고 우와? 이런 것도 되네? 정도로 기분을 느끼며 그냥 지나쳐가는 기능이 될 뻔했으나, 팀에 새로운 직원 분이 오시게 되니 절차가 굉장히 복잡하다는 것을 알고, 간소화 시키고 싶은 생각에 google을 이용한 로그인을 살펴보게 되었다.

회사에 새로운 직원이 입사하게 되면

G suite 계정 생성 (회사 이메일 생성)

관리자 사이트 아이디 생성 (회사 이메일 계정과 같은)

(프로그램을 사용 하기 위해) 서비스 사이트들 마다 아이디 생성

어휴 도대체 몇 개의 아이디를 생성하고 관리해야 하는 것인지 모르겠다.

전개

이 과정에서 대 혼란이 야기 된다. 어떻게 하면 관리 아이디는 적게 유지 할 수 있을까? 에 대한 해답은 단순하게도 많은 회사들이 사용하는 G Suite 다.

그럼 어떻게 해야 구글 로그인을 이용할 수 있을까?를 생각해서 개발자 문서들을 찾아보기 시작한다. 처음에 검색 키워드를 google SSO로 하는 바람에 SAML 2.0 을 보기 시작한다.

덕분에 SAML의 동작 방식도 이해하고, Protocol에 대한 이해도를 올릴 수 있어서 좋은 시간이었던 것은 분명하다. 하지만 이것은 내가 원하는 방향이 아니었다. SAML 관련 이야기는 다음 포스팅으로 해야지! (이렇게 포스팅 꺼리를 남겨둡니다)

google signin 나의 요구사항은 도메인 제약(Hosted Domain)을 하는 구글 로그인 이었다.

구글 로그인 문서를 살펴보면서 어떻게 해야 도메인 제약을 걸 수 있는가? 에 대한 해답을 찾으려고 돌아다녔지만, 답은 쉽게도 slack-google 로그인에서 찾을 수 있었다. 버튼에 링크 주소가 있어서 쉽게 찾았다.

바로 OpenID Connect 를 이용해서 google 로그인을 했더라. 해당 google 문서는 여기에서 확인 가능하다.

Open ID를 이용한 Google Signin 만들기
  1. 우선 Google OAuth 2.0 셋팅을 해야한다.
    • Client ID, secret 을 받고, origin url, redirect url 을 설정한다.
  2. 사용자 인증 진행 (server flow)
    • create anti-forgery state token
    • authentication request 를 google 에 보냄
    • anti-forgery state token 확인
    • token들을 얻기 위한 code와의 교환
    • ID token 으로 사용자 정보를 획득
    • 사용자 인증 시키기
  3. 사용자 인증 화면 (front flow)
    • 맨 위 화면처럼 버튼 클릭 하나를 넣어둠

우선은 진한 글씨로 된 부분이 제일 중요한 로직이다. 현재 관리자 사이트는 ASP.NET MVC Framework 프로젝트로 되어있는데, 그에 알맞게 코드 진행을 할 예정이다.

@create anti-forgery state token

CSRF 공격을 피하기 위해서 anti-forgery token 을 만들어서 유효성을 따지게 하는데, 이 부분은 MVC 에서 제공하는 Html.AntiForgeryToken() 을 응용하여 만들게 되었다. 해당 기능은 System.Web.Helpers.AntiForgery Class 를 기반으로 만들어져 있어서 해당 클래스의 멤버 변수와 메소드들을 사용해서 새로운 Anti-Forgery Token을 생성했다.

public string GetAntiForgeryToken()  
{
    string cookieToken, formToken;
    AntiForgery.GetTokens(null, out cookieToken, out formToken);
    return cookieToken + "," + formToken;
}

@authentication request 를 google 에 보냄

버튼 click event 에 아래와 같은 기능을 실행하도록 달았다. razor view 문법으로 @로 시작하는 부분은 server 상에서 실행하는 문법이다.

<script>  
$("#btngooglelogin").click(function(){
    var url = 'https://accounts.google.com/o/oauth2/v2/auth';
    var params = {
        client_id: '@ConfigurationManager.AppSettings["GoogleClientID"]',
        response_type: 'code',
        scope: 'openid profile email',
        redirect_uri: '@ConfigurationManager.AppSettings["GoogleRedirectUrl"]',
        state: '@GetAntiForgeryToken()',
        access_type: 'online',
        hd:'domain.com' // 이 부분이 원했던 Hosted Domain 기능이다
    };
    document.location.href = url + '?' + $.param(params);
});
</script>  

이벤트를 실행하게 되면 Origin Host -> 구글 Oauth 2.0 인증 -> Redirect Url 로 결과를 보내게 된다.
아래 부터는 (위에 Google OAuth 2.0 셋팅에서 설정한) Redirect Url 에서 처리 하는 내용이다. Redirect Url 을 https://example.domain.com/sso/google 로 설정했기 때문에 Default Route 기능으로 인해 SSOController 안의 ActionResult Google()이 실행이 될꺼라고 예상하고 코드 진행이 될 것이다.

@anti-forgery state token 확인

state request parameter 로 token 정보가 넘어오게 된다.

public async Task<ActionResult> Google()  
{
    ... 중략 ....
    var state = Request["state"];
    IsValidToken(state);

    ... 중략 ....
}

private bool IsValidToken(string token)  
{
    if (token == null || token.Length == 0 || token.Contains(",") == false)
        throw new ArgumentNullException(nameof(token));

    try
    {
        var arrToken = token.Split(',');
        AntiForgery.Validate(arrToken[0].Trim(), arrToken[1].Trim());
        return true;
    }
    catch (HttpAntiForgeryException e)
    {
        throw new HttpAntiForgeryException("Anti forgery token not found");
    }
}

@token들을 얻기 위한 code와의 교환

code request parameter 로 Token을 얻을 수 있는 정보가 넘어오게 된다.

해당 parameter 를 가지고 https://www.googleapis.com/oauth2/v4/token 주소로 다시 재요청을 하게 되면, 따끈따끈한 Token Info를 전달해준다.

아래는 그 코드 진행을 다룬다.

public async Task<ActionResult> Google()  
{
    ... 중략 ....
    var dic = new Dictionary<string, string>();
    dic["code"] = Request["code"] ?? "";
    dic["client_id"] = ConfigurationManager.AppSettings["GoogleClientID"];
    dic["client_secret"] = ConfigurationManager.AppSettings["GoogleClientSecret"];
    dic["redirect_uri"] = ConfigurationManager.AppSettings["GoogleRedirectUrl"];
    dic["grant_type"] = "authorization_code";

    ResponseGoogleSSO result = null; 
    using (var client = new HttpClient())
    {
        var response = await client.PostAsync("https://www.googleapis.com/oauth2/v4/token", new FormUrlEncodedContent(dic));
        result = await response.Content.ReadAsAsync<ResponseGoogleSSO>();
    }

    if (string.IsNullOrEmpty(result.error) == false)
        throw new InvalidOperationException(string.Format("{0} : {1}", result.error_description, result.error));

    ... 중략 ....
}

@ID token 으로 사용자 정보를 획득

유효한 Code를 요청하면 응답으로 ResponseGoogleSSO result 에는 id_token이 찍힐 것이다. 이 토큰을 가지고 다시 또(!!) 사람이 알아볼 수 있는 token 정보로 변환 하는 과정이 필요하다. https://www.googleapis.com/oauth2/v3/tokeninfo 의 주소로 ?id_token={value}를 보내게 되면 적합한 ID_Token 이라면 올바른 정보를 보여줄 것이다.

해당 아래는 ID_Token 을 가지고 GoogleTokenModel을 가지고 오는 코드다.

public async Task<ActionResult> Google()  
{
    ... 중략 ....
    var model = await GetGoogleToken(result.id_token);
    ... 중략 ....
}

private async Task<GoogleTokenModel> GetGoogleToken(string token)  
{
    using (var client = new HttpClient())
    {
        var response = await client.GetAsync("https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=" + token);
        var result = await response.Content.ReadAsAsync<GoogleTokenModel>();
        if (string.IsNullOrEmpty(result.error_description) == false)
            throw new InvalidOperationException(result.error_description);

        return result;
    }
}

@사용자 인증 시키기

현재 관리자 프로젝트는 ASP.NET MVC Framework 를 사용하고, 간단하게 폼인증(Form Authentication)을 사용하고 있다. 그래서 사용자의 인증(Authentication)을 시키는 방법은 FormsAuthentication.SetAuthCookie(userid, false); 로 간단하게 해결한다.

아래는 Full Source 이다.

  • 어차피 직원들만 쓰는 (G suite 계정으로 접속하는) 사이트 이다.
  • 결과를 제대로만 보여주면 되기에 Session Object 를 남발(?)하게 되었다.
  • 오류 처리는 exception 으로 대부분의 오류를 걸러내는 방향으로 코드를 작성했다.

결론

모든 사내 관리자 사이트들을 모두 G suite를 사용하여 구글 로그인으로 변경 했다.

이로서 관리자 아이디를 만들 필요도 없고, 모든 회사 관리자 사이트들은 G suite 계정을 따른다. 사내 직원 관리는 G suite 하나만 관리하면 된다.

직원들의 입장에서도 구글 로그인만 되어있으면, 관리자 사이트를 들어갈 때 1-click 로그인으로 들어갈 수 있다. 또한 더 이상 비밀번호 몰라서 못 들어가는 사태는 발생되지 않으리라. (이 부분을 모두 google 에게 (위임)떠넘기게 되어서 편함을 이루 말할 수 없다.)

이 모든 내용은 G suite 를 사용하는 회사이기에 가능한 내용이다.
직원 관리 시스템이 존재하는 곳이라면, Google 에게 위임을 할 수가 없으니 해당 기능을 사용할 수가 없겠지만, 대부분의 startup 회사들이라면 G suite를 사용할테니 큰 부담은 없으리라고 생각한다.

Ssemi

Read more posts by this author.