Microsoft MVP성태의 닷넷 이야기
.NET Framework: 190. 트위터 계정으로 로그인하는 C# 웹 사이트 제작 [링크 복사], [링크+제목 복사]
조회: 24714
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
트위터 계정으로 로그인하는 C# 웹 사이트 제작

사이트 만들 때 마다 사용자 로그인을 위한 DB를 유지해야 하는데요. 때로는, 그냥 유명 사이트의 계정과 연동하는 것이 여러 모로 이득이 되는 경우가 있습니다. 이번 글에서는, 우리가 흔히 알고 있는 사이트 중에서 "트위터(Twitter.com)"의 계정으로 인증 받을 수 있는 ASP.NET 웹 사이트를 제작해 보겠습니다.

이를 위해서 .NET에서 트위터 API 연동을 쉽게 해주는 라이브러리를 사용할 텐데요. 저는 Twitterizer를 선택했습니다. 다음의 웹 사이트에서 다운로드 받을 수 있고, 간단하게 Twitterizer2.dll을 참조하기만 하면 끝입니다.

Twitterizer - A .NET library for Twitter folk
; http://www.twitterizer.net/

Full - download
; http://www.twitterizer.net/files/Twitterizer2-2.3.zip

본격적인 구현에 앞서, 여러분들의 웹 사이트에서 트위터 연동 기능을 제공하려면 반드시 트위터 사이트에서 해당 (웹) 응용 프로그램을 등록시켜 주어야 합니다. 이에 대해서는 다음의 사이트에서 잘 설명해 주고 있습니다.

트위터 twitter API 어플리케이션 등록하기
; http://smok95.tistory.com/213

위의 글에서는 "Application Type"을 "Client"로 설정했는데, 이번 글의 예제를 위해서는 "Browser"로 바꿨으나 그다지 큰 의미는 없어 보입니다.

등록과정을 거치면 ConsumerKey와 CustomerSecret를 할당받게 되는데, 여기까지 완료하셨으면 "개발 준비" 단계까지는 완료되었다고 볼 수 있습니다. ^^




자,,, 그럼 한번 트위터 계정 로그인에 묻어가는 코드를 작성해 볼까요?

우선, "로그인" 버튼을 만들어야겠지요. ^^ default.aspx에 로그인 버튼을 하나 만들고 그에 대해 서버 측 Click 이벤트 핸들러를 등록합니다.

==== default.aspx ====

<asp:Button ID="Button1" runat="server" Text="Button" onclick="Button1_Click" />

==== default.aspx.cs ====

protected void Button1_Click(object sender, EventArgs e)
{
    string consumerKey = ConfigurationManager.AppSettings["ConsumerKey"];
    string consumerSecret = ConfigurationManager.AppSettings["ConsumerSecret"];

    OAuthTokenResponse otr = OAuthUtility.GetRequestToken(consumerKey, consumerSecret,
                                 "http://127.0.0.1:48236/oauth_twitter.aspx");
    Uri uri = OAuthUtility.BuildAuthorizationUri(otr.Token);
    Response.Redirect(uri.AbsoluteUri);
}

소스 코드를 찬찬히 들여다 보면, 일단, consumerKey, consumerSecret 문자열은 트위터에 응용 프로그램 등록하면서 받았던 문자열입니다. 위의 코드에서는 appSettings에 넣어두었던 것을 그대로 재사용한 것이고.

다음으로, RequestToken 값을 OAuthUtility.GetRequestToken 메서드를 이용해서 받아올 수 있습니다. 트위터 계정과 연동하는 것이기 때문에 반드시 트위터 웹 사이트에서 "사용자가 직접 로그인"하는 과정을 거치게 됩니다. 이를 위해서 트위터로 사용자 방문을 시켜야하는데, 트위터 측의 로그인 URL을 얻기 위해서 OAuthUtility.BuildAuthorizationUri 메서드를 호출하여 줍니다. 그런 다음, Response.Redirect시키면 사용자의 웹 브라우저는 트위터의 로그인 화면을 보여줍니다.

이제 사용자는 로그인을 하게 되고, ("Remember me" 옵션을 이전에 체크하고 로그인을 했다거나, 같은 브라우저 세션에서 이미 로그인을 했었다면 로그인 과정은 생략됨) 아래와 같이 사용자로 하여금 "consumerKey"에 해당하는 그 응용 프로그램을 신뢰할 것인지를 묻게 됩니다.

[그림 1: 응용 프로그램 측에 사용자 데이터 접근 허용]
integrate_twitter_account_1.png

이 과정에서 사용자가 "Allow" 버튼을 누르면 트위터 측에서는 이전에 OAuthUtility.GetRequestToken의 3번째 인자로 넘겨 주었던 "http://127.0.0.1:48236/oauth_twitter.aspx" 콜백 URL로 사용자 웹 브라우저를 이동시킵니다. 이 때, 인증받은 정보를 Query String 형식으로 다음과 같이 넘겨주게 됩니다.

oauth_twitter.aspx?oauth_token=...[Access 토큰값]&oauth_verifier=[PIN 정보]

특이하죠. 이 때문에, 사용자는 트위터 이외에 자신이 이용하려는 다른 웹 사이트가 계정 정보를 소유하지 못하도록 하는 효과를 갖습니다. 즉, 위의 "SysnetTestWebApp" 웹 사이트는 사용자의 트위터 ID/PW를 알 수 없고 인증되었다는 그 사실만을 알게 되는 것입니다.

"SysnetTestWebApp" 웹 사이트 측에서는, 사용자에 대한 정보나 사용자를 대신해서 트위터 서비스를 이용하려면 위의 콜백 URL에서 전달받았던 oauth_token, oauth_verifier 값을 이용해서 다음과 같이 Access 토큰 값을 얻을 수 있습니다.

==== oauth_twitter.aspx ====

public partial class oauth_twitter : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string requestToken = Request.QueryString["oauth_token"];
        string accessVerifier = Request.QueryString["oauth_verifier"];

        string consumerKey = ConfigurationManager.AppSettings["ConsumerKey"];
        string consumerSecret = ConfigurationManager.AppSettings["ConsumerSecret"];

        OAuthTokenResponse accessTokenResponse
                = OAuthUtility.GetAccessToken(consumerKey, consumerSecret, requestToken, accessVerifier);

        string accessToken = accessTokenResponse.Token;
        string accessTokenSecret = accessTokenResponse.TokenSecret;

        TextBox1.Text = accessTokenResponse.UserId.ToString();
        TextBox2.Text = accessTokenResponse.ScreenName;
    }
}

==== 실행 결과 ====
사용자 ID: xxxxxxxx
사용자 이름: techsharer

그럼, 설명이 모두 끝난 것 같군요. 위에서 중요한 것은 accessToken, accessTokenSecret 값입니다. 만약, 다음 번에 "SysnetTestWebApp" 웹 사이트를 방문했을 때 로그인 과정을 생략하도록 "Remember Me"와 같은 기능을 제공하고 싶다면 "accessToken, accessTokenSecret" 2가지 값을 DB에 저장해 둔 후, 그 값을 조회할 수 있는 키를 사용자 측에 쿠키값으로 내려주는 방식으로 구현할 수 있습니다.

또, 어떻게 응용할 수 있을까요? 위의 웹 사이트는 전적으로 모든 인증 관리를 트위터에 맡겼지만, 이와 달리 자체 로그인 정보를 유지하는 웹 사이트가 그 사용자의 트위터 연동을 하고 싶은 경우도 있을 텐데요. 이런 경우에는 해당 사용자의 로그인 정보 테이블에 "accessToken, accessTokenSecret" 값을 모두 보관하고 있다가 트위터 연동 API를 호출해야 할 필요가 있을 때 액세스 토큰을 재 구성해서 트위터 API를 이용하는 것도 가능합니다.

실제로, 대부분의 트위터 연동 API는 "accessToken, accessTokenSecret" 값을 기반으로 하는 인증 토큰을 요구합니다. 예를 들어, 위에서처럼 로그인한 사용자의 Timeline 목록을 원한다면 다음과 같이 코딩하는 것이 가능합니다.

OAuthTokens authToken = new OAuthTokens();
authToken.AccessToken = accessToken;
authToken.AccessTokenSecret = accessTokenSecret;
authToken.ConsumerKey = consumerKey;
authToken.ConsumerSecret = consumerSecret;

TimelineOptions to = new TimelineOptions();
to.Count = 20;
to.IncludeRetweets = false;
TwitterResponse<TwitterStatusCollection> tr = TwitterTimeline.HomeTimeline(authToken, to);

TwitterStatusCollection tsc = tr.ResponseObject;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < tsc.Count; i++)
{
    TwitterStatus ts = tsc[i];
    sb.AppendLine(ts.CreatedDate.ToString() + " : " + ts.Text + " from " + ts.User.ScreenName);
}

이렇게 해서 트위터 계정 연동을 알아봤는데요. 개인적으로 아쉬웠던 점이 2가지 정도 있었습니다. (둘다 제가 방법을 모른 걸 수도 있습니다. ^^)

첫 번째는, 원래의 트위터 웹 사이트와 SSO(Single-sign on)가 되도록 하고 싶었는데, 이것이 가능하지 않는 것 같아서 구현을 하지 못했습니다. 즉, 사용자가 트위터 웹 사이트를 보다가 "SysnetTestWebApp" 웹 사이트로 이동을 하면 자연스럽게 로그인 상태로 나오면 좋겠는데,,, 안된다는 것!

두 번째는, 사용자가 트위터 웹 사이트로 이동한 후 로그인을 정상적으로 한 다음, 매번 "[그림 1]"과 같이 "허락"을 구하는 화면이 나온다는 것입니다. 좀 귀찮을 수도 있는 단계인데요. 아마도 이를 우회하기위해서라도 연동 웹 사이트 측에서는 "Remember Me"와 같은 기능을 구현해 두는 것이 좋을 것 같습니다.

음... 어쨌든, 테스트해 보고 나니 마음에 듭니다. 이참에 "SYSNET" 웹 사이트를 아예 트위터 계정 연동으로 돌려버릴까... 고민이 좀 되는군요. ^^

첨부한 소스는 지금까지의 코드를 포함하고 있으니 참고하시면 되겠습니다. ^^ (물론, consumerKey 등의 정보는 제거되었으니, 각자가 받은 키를 web.config의 appSettings에 넣어주시면 잘 동작할 것입니다.)




마지막으로, 참고할 만한 XAuth 인증 방법!

처음, Twitterizer2를 이용하여 프로그램을 하려고 했을 때는 Twitter API에 대해 잘 감싼 형태로 되어 있을 거라 판단하고 인증 과정을 단순하게 봤습니다. 실제로 Twitterizer에는 다음과 같이 ConsumerKey와 CustomerSecret 정보와 함께 트위터 계정 정보만으로 인증하는 API가 제공되고 있습니다.

string consumerKey = ConfigurationManager.AppSettings["ConsumerKey"];
string consumerSecret = ConfigurationManager.AppSettings["ConsumerSecret"];

OAuthTokenResponse otr = 
    XAuthUtility.GetAccessTokens(consumerKey, consumerSecret,
                 "[트위터 ID]", "[트위터 비밀번호");

하지만, 정작 실행을 하면 다음과 같이 예외가 발생합니다.

Twitterizer.TwitterizerException was unhandled by user code
    Message=The remote server returned an error: (401) Unauthorized.

<?xml version="1.0" encoding="UTF-8"?>
<hash>
  <error>Client application is not permitted to use xAuth.</error>
  <request>/oauth/access_token?x_auth_username=[계정ID]&x_auth_password=[계정암호]&x_auth_mode=client_auth</request>
</hash>

왜 오류가 발생하는지는 다음의 블로그에서 설명이 되어 있습니다.

Twitter Client/Browser 개발을 위한 인증(oAuth, xAuth)
; http://dreamofblue.tistory.com/73

그리고, 전체적인 OAuth 인증 과정 이해는 다음의 글을 참조하시면 도움이 됩니다.

트위터 twitter API - liboauth를 이용한 OAuth인증 및 API사용 예
; http://smok95.tistory.com/214

앞으로의 웹 세계를 생각한다면, OAuth나 Windows Identity Management와 같은 식의 인증 처리에 익숙해지는 것이 필수가 아닐까 싶군요. ^^




[파이썬의 경우]

Twitter 검색을 위한 개발자 플랫폼에 로그인하고,

Twitter Developer Platform
; https://developer.twitter.com/

App 생성과 함께 보이는 4개의 키 값을 복사한 후,

api_key = 'Fy...[생략]...HA'
api_secret = '4p...[생략]...vE'
access_token = '17...[생략]...1c'
access_token_secret = 'mV...[생략]...OK'

다음과 같이 tweepy 모듈로 간단하게 코딩!

import tweepy

auth = tweepy.OAuthHandler(api_key, api_secret)
auth.set_access_token(access_token, access_token_secret)

api = tweepy.API(auth)

keyword = '.NET5'
search = []

tweets = api.search(q=keyword, count=100)
for tweet in tweets:
    search.append(tweet)

data = {}
i = 1
print('[' + keyword + '에 대한 트윗 글')

for tweet in search:
    data['text'] = tweet.text
    print(i, ':', data)
    i += 1




연관은 없지만, 마찬가지로 SNS 검색이라는 관점에서 "NAVER 블로그 검색"도 해볼까요? ^^ 우선 개발자 플랫폼에,

Documents > 서비스 API > 검색
; https://developers.naver.com/docs/serviceapi/search/blog/blog.md#%EB%B8%94%EB%A1%9C%EA%B7%B8

Open API 이용 신청을 하고, App 생성 시 발급받는 client_key와 client_secret을 복사 후,

client_key = 'Uu...[생략]...gc'
client_secret = 'lc...[생략]...1w'

이렇게 간단하게 코딩 완료!

import urllib.parse
from urllib import request, parse
import json

query = '.NET5'
encText = urllib.parse.quote(query)

blog_search_url = 'https://openapi.naver.com/v1/search/blog?query=' + encText + '&display=100'

req = request.Request(blog_search_url)
req.add_header("X-Naver-Client-Id", client_key)
req.add_header("X-Naver-Client-Secret", client_secret)

resp = request.urlopen(req)
resp_code = resp.getcode()

if resp_code == 200:
    response_body = resp.read()
    dataList = json.loads(response_body)

    for data in dataList['items']:
        print(data['title'])
        print('[' + data['description'] + ']')
else:
    print('오류코드: ' + resp_code)




[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 8/15/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2010-11-24 04시48분
[스티플의추억] 트위터 OAuth 의 인증 토큰 획득과 인증 토큰을 사용해서 api 쓰는 과정을 분석하실 분들은

http://www.voiceoftech.com/swhitley/index.php/2009/03/twitter-oauth-with-net/
위의 링크에 있는 소스코드가 심플해서 도움이 되실듯...

제 경우에는 twitterizer의 OAuth 내부 구현이 방대하고, 복잡한 편이라...
흐름의 줄을 놓치기 쉽상이더군요.
[손님]
2010-11-24 04시53분
[스티플의추억] xAuth라는게 10월달인가에 막힌 BasicAuth를 말하는것인가 보군요.
완전히 막힌줄 알았는데, 내부 심사를 거쳐서 허용을 해주는가 보군요.
제 생각에는 기존에 BasicAuth를 쓰던 대형서비스의 마이그레이션 기간을 주기 위해서, 남겨 놓은거 같네요.
신규 서비스는 OAuth를 무조건 써야 할테니 BasicAuth는 잊어버려도 될겁니다.
[손님]
2010-11-25 12시45분
xAuth에 대해서 오해가 있으신 것 같습니다. 제 글에서 소개한 "http://dreamofblue.tistory.com/73"에서도 스마트 폰 애플리케이션의 경우에는 oAuth 적용이 그다지 매끄럽지 않다는 경험을 적고 있습니다. 실제로, GetRequestToken 메서드에 의해서 전달되는 callback URL이 스마트 폰의 애플리케이션에 직접 연관시킬 수 있는 방법이 없기 때문에 사용자는 PIN 정보를 기억해서 응용 프로그램에 적어줘야 하는 문제 제기는 올바른 것 같습니다. xAuth가 필요한 부분이죠.

그리고, 10월에 막힌 방법은 HTTP의 Basic 인증 방식을 사용한 것으로 그것이 xAuth를 의미하는 것은 아닙니다. xAuth 역시 oAuth와 같은 인증 방식입니다.

About xAuth
; http://dev.twitter.com/pages/xauth

트위터의 위의 글에서도 공식적으로 언급하고 있지만, xAuth는 데스크 탑 및 모바일 응용 프로그램을 위한 적절한 인증 방식입니다.
정성태
2010-12-15 09시47분
[스티플의추억] 아~~ 제가 용어를 잘 몰라서 혼동했었군요.
xAuth라는게 윈폼(?)의 인증을 말하는것이로군요.
[손님]
2012-04-26 10시35분
OAuth 1.0에 대해서 이렇게 잘 설명한 포스트가 있을까요? ^^

OAuth와 춤을
; http://helloworld.naver.com/helloworld/24942
정성태
2015-09-03 04시58분
정성태
2022-05-26 10시32분
정성태

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13094정성태7/6/202218오류 유형: 816. Golang - "short write" 오류 원인
13093정성태7/5/202257.NET Framework: 2029. C# - HttpWebRequest로 localhost 접속 시 2초 이상 지연
13092정성태7/3/2022121.NET Framework: 2028. C# - HttpWebRequest의 POST 동작 방식파일 다운로드1
13091정성태7/3/202279.NET Framework: 2027. C# - IPv4, IPv6를 모두 지원하는 서버 소켓 생성 방법
13090정성태6/29/202260오류 유형: 815. PyPI에 업로드한 패키지가 반영이 안 되는 경우
13089정성태6/28/202280개발 환경 구성: 646. HOSTS 파일 변경 시 Edge 브라우저에 반영하는 방법
13088정성태6/27/202258개발 환경 구성: 645. "Developer Command Prompt for VS 2022" 명령행 환경의 폰트를 바꾸는 방법
13087정성태6/23/2022129스크립트: 41. 파이썬 - FastAPI / uvicorn 호스팅 환경에서 asyncio 사용하는 방법
13086정성태6/22/2022238.NET Framework: 2026. C# 11 - 문자열 보간 개선 2가지파일 다운로드1
13085정성태6/22/2022192.NET Framework: 2025. C# 11 - 원시 문자열 리터럴(raw string literals)파일 다운로드1
13084정성태6/21/202270개발 환경 구성: 644. Windows - 파이썬 2.7을 msi 설치 없이 구성하는 방법
13083정성태6/20/2022195.NET Framework: 2024. .NET 7에 도입된 GC의 메모리 해제에 대한 segment와 region의 차이점
13082정성태6/19/2022117.NET Framework: 2023. C# - Process의 I/O 사용량을 보여주는 GetProcessIoCounters Win32 API파일 다운로드1
13081정성태6/17/2022192.NET Framework: 2022. C# - .NET 7 Preview 5 신규 기능 - System.IO.Stream ReadExactly / ReadAtLeast파일 다운로드1
13080정성태6/17/2022129개발 환경 구성: 643. Visual Studio 2022 17.2 버전에서 C# 11 또는 .NET 7.0 preview 적용
13079정성태6/17/202296오류 유형: 814. 파이썬 - Error: The file/path provided (...) does not appear to exist
13078정성태6/16/2022188.NET Framework: 2021. WPF - UI Thread와 Render Thread파일 다운로드1
13077정성태6/15/2022116스크립트: 40. 파이썬 - postgresql 환경 구성
13075정성태6/15/2022201Linux: 50. Linux - apt와 apt-get의 차이 [2]
13074정성태6/13/2022205.NET Framework: 2020. C# - NTFS 파일에 사용자 정의 속성값 추가하는 방법파일 다운로드1
13073정성태6/12/2022244Windows: 207. Windows Server 2022에 도입된 WSL 2
13072정성태6/10/2022157Linux: 49. Linux - ls 명령어로 출력되는 디렉터리 색상 변경 방법
13071정성태6/9/2022200스크립트: 39. Python에서 cx_Oracle 환경 구성
13070정성태6/8/2022243오류 유형: 813. Windows 11에서 입력 포커스가 바뀌는 문제 [1]
13069정성태5/26/2022495.NET Framework: 2019. C# - .NET에서 제공하는 3가지 Timer 비교
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...