Microsoft MVP성태의 닷넷 이야기
웹: 24. 네이버는 어떻게 로그인 처리를 할까요? [링크 복사], [링크+제목 복사]
조회: 29463
글쓴 사람
정성태 (kevin13@chol.net)
홈페이지
첨부 파일
 

네이버는 어떻게 로그인 처리를 할까요?

마침, 질문하신 분이 계셔서 한번 알아보기로 했습니다. ^^

naver 소켓 로그인
; http://www.sysnet.pe.kr/3/0/986

위의 글에 보면, Naver 로그인 시에 https로 정보가 날아가기 때문에 어떤 식의 HTTP 통신이 발생하는 지 알 수 없다는 것인데요. 하지만 실제로 살펴본 결과, 네이버는 부분적으로 HTTPS 통신과 GZIP 압축을 곁들이기 때문에 패킷을 가로채는 도구로는 알 수 없는 바이너리 데이터가 나타난 것이었습니다.

gzip 압축의 경우에는, IE 설정을 통해서 강제로 gzip 지원을 하지 않는다고 표시를 하면 이후의 통신에서 웹 서버 측에서도 gzip 압축을 사용하지 않기 때문에 패킷 모니터링 도구로 보는 것이 가능합니다. 이를 위해 아래와 같이 HTTP 1.1 통신 설정을 해제해 주면 됩니다.

naver_how_to_login_1.png

물론, 이렇게 했어도 HTTPS 통신의 경우에는 패킷 모니터링 도구로는 답이 없습니다. 하지만, Fiddler의 도움을 받으면 그런 경우에도 문제 없이 패킷 내용을 볼 수 있습니다.

참고로, 이 글에서는 기왕에 fiddler를 사용하는 것이니, HTTP 1.1 해제 옵션은 사용하지 않겠습니다.




자, 이제 네이버를 IE에서 방문한 다음 '로그인' 버튼을 누르는 순간부터 발생하는 HTTP 통신을 캡쳐해 보겠습니다.

naver_how_to_login_2.png

재미있군요. ^^ 곧바로 로그인 처리를 하는 웹 페이지로 폼 데이터를 전송하지 않고 static.nid.naver.com으로부터 keys_js.nhn 파일을 가져오고 있습니다. 내용을 한번 볼까요?

naver_how_to_login_3.png

보시는 것처럼, keys_js.nhn 요청에 대한 응답은 gzip으로 압축되어 있어서 기본적으로 볼 수 없는데요. Fiddler 도구에서는 이를 위해 "Response is encoded and may need to be decoded before inspection. Click here to transform." 영역을 사용자가 누르는 시점에 다음과 같이 정상적으로 압축 해제된 결과를 확인할 수 있도록 해주고 있습니다.

==== 요청 ====
GET http://static.nid.naver.com/enclogin/keys_js.nhn HTTP/1.1
Accept: application/javascript, */*;q=0.8
Referer: http://static.nid.naver.com/login.nhn?svc=me&url=http%3A%2F%2Fwww.naver.com
Accept-Language: ko-KR
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: static.nid.naver.com
Connection: Keep-Alive
Cookie: npic=...[생략]...nid_enctp=1; nid_me=0

==== 응답 ====
HTTP/1.1 200 OK
Server: nginx/0.8.54
Date: Mon, 15 Aug 2011 03:01:54 GMT
Content-Type: text/html
Connection: keep-alive
X-Powered-By: PHP/5.1.4
Expires: -1
Cache-Control: no-store, no-cache, must-revalidate
Cache-Control: post-check=0, pre-check=0
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate
Content-Length: 1551

        sessionkey = 'gOS...[생략]...N0';
        keyname = '1...[생략]...42';
        evalue = '5DF...[생략]...8D0DC5413';
        nvalue = '010001';
    $('encnm').value = '1...[생략]...42';
    if (!sessionkey || !keyname || !evalue || !nvalue) {
    //if (true) {
        if ($('enctp').value == 3) {
                    showErrorDiv2(4);
        } else {
                    showErrorDiv(4);
        }
        $('frmNIDLogin').action="https://nid.naver.com/nidlogin.relogin";
    } else {
        if ($('enctp').value == 1) {
            var rsa = new RSAKey();
            rsa.setPublic(evalue, nvalue);
            $('encpw').value = rsa.encrypt(getLenChar(sessionkey) + sessionkey
                    + getLenChar($('uid').value) + $('uid').value
                    + getLenChar($('upw').value) + $('upw').value);
            $('upw').value = "";
            $('uid').value = "";
            $('frmNIDLogin').submit();
        } else if ($('enctp').value == 2) {
            keySetting('gO...[생략]...001');
            loginClick();
        } else if ($('enctp').value == 3) {
            document.CKKeyPro.E2EInitEx("naver", sessionkey, evalue,nvalue, $('id').value);
            $('encpw').value = document.CKKeyPro.GetEncData("","frmNIDLogin", "pw");
            $('id').value = "";
            $('pw').value = "";
            $('frmNIDLogin').submit();
        }
    }

보시는 것처럼, RSA 암호화에 필요한 값들을 네이버는 '로그인' 버튼이 눌리는 시점에 서버로부터 가져와서 처리를 하고 있습니다. 그리고 나서 enctp 값에 따라 if 문 처리를 하고 있는데, GET 요청에 보면 "nid_enctp=1"값으로 미뤄볼 때 결국 다음과 같은 if 문이 실행되는 것을 짐작할 수 있습니다.

var rsa = new RSAKey();
rsa.setPublic(evalue, nvalue);
$('encpw').value = rsa.encrypt(getLenChar(sessionkey) + sessionkey
        + getLenChar($('uid').value) + $('uid').value
        + getLenChar($('upw').value) + $('upw').value);
$('upw').value = "";
$('uid').value = "";
$('frmNIDLogin').submit();

보시면, 원래의 HTTP Form 필드에서 upw/uid 값은 빈 문자열("")로 초기화 하고 모든 값을 encpw 필드에 rsa.encrypt로 암호화 해서 넣고 있습니다. 아하~~~ 매우 바람직한 방법으로 보입니다. 패킷을 가로채는 악의적인 사용자로 하여금 암호뿐만 아니라 아이디 조차도 알 수 없게 만들어 버린 것입니다.

이렇게 해서 암호화된 encpw 값이 전송되는 부분은 Fiddler의 "/nidlogin.login"에서 찾아볼 수 있습니다.

==== 요청 ====
POST https://nid.naver.com/nidlogin.login HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Referer: http://static.nid.naver.com/login.nhn?svc=me&url=http%3A%2F%2Fwww.naver.com
Accept-Language: ko-KR
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: nid.naver.com
Content-Length: 365
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: npic=...[생략]...nid_enctp=1; nid_me=0

enctp=1&encpw=...[생략]...&encnm=100005842&svctype=0&url=http%3A%2F%2Fwww.naver.com&enc_url=http%253A%252F%252Fwww.naver.com&postDataKey=&saveID=0&nvme=0&smart_level=1&id=&pw=

==== 응답 ====
HTTP/1.1 302 Found
Date: Mon, 15 Aug 2011 03:01:55 GMT
Server: Apache
Set-Cookie: nid_enctp=1; expires=Sun, 13-Nov-2011 03:01:55 GMT; path=/; domain=.nid.naver.com;
Set-Cookie: nid_inf=...[생략]...; path=/; domain=.naver.com;
Set-Cookie: nid_pwa=expired; expires=Wed, 02-Jun-1999 00:00:00 GMT; path=/; domain=.naver.com;
Set-Cookie: adsession=expired; expires=Wed, 02-Jun-1999 00:00:00 GMT; path=/; domain=.naver.com;
Set-Cookie: NID_DH_UI=...[생략]...; path=/; domain=.naver.com;
Set-Cookie: NID_AUT=...[생략]...=; path=/; domain=.naver.com;
Set-Cookie: NID_MATCH_M=...[생략]...; path=/; domain=.naver.com;
Set-Cookie: nid_me=...[생략]...; expires=Sun, 11-May-2014 03:01:55 GMT; path=/; domain=.nid.naver.com;
Set-Cookie: NID_SES=...[생략]...; path=/; domain=.naver.com;
Set-Cookie: nid_sid=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/; domain=.naver.com;
Location: http://static.nid.naver.com/sso/cross-domain.nhn?sid=...[생략]...
Content-Length: 27
Connection: close
Content-Type: text/html

<html><body></body></html>

보시는 것처럼, 폼 데이터를 POST 방식으로 "https://nid.naver.com/nidlogin.login" 페이지로 전송하고 있는데, 개인적으로 여기서 약간 이해가 안되는 부분이 있습니다. 어차피 HTTPS통신을 사용할 거면 굳이 폼 데이터를 RSA 암호화 할 필요는 없었는데,,, 혹시 MITM(Man-in-the-middle Attack) 공격에 대한 대비책인지는 모르겠습니다.

어쨌든, 결과를 살펴보면 전형적인 쿠키 인증 절차로 보입니다. keys_js.nhn 자바 스크립트 파일에서 처리된 encpw 필드 값이 nidlogin.login 으로 전달되고, 그 웹 페이지에서는 (아마도) RSA 개인키를 통해 encpw 필드 값을 복호화해서 사용자가 입력한 계정/암호 값을 기반으로 인증을 할 것입니다. 성공하면, 위의 응답에서 본 것처럼 인증과 관련된 각종 쿠키값을 보내줄 텐데 이후의 HTTP 통신은 이렇게 설정된 쿠키값들을 기반으로 이뤄질 것입니다.

나머지는 그다지 중요한 것 같진 않지만 마저 살펴보면, 위의 응답이 "HTTP/1.1 302 Found"이므로 "Location" 헤더로 설정된 "http://static.nid.naver.com/sso/cross-domain.nhn?sid=...[생략]..." 페이지로 웹 브라우저는 재방문을 해야 합니다.

==== 요청 ====
GET http://static.nid.naver.com/sso/cross-domain.nhn?sid=...[생략]... HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Connection: Keep-Alive
Accept-Language: ko-KR
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Pragma: no-cache
Accept-Encoding: gzip, deflate
Cookie: npic=...[생략]...nid_enctp=1; nid_me=0
Cache-Control: no-cache
Host: static.nid.naver.com

==== 응답 ====
HTTP/1.1 302
Server: nginx/0.8.54
Date: Mon, 15 Aug 2011 04:13:35 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.1.4
Location: http://mstatic.nid.blog.me/sso/cross-domain.nhn?domain_idx=0&sid=...[생략]...
Cache-Control: no-store, no-cache, must-revalidate

0

경로에 sso 라는 이름이 포함된 것으로 보아, Single-sign-on 을 위해 또 다른 웹 페이지를 방문하고 있는 것 같습니다. 위의 응답도 역시 302 redirection이므로 연쇄적으로 또 다른 SSO 경로를 방문하는데, 위의 결과 이외에도 네이버는 다음과 같이 2개의 웹 페이지를 더 방문합니다.

  • http://mstatic.nid.me2day.net/sso/cross-domain.nhn?domain_idx=1&sid=...[생략]...
  • http://static.nid.naver.com/login/sso/finalize.nhn?url=http%3A%2F%2Fwww.naver.com&sid=...[생략]...

정리해 보면, 네이버는 로그인 한번에 인증과 함께 4개의 SSO 를 함께 처리하고 있습니다. 그리고 최종적으로 마지막의 finalize.nhn 까지 와서야 비로소 www.naver.com 으로 이동하도록 302 코드를 반환하는 것으로 끝이 납니다.




살펴보니, 제법 네이버의 로그인 체계가 마음에 듭니다. 혹시나, 자신의 웹 사이트에 로그인을 구현해야 하는 분들이 있다면 위의 과정을 모방해서 구현하는 것도 좋은 선택이 될 수 있겠습니다. ^^

이것으로, 대강의 분석이 끝났으니 다시 처음 질문했던 상황으로 돌아가 볼까요? 문제는 Socket 프로그래밍으로 이것을 어떻게 해야 하느냐 입니다. 음... 간단한 문제가 아니군요. ^^ 시간 관계상 제가 직접 구현할 수는 없지만 대략 다음과 같은 순으로 진행해야 합니다.

  1. 사용자로부터, 로그인 아이디/암호를 입력받는다.
  2. http://static.nid.naver.com/enclogin/keys_js.nhn 에 요청해서 암호화 관련 키들을 담고 있는 js 파일을 받아서 파싱한 후 sessionkey, evalue, nvalue 값을 얻는다.
  3. 자바 스크립트로 되어 있는 RSA 코드를 C/C++로 마이그레이션하고, rsa.encrypt 메서드와 동일한 동작으로 암호화해서 encpw 값을 구함.
  4. ssl 소켓 통신을 이용하여 https://nid.naver.com/nidlogin.login 으로 각종 폼 데이터를 전송하고 반환받은 쿠키값들을 보관

제 역할은 이것으로 끝난 것 같습니다. ^^ 혹시나 위의 구현을 하시는 분이 있다면 블로그를 통해 공개하시는 것도 좋을 것 같습니다. ^^

(참고로, 저더러 네이버 로그인 처리를 구현하라고 한다면? 차라리 웹 브라우저 컨트롤을 하나 내장해서 로그인 완료과정까지 마친 다음 최종 쿠키값만을 별도로 얻도록 구성할 것 같습니다. 만약, 제가 시간이 많은 상황에 놓여 있다면 위에서 설명한 데로 구현을 바꿀 것이고, 더욱 많은 상황에 놓인다면 ssl 프로토콜 문서에 따라 직접 구현할 것입니다. ^^)




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





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

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

비밀번호

댓글 쓴 사람
 



2012-09-13 12시40분
[김영대] 정말 감사드립니다. 잘 배우겠습니다
[손님]
2012-09-18 01시11분
[백유미] 좋은 글 잘 읽었습니다.
[손님]

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
11817정성태2/17/201922오류 유형: 514. WinDbg Preview 실행 오류 - Error : DbgX.dll : WindowsDebugger.WindowsDebuggerException: Could not load dbgeng.dll
11816정성태2/17/201951Windows: 157. 윈도우 스토어 앱(Microsoft Store App)을 명령행에서 직접 실행하는 방법
11815정성태2/14/201939오류 유형: 513. Visual Studio 2019 - VSIX 설치 시 "The extension cannot be installed to this product due to prerequisites that cannot be resolved." 오류 발생
11814정성태2/12/201993오류 유형: 512. VM(가상 머신)의 NT 서비스들이 자동 시작되지 않는 문제
11813정성태2/12/2019119.NET Framework: 809. C# - ("Save File Dialog" 등의) 대화 창에 확장 속성을 보이는 방법
11812정성태2/11/201974오류 유형: 511. Windows Server 2003 VM 부팅 후 로그인 시점에 0xC0000005 BSOD 발생
11811정성태2/11/201984오류 유형: 510. 서버 운영체제에 NVIDIA GeForce Experience 실행 시 wlanapi.dll 누락 문제
11810정성태2/11/201977.NET Framework: 808. .NET Profiler - GAC 모듈에서 GAC 비-등록 모듈을 참조하는 경우의 문제
11809정성태2/11/201993.NET Framework: 807. ClrMD를 이용해 메모리 덤프 파일로부터 특정 인스턴스를 참조하고 있는 소유자 확인
11808정성태2/8/2019342디버깅 기술: 123. windbg - 닷넷 응용 프로그램의 메모리 누수 분석
11807정성태1/29/2019198Windows: 156. 가상 디스크의 용량을 복구 파티션으로 인해 늘리지 못하는 경우
11806정성태1/29/2019195디버깅 기술: 122. windbg - 덤프 파일로부터 PID와 환경 변수 등의 정보를 구하는 방법
11805정성태1/28/2019530.NET Framework: 806. C# - int []와 object []의 차이로 이해하는 제네릭의 필요성 [2]파일 다운로드1
11804정성태1/24/2019188Windows: 155. diskpart - remove letter 이후 재부팅 시 다시 드라이브 문자가 할당되는 경우
11803정성태1/10/2019404디버깅 기술: 121. windbg - 닷넷 Finalizer 스레드가 멈춰있는 현상
11802정성태1/7/2019513.NET Framework: 805. 두 개의 윈도우를 각각 실행하는 방법(Windows Forms, WPF)파일 다운로드1
11801정성태1/1/2019525개발 환경 구성: 427. Netsh의 네트워크 모니터링 기능 [1]
11800정성태12/28/2018346오류 유형: 509. WCF 호출 오류 메시지 - System.ServiceModel.CommunicationException: Internal Server Error
11799정성태12/19/2018619.NET Framework: 804. WPF(또는 WinForm)에서 UWP UI 구성 요소 사용하는 방법 [2]파일 다운로드1
11798정성태12/19/2018824개발 환경 구성: 426. vcpkg - "Building vcpkg.exe failed. Please ensure you have installed Visual Studio with the Desktop C++ workload and the Windows SDK for Desktop C++"
11797정성태12/19/2018362개발 환경 구성: 425. vcpkg - CMake Error: Problem with archive_write_header(): Can't create '' 빌드 오류
11796정성태12/19/2018383개발 환경 구성: 424. vcpkg - "File does not have expected hash" 오류를 무시하는 방법
11795정성태12/19/2018405Windows: 154. PowerShell - Zone 별로 DNS 레코드 유형 정보 조회
11794정성태12/17/2018395오류 유형: 508. Get-AzureWebsite : Request to a downlevel service failed.
11793정성태12/16/2018505개발 환경 구성: 423. NuGet 패키지 제작 - Native와 Managed DLL을 분리하는 방법
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...