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

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

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

naver 소켓 로그인
; https://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:techsharer@outlook.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)
12229정성태6/13/202055.NET Framework: 910. USB/IP PROJECT를 이용해 C#으로 USB Keyboard + Mouse 가상 장치 만들기
12228정성태6/12/202047.NET Framework: 909. C# - Source Generator를 적용한 XmlCodeGenerator파일 다운로드1
12227정성태6/12/2020108오류 유형: 616. Visual Studio의 느린 업데이트 속도에 대한 원인 분석 [1]
12226정성태6/12/202043개발 환경 구성: 493. OpenVPN의 네트워크 구성파일 다운로드1
12225정성태6/11/202038개발 환경 구성: 492. 윈도우에 OpenVPN 설치 - 클라이언트 측 구성
12224정성태6/11/202047개발 환경 구성: 491. 윈도우에 OpenVPN 설치 - 서버 측 구성
12223정성태6/9/2020102.NET Framework: 908. C# - Source Generator 소개파일 다운로드1
12222정성태6/3/202042VS.NET IDE: 146. error information: "CryptQueryObject" (-2147024893/0x80070003)
12221정성태6/3/202056Windows: 168. 비어 있지 않은 디렉터리로 symbolic link(junction) 연결하는 방법
12220정성태6/3/202064.NET Framework: 907. C# DLL로부터 TLB 및 C/C++ 헤더 파일(TLH)을 생성하는 방법
12219정성태6/1/2020170.NET Framework: 906. C# - lock (this), lock (typeof(...))를 사용하면 안 되는 이유파일 다운로드1
12218정성태5/31/2020101.NET Framework: 905. C# - DirectX 게임 클라이언트 실행 중 키보드 입력을 감지하는 방법
12217정성태5/24/202053오류 유형: 615. Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
12216정성태5/15/2020169.NET Framework: 904. USB/IP PROJECT를 이용해 C#으로 USB Keyboard 가상 장치 만들기
12215정성태5/12/2020189개발 환경 구성: 490. C# - (Wireshark의) USBPcap을 이용한 USB 패킷 모니터링파일 다운로드1
12214정성태5/5/2020136개발 환경 구성: 489. 정식 인증서가 있는 경우 Device Driver 서명하는 방법 (2) - UEFI/SecureBoot
12213정성태5/3/2020245개발 환경 구성: 488. (코드로 가상 USB 장치를 만들 수 있는) USB/IP PROJECT 소개
12212정성태5/1/2020122개발 환경 구성: 487. UEFI / Secure Boot 상태인지 확인하는 방법
12211정성태4/27/2020233개발 환경 구성: 486. WSL에서 Makefile로 공개된 리눅스 환경의 C/C++ 소스 코드 빌드
12210정성태4/20/2020332.NET Framework: 903. .NET Framework의 Strong-named 어셈블리 바인딩 (1) - app.config을 이용한 바인딩 리디렉션 [1]파일 다운로드1
12209정성태4/13/2020187오류 유형: 614. 리눅스 환경에서 C/C++ 프로그램이 Segmentation fault 에러가 발생한 경우 (2)
12208정성태4/12/2020232Linux: 29. 리눅스 환경에서 C/C++ 프로그램이 Segmentation fault 에러가 발생한 경우
12207정성태4/2/2020217스크립트: 19. Windows PowerShell의 NonInteractive 모드
12206정성태4/2/2020299오류 유형: 613. 파일 잠금이 바로 안 풀린다면? - The process cannot access the file '...' because it is being used by another process.
12205정성태4/2/2020194스크립트: 18. Powershell에서는 cmd.exe의 명령어를 지원하진 않습니다.
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...