3600, //토큰 유효시간 - 1시간 "expire_time" => 3600 * 24 * 3, //토큰 재발행 유효시간 3일. 이후 재로그인 "logging_time" => 3600 * 24 * 31 * 3, //로그보관 시간 3달 (현재미사용 - 나중에 로그쌓이는것 보고 사용여부 결정) ); private const SpareTime = 120; public function __construct( $cate_table="" ){ parent::__construct(); $this->DB->tableExists(TB_token_log); } /** * putToken => 토큰 저장 * @param string $member_id : 로그인된 사용자 아이디 * @param string $auth : 인증코드. 각각의 로그인의 Unique세션으로 사용되며 Default는 요청마다 랜덤으로 새로 생성되지만, 인증키를 사용자 아이디로 고정으로 지정하면 중복접속 방지용으로 사용가능(Single Sign On) * @param array $config : 기본설정 대체환경 */ public function putToken( $member_id, $auth="", $config=Array() ){ //$auth 를 ID로 잡으면 Single Sign On, 그냥두면 Multi login $member_id = trim($member_id); if( !$member_id ) return false; if( defined('_SINGLE_LOGIN_') && _SINGLE_LOGIN_ ){ //Single Sign On 적용 $auth = $member_id; }else{ $auth = $auth ?: bin2hex(openssl_random_pseudo_bytes(self::RdByte)); //인증키 랜덤생성 } $token = md5(self::Ezconst . $auth . uniqid()); //토큰생성 $config = $config + self::$Config; $valid_time = time() + $config['valid_time']; $exprie_time = time() + $config['expire_time']; $this->retireToken($auth); //$sql = "INSERT INTO ". TB_token_log ." (member_id, auth, token, valid_time, expire_time, active, ip) VALUES (?, ?, ?, ?, ?, ?, ?)"; $sqlrow = Array( "member_id" => $member_id, "auth" => $auth, "token" => $token, "valid_time" => $valid_time, "expire_time" => $exprie_time, "Active" => "Y", "ip" => get_ip() ); $rst = $this->DB->insert(TB_token_log, $sqlrow); if( $rst ){ return $token; } return false; } /** * retireToken => 토큰 무효화. 비정상 접근시 사용 * @param string $auth : 인증코드. 해당 인증코드로 생성된 모든 토큰 무효화. */ public function retireToken( $auth ){//모든 $auth 비활성화 처리 //$sql = "UPDATE TB_token_log SET Active = 'N' WHERE Auth = ?"; $sqlrow = Array( "active" => "N", ); $where = new DB_where("and"); $where->add("auth = ?", $auth); $wqry = $where->get_where(); $whereqry = $wqry['qry']; $whererow = $wqry['val']; return $this->DB->update(TB_token_log, $sqlrow, $whereqry, $whererow); } /** * getFromToken => DB에서 토큰 관련정보 획득 * @param string $token : 사용자가 제출한 토큰. */ public function getFromToken( $token ){//Token 정보 Load //$sql = "SELECT USER_ID, Auth, Valid_Time, Expire_Time, Active FROM TB_token_log WHERE Token = ?"; $where = new DB_where("and"); $where->add("token = ?", $token); $wqry = $where->get_where(); $whereqry = $wqry['qry']; $whererow = $wqry['val']; $this->DB->query_record = false; //query log 기록 금지 $sql = "select member_id, auth, valid_time, expire_time, active from ". TB_token_log ." where {$whereqry}"; $row = $this->DB->queryFirstRow($sql, $whererow ); $this->DB->query_record = true; //query log 기록 return $row; } /** * getLastToken => DB에서 Auth 관련 최종정보 획득 * @param string $auth : 요청 인증. */ public function getLastAuth( $auth ){//Token 정보 Load //$sql = "SELECT USER_ID, Auth, Valid_Time, Expire_Time, Active FROM TB_token_log WHERE Token = ?"; $where = new DB_where("and"); $where->add("auth = ?", $auth); $where->add("active = ?", "Y"); $wqry = $where->get_where(); $whereqry = $wqry['qry']; $whererow = $wqry['val']; $this->DB->query_record = false; //query log 기록 금지 $sql = "select top 1 member_id, auth, valid_time, expire_time, active from ". TB_token_log ." where {$whereqry} order by valid_time desc"; $row = $this->DB->queryFirstRow($sql, $whererow ); $this->DB->query_record = true; //query log 기록 return $row; } /** * statusToken => Token의 현재상태 판단 * @param array|string $token_info : 토큰 또는 토큰 정보 * @return string * Y: 인증성공 * N: 인증실패 * E: 연장필요 새로운 Token 발행 * F: 의심되는 접근. 전체 인증 Token 비활성화 */ public function statusToken( $token_info ){//Token 상태 (Y:인증성공) if( !is_array($token_info) ) $token_info = $this->getFromToken($token_info); if( !$token_info['auth'] ) return 'N'; $now = time(); switch( true ){ case ( $token_info['expire_time'] < $now ) : //세션만료 return 'N'; //인증실패 case ( $token_info['valid_time'] >= $now ) : //유효시간 이내 if( trim($token_info['active']) != "Y" ) return "F"; //인증 비활성 필요 return 'Y'; //인증성공 case ( $token_info['expire_time'] >= $now ) : if( trim($token_info['active']) != "Y" ){ if( ($token_info['expire_time'] + self::SpareTime) >= $now ){ return 'Y'; //동시 신호용 인증성공 $spare_time 초간 인정 }else{ return "F"; //인증 비활성 필요 } } return 'E'; //연장요청 } return 'N'; } public function chkToken( $token ){ //$fp = fopen(_UP_PATH_."/token_2t.txt", "a+"); //$fptxt = "=======================".PHP_EOL; //$fptxt .= "NOW=> ".time().PHP_EOL; //$fptxt .= "REQ=> ".print_r($_REQUEST,true).PHP_EOL; $token_info = $this->getFromToken( $token ); $token_stauts = $this->statusToken($token_info); //$fptxt .= "STATUS=> ".$token_stauts.PHP_EOL; switch( $token_stauts ){ case "E" : //토큰 연장 $chk_token = $this->getLastAuth($token_info['auth']); if( $token_info['token'] == $chk_token['token'] ){ $new_token = $this->putToken($token_info['member_id'], $token_info['auth']); } case "Y" : //토큰 유효 $_SESSION['auth_mem_id'] = $token_info['member_id']; break; // 토큰 연장처리 되어 새로운 토큰($new_token) 이 발행되었는데도 갱신하지 않고, // 예전 토큰 그대로 사용하면 중복 인증요청으로 무효토큰 처리되니 // 새로운 토큰이 발행되면 다음 요청시엔 꼭 새로운 토큰으로 교체하는 과정이 필요. // APP 에서 요청할때 응답이 필요한 요청이외의 단순 요청부분까지 응답을 필수 확인하여 $new_token 값이 있는지 점검후 교체하는 로직 필수!! case "F" : //토큰 비활성 $this->retireToken( $token_info['auth'] ); case "N" : //토큰 무효 unset($_SESSION['auth_mem_id']); break; } //fputs($fp, $fptxt); //fclose($fp); return $new_token; } /* public static function getTokenLogin( $token, $config=Array() ){//Token 처리 $token_info = self::getToken($token); $status = self::stateToken($token_info); switch( $status ){ case "Y" : $retval = $member; break; case "N" : $retval = false; break; case "E" : $token = self::putToken($token_info['member_id'], $token_info['auth'], $config); $retval = $token; break; case "F" : self::retireToken( $token_info['auth'] ); $retval = false; break; } } */ }