oops-OAUTH2
[ class tree: oops-OAUTH2 ] [ index: oops-OAUTH2 ] [ all elements ]

Source for file KAKAO.php

Documentation is available at KAKAO.php

  1. <?php
  2. /**
  3.  * Project: oops\OAUTH2\KAKAO:: Kakao oauth2 pear package<br>
  4.  * File:    KAKAO.php<br>
  5.  * Dependency:
  6.  *   - {@link http://pear.oops.org/docs/li_HTTPRelay.html oops/HTTPRelay}
  7.  *   - {@link http://pear.oops.org/docs/li_myException.html oops/myException}
  8.  *
  9.  * oops\OAUTH2\KAKAO pear package는 Kakao oauth2 login 및 profile 정보를
  10.  * 다루기 위한 library이다.
  11.  *
  12.  * 이 package를 사용하기 위해서는 먼저 Kakao Developers Console 에서
  13.  * Client ID와 Client Secret key를 발급받아야 한다.
  14.  *
  15.  * @category  HTTP
  16.  * @package   oops\OAUTH2
  17.  * @subpackage oops\OAUTH2\KAKAO
  18.  * @author    JoungKyun.Kim <http://oops.org>
  19.  * @copyright (c) 2019, OOPS.org
  20.  * @license   BSD License
  21.  * @example   OAUTH2/tests/kakao.php KAKAO pear package 예제 코드
  22.  * @filesource
  23.  */
  24.  
  25. /**
  26.  * Namespace oops\OAUTH2
  27.  */
  28. namespace oops\OAUTH2;
  29.  
  30. /**
  31.  * import HTTPRelay class
  32.  */
  33. require_once 'HTTPRelay.php';
  34.  
  35.  
  36. /**
  37.  * oops\OAUTH2\KAKAO pear pcakge의 main class
  38.  *
  39.  * OAuth2를 이용하여 KAKAO 로그인을 진행하고, 로그인된 사용자의
  40.  * 정보를 얻어온다.
  41.  *
  42.  * @package oops\OAUTH2
  43.  * @subpackage oops\OAUTH2\KAKAO
  44.  * @author    JoungKyun.Kim <http://oops.org>
  45.  * @copyright (c) 2019, OOPS.org
  46.  * @license   BSD License
  47.  * @example   OAUTH2/tests/kakao.php KAKAO pear 예제 코드
  48.  */
  49. Class KAKAO {
  50.     // {{{ properities
  51.     /**#@+
  52.      * @access private
  53.      */
  54.     /**
  55.      * 세션 이름
  56.      * @var string 
  57.      */
  58.     private $sessid   '_OAUTH2_';
  59.     /**
  60.      * login url
  61.      * @var string 
  62.      */
  63.     private $reqAuth  'https://kauth.kakao.com/oauth/authorize';
  64.     /**
  65.      * token url
  66.      * @var string 
  67.      */
  68.     private $reqToken 'https://kauth.kakao.com/oauth/token';
  69.     /**
  70.      * revoke url
  71.      * @var string 
  72.      */
  73.     private $reqRevoke 'https://kapi.kakao.com/v2/user/logout';
  74.     /**
  75.      * user information url
  76.      * @var string 
  77.      */
  78.     private $reqUser  'https://kapi.kakao.com/v2/user/me';
  79.     /**
  80.      * app information
  81.      * @var stdClass memebr는 다음과 같음
  82.      *    - id     : Kakao REST key
  83.      *    - secret : Kakao ClientSecret key
  84.      *    - callback : 이 class를 호출하는 페이지
  85.      */
  86.     private $app;
  87.     /**
  88.      * SSL type
  89.      * @var string 
  90.      */
  91.     private $proto 'http';
  92.     /**#@-*/
  93.     /**
  94.      * KAKAO 로그인에 필요한 session 값
  95.      * @access public
  96.      * @var stdClass 
  97.      */
  98.     public $sess;
  99.     // }}}
  100.  
  101.     // {{{ +-- public (void) __construct ($v)
  102.     /**
  103.      * Kakao 로그인 인증 과정을 수행한다. 인증 과정 중에
  104.      * 에러가 발생하면 myException 으로 에러 메시지를
  105.      * 보낸다.
  106.      *
  107.      * logout 시에 globale 변수 $_OAUTH2_LOGOUT_TEMPALTE_ 로 사용자 logout template
  108.      * 을 지정할 수 있다. template 파일은 pear/OAUTH2/login.template 를 참조하면 된다.
  109.      *
  110.      * @access public
  111.      * @param stdClass $v 
  112.      *    - id       발급받은 Kakao REST key
  113.      *    - secret   발급받은 Kakao ClientScret key
  114.      *    - callback 이 클래스가 호출되는 url
  115.      * @return void 
  116.      */
  117.     function __construct ($v{
  118.         if isset ($_SESSION[$this->sessid]) ) {
  119.             $_SESSION[$this->sessidnew \stdClass;
  120.             $_SESSION[$this->sessid]->appId = (object) $v;
  121.         }
  122.         $this->sess = &$_SESSION[$this->sessid];
  123.         $this->app = (object) $v;
  124.  
  125.         if isset ($_SERVER['HTTPS']) )
  126.             $this->proto .= 's';
  127.  
  128.         if isset ($_GET['logout']) ) {
  129.             $this->reqLogout ();
  130.             return;
  131.         }
  132.  
  133.         $this->checkError ();
  134.         $this->reqLogin ();
  135.         $this->reqAccessToken ();
  136.     }
  137.     // }}}
  138.  
  139.     // {{{ +-- private (string) mkToken (void)
  140.     /**
  141.      * 세션 유지를 위한 token 값
  142.      *
  143.      * @access private
  144.      * @return string 
  145.      */
  146.     private function mkToken ({
  147.         $mt microtime ();
  148.         $rand mt_rand ();
  149.         return md5 ($mt $rand);
  150.     }
  151.     // }}}
  152.  
  153.     // {{{ +-- private (void) reqLogin (void)
  154.     /**
  155.      * 로그인 창으로 redirect
  156.      *
  157.      * @access private
  158.      * @return void 
  159.      */
  160.     private function reqLogin ({
  161.         $app &$this->app;
  162.         $this->sess->state $this->mkToken ();
  163.  
  164.         if $_GET['code'|| isset ($this->sess->oauth)  )
  165.             return;
  166.  
  167.         $url sprintf (
  168.             '%s?client_id=%s&response_type=code&redirect_uri=%s&state=%s',
  169.             $this->reqAuth$app->id,
  170.             rawurlencode ($app->callback)$this->sess->state
  171.         );
  172.  
  173.         Header ('Location: ' $url);
  174.         exit;
  175.     }
  176.     // }}}
  177.  
  178.     // {{{ +-- private (void) reqAccessToken (void)
  179.     /**
  180.      * Authorization code를 발급받아 session에 등록
  181.      *
  182.      * KAKAO::$sess->oauth 를 stdClass로 생성하고 다음의
  183.      * member를 등록한다.
  184.      *
  185.      *   - access_token:      발급받은 access token. expires_in(초) 이후 만료
  186.      *   - refresh_token:     access token 만료시 재발급 키 (14일 expire)
  187.      *   - token_type:        Bearer or MAC
  188.      *   - expires_in:        access token 유효시간(초)
  189.      *   - error:             error code
  190.      *   - error_description: error 상세값
  191.      *
  192.      * @access private
  193.      * @return void 
  194.      */
  195.     private function reqAccessToken ({
  196.         $sess &$this->sess;
  197.         $app &$this->app;
  198.  
  199.         if $_GET['code'|| isset ($sess->oauth) )
  200.             return;
  201.  
  202.         $post array (
  203.             'code' => $_GET['code'],
  204.             'client_id' => $app->id,
  205.             'client_secret' => $app->secret,
  206.             'redirect_uri' => $app->callback,
  207.             'grant_type' => 'authorization_code'
  208.         );
  209.  
  210.         $http new \HTTPRelay;
  211.         $buf $http->fetch ($this->reqToken10''$post);
  212.         $r json_decode ($buf);
  213.  
  214.         if $r->error )
  215.             $this->error ($r->error_description);
  216.         
  217.         $sess->oauth $r;
  218.     }
  219.     // }}}
  220.  
  221.     // {{{ +-- private (void) checkError (void)
  222.     /**
  223.      * 에러 코드가 존재하면 에러 처리를 한다.
  224.      *
  225.      * @access private
  226.      * @return void 
  227.      */
  228.     private function checkError ({
  229.         $sess &$this->sess;
  230.  
  231.         if $_GET['error')
  232.             $this->error ($_GET['error_description']);
  233.  
  234.         if $_GET['state'&& $_GET['state'!= $sess->state )
  235.             $this->error ('Invalude Session state: ' $_GET['state']);
  236.     }
  237.     // }}}
  238.  
  239.     // {{{ +-- private (void) error ($msg)
  240.     /**
  241.      * 에러를 Exception 처리한다.
  242.      *
  243.      * @access private
  244.      * @return void 
  245.      */
  246.     private function error ($msg{
  247.         $msg $_SERVER['HTTP_REFERER'"\n" $msg;
  248.         throw new \myException ($msgE_USER_ERROR);
  249.     }
  250.     // }}}
  251.  
  252.     // {{{ +-- private (string) redirectSelf ($noafter = false)
  253.     /**
  254.      * 현재 URL에 after argument를 set한다.
  255.      *
  256.      * @access private
  257.      * @param bool (optional) true로 설정시에, after parameter를
  258.      *              추가하지 않는다.
  259.      * @return string 
  260.      */
  261.     private function redirectSelf ($noafter false{
  262.         if trim ($_SERVER['QUERY_STRING']) )
  263.             $qs sprintf ('?%s&after'$_SERVER['QUERY_STRING']);
  264.         else
  265.             $qs '?after';
  266.  
  267.         $req preg_replace ('/\?.*/'''trim ($_SERVER['REQUEST_URI']));
  268.         if $req $req '/';
  269.  
  270.         return sprintf (
  271.             '%s://%s%s%s',
  272.             $this->proto$_SERVER['HTTP_HOST']$req$qs
  273.         );
  274.     }
  275.     // }}}
  276.  
  277.     // {{{ +-- public (stdClass) Profile (void)
  278.     /**
  279.      * 로그인 과정이 완료되면 발급받은 oops\OAUTH2\KAKAO::$sess->oauth 에
  280.      * 등록된 키를 이용하여 로그인 사용자의 정보를 가져온다.
  281.      *
  282.      * @access public
  283.      * @return stdClass 다음의 object를 반환
  284.      *    - id     사용자 UID
  285.      *    - name   사용자 별칭
  286.      *    - email  KAKAO 인증 이메일 (없을 수 있다)
  287.      *             verified email 을 원하면, r->kakao_account->is_email_verified 를 이용
  288.      *    - img    프로필 사진 URL 정보
  289.      *    - r      KAKAO profile, kakao_account 원본 값
  290.      */
  291.     public function Profile ({
  292.         $sess &$this->sess;
  293.  
  294.         if isset ($sess->oauth) )
  295.             return false;
  296.  
  297.         $req $sess->oauth->token_type ' ' $sess->oauth->access_token;
  298.         $header array (
  299.             'Authorization' => $req,
  300.             'Content_type' => 'application/x-www-form-urlencoded;charset=utf-8'
  301.         );
  302.  
  303.         $http new \HTTPRelay ($header);
  304.         $buf $http->fetch ($this->reqUser);
  305.  
  306.         if $buf )
  307.             $this->error (sprintf ('[OAUTH2] Failed get user profile for %s'__CLASS__));
  308.  
  309.         # V1
  310.         #stdClass Object
  311.         #(
  312.         #   [kaccount_email]
  313.         #   [kaccount_email_verified]
  314.         #   [id]
  315.         #   [properties] = stdClass Object
  316.         #   (
  317.         #       [profile_image]
  318.         #       [nickname]
  319.         #       [thumbnail_image]
  320.         #   )
  321.         #)
  322.         #
  323.         # V2
  324.         #stdClass Object
  325.         #(
  326.         #    [id]
  327.         #    [has_signed_up]
  328.         #    [properties] => stdClass Object
  329.         #        (
  330.         #            [nickname]
  331.         #            [profile_image]
  332.         #            [thumbnail_image]
  333.         #        )
  334.         #    [kakao_account] => stdClass Object
  335.         #        (
  336.         #            [has_email]
  337.         #            [is_email_valid]
  338.         #            [is_email_verified]
  339.         #            [email]
  340.         #            [has_age_range]
  341.         #            [has_birthday]
  342.         #            [has_gender]
  343.         #        )
  344.         #)
  345.         $r json_decode ($buf);
  346.  
  347.         $re array (
  348.             'id'    => $r->id,
  349.             'name'  => $r->properties->nickname,
  350.             'email' => ($r->kakao_account->has_email && $r->kakao_account->is_email_valid == $r->kakao_account->email '',
  351.             'img'   => preg_replace ('/^http:/''https:'$r->properties->profile_image),
  352.             'r'     => (object) array (
  353.                 'properties' => $r->properties,
  354.                 'kakao_account' => $r->kakao_account
  355.             )
  356.         );
  357.  
  358.         return (object) $re;
  359.     }
  360.     // }}}
  361.  
  362.     // {{{ +-- public (void) reqLogout (void)
  363.     /**
  364.      * Kakao 로그인의 authorization key를 만료 시키고
  365.      * 세션에 등록된 정보(oops\OAUT2\KAKAO::$sess)를 제거한다.
  366.      *
  367.      * @access public
  368.      * @return void 
  369.      */
  370.     public function reqLogout ({
  371.         $sess &$this->sess;
  372.         $app &$this->app;
  373.  
  374.         if isset ($sess->oauth) )
  375.             return;
  376.  
  377.         if isset ($_GET['after']) ) {
  378.             $req $sess->oauth->token_type ' ' $sess->oauth->access_token;
  379.             $header array ('Authorization' => $req);
  380.  
  381.             $http new \HTTPRelay ($header);
  382.             $buf $http->fetch ($this->reqRevoke10);
  383.  
  384.             if trim ($_SERVER['QUERY_STRING']) )
  385.                 $qs sprintf ('?%s&after'$_SERVER['QUERY_STRING']);
  386.             else
  387.                 $qs '?after';
  388.  
  389.             $redirect $_SERVER['SCRIPT_URI'$qs;
  390.  
  391.             $logoutDocPath 'OAUTH2/logout-agree.template';
  392.             if $GLOBALS['_OAUTH2_LOGOUT_TEMPALTE_'{
  393.                 if file_exists ($GLOBALS['_OAUTH2_LOGOUT_TEMPALTE_']) )
  394.                     $logoutDocPath $GLOBALS['_OAUTH2_LOGOUT_TEMPALTE_'];
  395.             }
  396.             $logoutDoc file_get_contents ($logoutDocPathtrue);
  397.             $src array (
  398.                 '/{%VENDOR%}/',
  399.                 '/{%REDIRECT%}/',
  400.                 '/{%LOGOUT-URL%}/',
  401.                 '/{%WIN-WIDTH%}/',
  402.                 '/{%WIN-HEIGHT%}/',
  403.                 '/{%INTERVAL%}/',
  404.             );
  405.             $dst array (
  406.                 'KAKAO',
  407.                 $redirect,
  408.                 'https://accounts.kakao.com/logout?continue=https://accounts.kakao.com/weblogin/account',
  409.                 6002501000
  410.             );
  411.             $logoutDoc preg_replace ($src$dst$logoutDoc);
  412.             echo $logoutDoc;
  413.             exit;
  414.         }
  415.  
  416.         unset ($_SESSION[$this->sessid]);
  417.     }
  418.     // }}}
  419. }
  420.  
  421. /*
  422.  * Local variables:
  423.  * tab-width: 4
  424.  * c-basic-offset: 4
  425.  * End:
  426.  * vim600: noet sw=4 ts=4 fdm=marker
  427.  * vim<600: noet sw=4 ts=4
  428.  */
  429. ?>

Documentation generated on Tue, 14 May 2019 02:00:08 +0900 by phpDocumentor 1.4.4