Microsoft MVP성태의 닷넷 이야기
웹: 24. 네이버는 어떻게 로그인 처리를 할까요? [링크 복사], [링크+제목 복사]
조회: 42333
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 2개 있습니다.)

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

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

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 프로토콜 문서에 따라 직접 구현할 것입니다. ^^)




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 5/11/2021]

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

비밀번호

댓글 작성자
 



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

... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...
NoWriterDateCnt.TitleFile(s)
12880정성태12/16/20217058VS.NET IDE: 170. Visual Studio에서 .NET Core/5+ 역어셈블 소스코드 확인하는 방법
12879정성태12/16/202113299오류 유형: 774. Windows Server 2022 + docker desktop 설치 시 WSL 2로 선택한 경우 "Failed to deploy distro docker-desktop to ..." 오류 발생
12878정성태12/15/20218352개발 환경 구성: 617. 윈도우 WSL 환경에서 같은 종류의 리눅스를 다중으로 설치하는 방법
12877정성태12/15/20217013스크립트: 36. 파이썬 - pymysql 기본 예제 코드
12876정성태12/14/20216838개발 환경 구성: 616. Custom Sources를 이용한 Azure Monitor Metric 만들기
12875정성태12/13/20216555스크립트: 35. python - time.sleep(...) 호출 시 hang이 걸리는 듯한 문제
12874정성태12/13/20216569오류 유형: 773. shell script 실행 시 "$'\r': command not found" 오류
12873정성태12/12/20217687오류 유형: 772. 리눅스 - PATH에 등록했는데도 "command not found"가 나온다면?
12872정성태12/12/20217473개발 환경 구성: 615. GoLang과 Python 빌드가 모두 가능한 docker 이미지 만들기
12871정성태12/12/20217584오류 유형: 771. docker: Error response from daemon: OCI runtime create failed
12870정성태12/9/20216168개발 환경 구성: 614. 파이썬 - PyPI 패키지 만들기 (4) package_data 옵션
12869정성태12/8/20218423개발 환경 구성: 613. git clone 실행 시 fingerprint 묻는 단계를 생략하는 방법
12868정성태12/7/20216996오류 유형: 770. twine 업로드 시 "HTTPError: 400 Bad Request ..." 오류 [1]
12867정성태12/7/20216684개발 환경 구성: 612. 파이썬 - PyPI 패키지 만들기 (3) entry_points 옵션
12866정성태12/7/202114053오류 유형: 769. "docker build ..." 시 "failed to solve with frontend dockerfile.v0: failed to read dockerfile ..." 오류
12865정성태12/6/20216752개발 환경 구성: 611. 파이썬 - PyPI 패키지 만들기 (2) long_description, cmdclass 옵션
12864정성태12/6/20215208Linux: 46. WSL 환경에서 find 명령을 사용해 파일을 찾는 방법
12863정성태12/4/20217134개발 환경 구성: 610. 파이썬 - PyPI 패키지 만들기
12862정성태12/3/20215877오류 유형: 768. Golang - 빌드 시 "cmd/go: unsupported GOOS/GOARCH pair linux /amd64" 오류
12861정성태12/3/20218106개발 환경 구성: 609. 파이썬 - "Windows embeddable package"로 개발 환경 구성하는 방법
12860정성태12/1/20216193오류 유형: 767. SQL Server - 127.0.0.1로 접속하는 경우 "Access is denied"가 발생한다면?
12859정성태12/1/202112382개발 환경 구성: 608. Hyper-V 가상 머신에 Console 모드로 로그인하는 방법
12858정성태11/30/20219643개발 환경 구성: 607. 로컬의 USB 장치를 원격 머신에 제공하는 방법 - usbip-win
12857정성태11/24/20217081개발 환경 구성: 606. WSL Ubuntu 20.04에서 파이썬을 위한 uwsgi 설치 방법
12856정성태11/23/20218892.NET Framework: 1121. C# - 동일한 IP:Port로 바인딩 가능한 서버 소켓 [2]
12855정성태11/13/20216243개발 환경 구성: 605. Azure App Service - Kudu SSH 환경에서 FTP를 이용한 파일 전송
... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...