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 을 설정한다.
  1. 사용자 인증 진행 (server flow)
  • create anti-forgery state token
  • authentication request 를 google 에 보냄
  • anti-forgery state token 확인
  • token들을 얻기 위한 code와의 교환
  • ID token 으로 사용자 정보를 획득
  • 사용자 인증 시키기
  1. 사용자 인증 화면 (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를 사용할테니 큰 부담은 없으리라고 생각한다.