728x90

메일 발송 조건

리눅스 CentOS 6 에서 sendmail 을 설치하고 관련 설정을 한 후 발송 테스트하고 적어둔다.

아래 코드는 회원 정보와 연동한 메일 발송 코드만 발췌한 것이다.

PHP는 자체적으로 메일을 발송하는 기능은 없다.

mail()함수는 메일 발송 폼을 작성하여 SMTP 서버로 전송해주는 역할을 한다.


<?php
$to = $email;
$from = "webmaster@abc.com";
$nameFrom = "AppMaster";

$subject = "비밀번호 변경";
$message ="<table>";
$message.="<tr>";
$message.= "<td>비밀번호는 암호화되어 있으므로, 회원님의 이메일 주소로 임시 비밀번호를 발급해 드립니다.</td>";
$message.="</tr>";
$message.="<tr>";
$message.= "<td>임시 비밀번호로 로그인 후, 반드시 새로운 비밀번호를 설정해 주십시요.</td>";
$message.="</tr>";
$message.="<tr>";
$message.= "<td>임시 비밀번호는  {$newPW}  입니다.</td>";
$message.="</tr>";
$message.="<tr>";
$message.= "<td>Web 사이트로 <a href='http://www.abc.com'>바로 이동</a> 하기</td>";
$message.="</tr>";
$message.="<tr>";
$message.= "<td>본 메일은 <font color=red>발신 전용</font>이라 수신이 불가능합니다.</td>";
$message.="</tr>";
$message.="</table>";

$mailheaders = "Return-Path: $from\r\n";
$mailheaders.= "From: $nameFrom <$from>\r\n";
$mailheaders.= "Content-Type: text/html;charset=utf-8\r\n";
$mailheaders.= "MIME-Version: 1.0\r\n";

mail($to, $subject, $message, $mailheaders, $from);
?>

블로그 이미지

Link2Me

,
728x90

jQuery 와 PHP 를 조합하여 로그인 실패시 접속시도 5회가 넘으면 접속을 차단하고, 관리자한테 문의하여 비밀번호를 초기화 요청할 수도 있고, 5분(지정시간) 경과후 다시 로그인을 시도할 수 있도록 하는 코드를 구현했다.


jQuery 함수만 적어둔다.


$('#login_submit').on('click',function(e){
    e.preventDefault();
    var userID = $('#loginID');
    var userPW = $('#loginPW');

    if(userID.val() ==''){
        alert('아이디를 입력하세요');
        userID.focus();
        return false;
    }

    if(userPW.val() ==''){
        alert('비밀번호를 입력하세요');
        userPW.focus();
        return false;
    }

    if(userPW.val().search(/\s/) != -1){
        alert("비밀번호는 공백없이 입력해주세요.");
        return false;
    }

    $.ajax({
        url:loginpath+'loginChk.php',
        type: 'POST',
        data: {
            userID:$("#loginID").val(),
            password:$('#loginPW').val()
        },
        dataType:'json',
        success:function(msg){
            switch(msg.result){
                case 0:
                    alert('입력한 정보를 다시 한번 확인하세요.');
                    break;
                case 1:
                case 2:
                    alert('로그인 '+msg.result+'회 실패했습니다');
                    break;
                case 3:
                case 4:
                case 5:
                    alert('로그인 '+msg.result+'회 실패했습니다\n 5회 틀릴 경우 접속이 차단됩니다.');
                    break;
                case 10:
                    alert('관리자가 로그인을 차단했습니다.');
                    break;
                case 11:
                    location.replace('index.php'); // 화면 갱신
                    break;
                case 12:
                    alert('가입 승인대기 중입니다.');
                    break;
                case 13:
                    alert('로그인 횟수 초과로 계정이 잠겼습니다.\n5분 후에 다시 로그인 하세요.');
                    break;
                case 21:
                    location.replace('index.php'); // 화면 갱신
                    break;
                default:
                    alert('다시 한번 확인하세요\n 에러메시지 : '+msg.result);
                    break;
            }
        },
        error: function(jqXHR, textStatus, errorThrown){
            alert("arjax error : " + textStatus + "\n" + errorThrown);
        }
    });

});
 


PHP 핵심 함수 코드이고 나머지 함수는 직접 구현하면 된다.

function LoginSuccessChk($userID,$password){ // Web 접속
    global $db;
    if(empty($userID) || empty($password)){
        return 0;
    } else {
        if($this->isUserExisted($userID) ==1){ // 가입된 userID가 존재하면
            if($this->PasswordChk($userID,$password) == 0){ // 패스워드 불일치
                // 1. access_failed_count 가 5 이상이면 access 를 3 으로 세팅한다.
                $rs = $this->updateAccountLock($userID);
                if($rs == 1){
                    // 3. DB에 저장된 시간과 현재 접속한 시간이 5분이 경과되면 자등으로 access=1 로 설정 변경한다.
                    $gaptime = $this->TimeDiffChk($userID);
                    if($gaptime == 1){
                        $this->clearAccountLock($userID);
                        return 0;
                    } else {
                        // 4. 5분이 경과되지 않았으면 메시지를 팝업한다.
                        return 13;
                    }
                } else {
                    // 2. access_failed_count 를 1 을 증가시킨다.
                    $this->plusLoginFailCount($userID);
                    return $this->LoginFailCountChk($userID); // 로그인 실패 횟수 반환
                }
            } else { // 패스워드 일치
                $rs = $this->AccessStatusChk($userID);
                if($rs == 11) { // 로그인 허용
                    // 1. access_failed_count 를 0 으로 초기화한다.
                    $this->clearLoginFailCount($userID);
                    // 2. access 를 1로 초기화한다.
                    $this->clearAccountLock($userID);
                    // 3. 접속일자 및 시간을 수정한다.
                    $this->AccessLog($userID,'');
                    return 11;
                } else if($rs == 13){
                    // DB에 저장된 시간과 현재 접속한 시간이 5분이 경과되면 자등으로 access=1 로 설정 변경한다.
                    $gaptime = $this->TimeDiffChk($userID);
                    if($gaptime == 1) {
                        $this->clearAccountLock($userID);
                        $this->AccessLog($userID,'');
                        return 11; // 로그인 허용
                    } else {
                        return 13; // 로그인 차단
                    }
                } else {
                    return $rs; // 10:불허, 12:승인대기, 13:차단
                }
            }
        } else {
            return 0; // 가입된 userID가 없으면
        }
    }
}


블로그 이미지

Link2Me

,
728x90

MySQLi 객체지향 접속방식으로 코드를 작성한 회원가입과 로그인 예제를 만들었다.

모두 꼼꼼하게 테스트하여 잘 동작함을 확인했다.


안전한 비밀번호 작성 규칙

- 영문자, 숫자, 특수문자 중 2가지 종류를 조합할 때에는 10자리 이상 / 3가지 종류를 조합할 때에는 8자리 이상으로 설정

- 비밀번호는 최소 6개월에 한번씩 변경

- 누구나 쉽게 알 수 있는 비밀번호 사용은 자제


<?php
// DB 정보 입력
define("DB_HOST", "localhost");
define("DB_DATABASE", "test");
define("DB_USER", "root");
define("DB_PASSWORD", "autoset");
?>

<?php
class DBController {
    protected $db; // 변수를 선언한 클래스와 상속받은 클래스에서 참조할 수 있다.

    // 생성자
    function __construct() {
        $this->db = $this->connectDB();
        // construct 메소드는 객체가 생성(인스턴스화)될 때 자동으로 실행되는 특수한 메소드다.
    }

    // 소멸자(destructor)
    function __destruct() {
        mysqli_close($this->connectDB());
    }

    private function connectDB() {
        require_once 'config.php';
        // MySQLi 객체지향 DB 연결
        $conn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE);
        return $conn; // return database handler
    }

}
?>

<?php
class LoginClass extends DBController {
    // class 자식클래스 extends 부모클래스
    // override : 부모 클래스와 자식 클래스가 같은 메소드를 정의했을 경우 자식 클래스가 우선시된다.

    public function hashSSHA($password) {

        $salt = sha1(rand());
        $salt = substr($salt, 0, 10);
        $encrypted = base64_encode(sha1($password . $salt, true) . $salt);
        $hash = array("salt" => $salt, "encrypted" => $encrypted);
        return $hash;
    }

    public function checkhashSSHA($salt, $password) {
        $hash = base64_encode(sha1($password . $salt, true) . $salt);
        return $hash;
    }

    // 회원 정보 신규 입력
    public function storeUser($userID, $userNM, $password, $mobileNO) {
        $hash = $this->hashSSHA($password);
        $encrypted_password = $hash['encrypted']; // encrypted password
        $salt = $hash['salt']; // salt
        $regdate = date("YmdHis");

        $stmt = $this->db->prepare("INSERT INTO members(userID, userNM, passwd, salt, regdate) VALUES(?, ?, ?, ?, ?)");
        $stmt->bind_param("sssss", $userID, $userNM, $encrypted_password, $salt,$regdate);
        $result = $stmt->execute();
        $stmt->close();

        // check for successful store
        if ($result) {
            $user = $this->getUser($userID, $password);
            //echo '<pre>';print_r($user);echo '</pre>';
            $rs = $this->storeUserDetail($user['uid'],$userNM,$userID,$mobileNO);
            if($rs == 1){
                return $user;
            } else {
                return -1;
            }
        } else {
            return -1; // 0
        }
    }

    // 회원 세부 정보 입력
    public function storeUserDetail($relateduid,$userNM,$email,$mobileNO){
        $mobileNO = preg_replace("/[^0-9]/", "", $mobileNO); //전화번호 숫자만 남기고 제거
        $sql ="INSERT INTO member_data(relateduid,userNM,email,mobileNO) VALUES(?, ?, ?, ?)";
        $stmt = $this->db->prepare($sql);
        $stmt->bind_param("isss", $relateduid, $userNM, $email, $mobileNO);
        $result = $stmt->execute();
        $stmt->close();
        if($result){
            return 1;
        } else {
            return 0;
        }
    }


    // 로그인 체크
    public function getUser($userID, $password) {
        $stmt = $this->db->prepare("SELECT * FROM members WHERE userID = ?");
        $stmt->bind_param("s", $userID);

        if ($stmt->execute()) {
            $user = $stmt->get_result()->fetch_assoc();
            $stmt->close();

            // verifying user password
            $salt = $user['salt'];
            $encrypted_password = $user['passwd'];
            $hash = $this->checkhashSSHA($salt, $password);
            // check for password equality
            if ($encrypted_password == $hash) {
                // user authentication details are correct
                return $user;
            }
        } else {
            return NULL;
        }
    }

    // 안드로이드/아이폰 로그인 체크
    public function LoginUserChk($userID,$password,$deviceID){
        if(empty($userID) || empty($password)){
            return 0;
        } else {
            $user = $this->getUser($userID, $password);
            if($user['uid']>0){ // 가입자 정보가 있다면
                // 장치 일련번호 체크
                if($user['phoneSE'] == NULL){
                    // 신규 장치번호 입력(최초 로그인)
                    $this->LoginUserEquipInput($userID,$deviceID);
                    return 1;
                } else {
                    if($user['phoneSE'] === $deviceID){
                        return 1; // 일련번호 일치
                    } else {
                        return -1; //일련번호 불일치
                    }
                }
            } else {
                return 0; // 로그인 실패
            }
        }

    }

    // 장치번호 업데이트
    public function LoginUserEquipInput($userID,$deviceID){
        if(strlen($deviceID)>0 && is_numeric($deviceID)){ // 안드로이드폰
            $ostype = 2;
        } else if(strlen($deviceID)>30){ // 아이폰
            $ostype = 1;
        } else { // 기타
            $ostype = 0;
        }

        $sql='update members set phoneSE=?, OStype=? where userID=?';
        $stmt = $this->db->prepare($sql);
        $stmt->bind_param("sss", $deviceID, $ostype, $userID);
        $status = $stmt->execute();
        if($status == true){
            return 1;
        } else {
            return 0;
        }
    }//end

    // 장치번호 초기화 (관리자용)
    public function EquipReset($userID){
        $ostype = 0;
        $sql='update members set phoneSE=NULL, OStype=? where userID=?';
        $stmt = $this->db->prepare($sql);
        $stmt->bind_param("ss", $ostype, $userID);
        $status = $stmt->execute();
        if($status == true){
            return 1;
        } else {
            return 0;
        }
    }//end

    // 회원 가입 여부 체크
    public function isUserExisted($userID) {
        $stmt = $this->db->prepare("SELECT userID from members WHERE userID = ?");

        $stmt->bind_param("s", $userID);
        $stmt->execute();
        //$stmt->store_result();
        $result = $stmt->get_result();

        if ($result->num_rows > 0) {
            // user existed
            $stmt->free_result();
            $stmt->close();
            return true;
        } else {
            // user not existed
            $stmt->free_result();
            $stmt->close();
            return false;
        }
    }

    // 회원 정보 삭제
    public function deleteUser($userID){
        $stmt = $this->db->prepare("delete FROM members WHERE userID = ?");
        $stmt->bind_param("s", $userID);
        $stmt->execute();
        $stmt->close();
    }

    // 인증이 성공한 상태에서 직접 접속을 시도하는 외부 접속으로부터 보호
    function checkReferer($url) {
        $referer = $_SERVER['HTTP_REFERER'];
        $res = strpos($referer,$url);
        return $res == 0 ? false : true;
    }
}
?>

 <?php
if (!isset($_SESSION)) {
    session_start();
}

// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    //echo '<pre>';print_r($_POST);echo '</pre>'; // 전송받은 배열을 확인하고 싶을 때 사용
    if(isset($userID) && !empty($userID) && isset($password) && !empty($password)) {
        include_once 'config/dbconnect.php';
        require_once 'config/loginClass.php';
        $c = new LoginClass();
        if ($c->isUserExisted($userID) === true) {
            echo '{"result":"0"}'; // echo json_encode(array('result' => '0')); 과 동일
        } else {
            $mobileNO = preg_replace("/[^0-9]/", "", $mobileNO); //전화번호 숫자만 남기고 제거
            // 회원 등록
            $user = $c->storeUser($userID, $userNM, $password, $mobileNO);
            if($user) {// 회원 등록 성공
                $_SESSION['userID'] = $user['userID'];
                $_SESSION['userNM'] = $user['userNM'];
                $_SESSION['admin'] = $user['admin'];
                echo json_encode(array('result' => '1'));
            } else {
                // 회원 등록 실패
                echo json_encode(array('result' => '-1'));
            }
        }
    }else {// 입력받은 데이터에 문제가 있을 경우
        echo json_encode(array('result' => '-2'));
    }
}
?>

 <?php
// 파일을 직접 실행하는 비정상적 동작을 방지 목적
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    if(isset($userID) && !empty($userID) && isset($password) && !empty($password)) {
        include_once 'config/dbconnect.php';
        require_once 'config/loginClass.php';
        $c = new LoginClass();
        $user = $c->getUser($userID, $password);
        //echo '<pre>'; print_r($user);echo '</pre>';
        if ($user != false) {
            $_SESSION['userID'] = $user['userID'];
            $_SESSION['userNM'] = $user['userNM'];
            $_SESSION['admin'] = $user['admin'];
            echo json_encode(array('result' => '1'));
        } else {
            echo json_encode(array('result' => '0'));
        }
    }else {// 입력받은 데이터에 문제가 있을 경우
        echo json_encode(array('result' => '-2'));
    }
} else { // 비정상적인 접속인 경우
    echo 0; // a.loginChk.php 파일을 직접 실행할 경우에는 화면에 0을 찍어준다.
    exit;
}
?>


직접 실행해 볼 수 있는 테이블 구조 및 관련 파일을 모두 첨부한다.

안드로이드에서 로그인하는 코드 샘플도 포함되어 있다.


mysqlI_login.zip



블로그 이미지

Link2Me

,
728x90

코드는 Morden PHP 라고 보기에는 좀 약할 수 있다. 하지만 참고하면 도움이 될 코드가 엄청 많다.

회원가입과 로그인, 최신 패스워드 권장 적용방식, 중복체크가 가능하고 방식은 Ajax 방식으로 처리한다.

jQuery, CSS 코드, PHP Class 함수는 안드로이드폰 로그인과의 연동부분까지 고려되어 있으니 입문 개발자에게는 훌륭한 샘플이 될 것이다.

메인 화면 Layout 구성은 초간단으로 기능 동작 중심이다. 회원가입과 로그인만 제대로 되는지 체크하는 용도라고 보면 된다.


== 회원가입 폼 ===

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="form.css"  />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function(){
    // 회원가입처리
    $('#join-submit').click(function(e){
        e.preventDefault();
        var userNM = $("input[name='userNM']");
        if( userNM.val() =='') {
            alert("성명을 입력하세요");
            userNM.focus();
            return false;
        }

        var email = $("input[name='email']");
        if(email.val() == ''){
            alert('이메일을 입력하세요');
            email.focus();
            return false;
        } else {
            var emailRegex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
            if (!emailRegex.test(email.val())) {
                alert('이메일 주소가 유효하지 않습니다. ex)abc@gmail.com');
                email.focus();
                return false;
            }
        }

        var mobileNO = $("input[name='mobileNO']");
        if(mobileNO.val() ==''){
            alert('휴대폰 번호를 입력하세요');
            mobileNO.focus();
            return false;
        } else if(!/^[0-9]{10,11}$/.test(mobileNO.val())){
            alert("휴대폰 번호는 숫자만 10~11자리 입력하세요.");
            return false;
        } else if(!/^(01[016789]{1}|02|0[3-9]{1}[0-9]{1})([0-9]{3,4})([0-9]{4})$/.test(mobileNO.val())){
            alert("유효하지 않은 전화번호 입니다.");
            return false;
        }

        var password = $("input[name='Password']");
        var repassword = $("input[name='rePassword']");
        if(password.val() =='') {
            alert("비밀번호를 입력하세요!");
            password.focus();
            return false;
        }
        if(password.val().search(/\s/) != -1){
            alert("비밀번호는 공백없이 입력해주세요.");
            return false;
        }
        if(!/^[a-zA-Z0-9!@#$%^&*()?_~]{8,20}$/.test(password.val())){
            alert("비밀번호는 숫자, 영문, 특수문자(!@$%^&*?_~ 만 허용) 조합으로 8~20자리를 사용해야 합니다.");
            return false;
        }
        // 영문, 숫자, 특수문자 2종 이상 혼용
        var chk=0;
        if(password.val().search(/[0-9]/g) != -1 ) chk ++;
        if(password.val().search(/[a-z]/ig)  != -1 ) chk ++;
        if(password.val().search(/[!@#$%^&*()?_~]/g) != -1) chk ++;
        if(chk < 2){
            alert("비밀번호는 숫자, 영문, 특수문자를 두가지이상 혼용하여야 합니다.");
            return false;
        }
        // email이 아닌 userID 인 경우에는 체크하면 유용. email은 특수 허용문자에서 걸러진다.
        /*
        if(password.val().search(userID.val())>-1){
            alert("userID가 포함된 비밀번호는 사용할 수 없습니다.");
            return false;
        }
        */
        if(repassword.val() =='') {
            alert("비밀번호를 다시 한번 더 입력하세요!");
            repassword.focus();
            return false;
        }
        if(password.val()!== repassword.val()){
            alert('입력한 두 개의 비밀번호가 일치하지 않습니다');
            return false;
        }

        //var loginpath =$("#ajaxPath").attr('data-path');
        $.ajax({
            url: 'a.register.php',
            type: 'POST',
            data: {
                userNM:userNM.val(),
                userID:email.val(),
                password:password.val(),
                mobileNO:mobileNO.val()
            },
            dataType: "json", // json, text
            success: function (response) {
                //alert(response); //text 로 하고 a.register.php 에서 print_r을 사용하면 넘어간 데이터를 확인 가능
                if(response.result == 1){
                    alert('가입 완료');
                    location.replace('index.php'); // 화면 갱신
                } else if(response.result == 0){
                    alert('이미 가입된 아이디입니다');
                } else if(response.result == -2){
                    alert('입력 데이터를 확인하세요');
                } else {
                    //alert('등록중에 에러가 발생했습니다' + response);
                    alert('등록중에 에러가 발생했습니다');
                }
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert("arjax error : " + textStatus + "\n" + errorThrown);
            }
        });

    });

    // userID(e-mail) 가입여부 검사
    $("#checkid").click(function(e){
        e.preventDefault();
        var email = $("input[name='email']");
        if(email.val() == ''){
            alert('이메일을 입력하세요');
            email.focus();
            return false;
        } else {
            var emailRegex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
            if (!emailRegex.test(email.val())) {
                alert('이메일 주소가 유효하지 않습니다. ex)abc@gmail.com');
                email.focus();
                return false;
            }
        }

        $.ajax({
            url: 'a.joinChk.php',
            type: 'POST',
            data: {userID:email.val()},
            dataType: "json",
            success: function (msg) {
                //alert(msg); // 확인하고 싶으면 dataType: "text" 로 변경한 후 확인 가능
                if(msg.result == 1){
                    alert('사용 가능합니다');
                } else if(msg.result == 0){
                     alert('이미 가입된 아이디입니다');
                    email.val('');
                }
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert("arjax error : " + textStatus + "\n" + errorThrown);
            }
        });
    });

});
</script>
</head>
<body>
<div class="container">
<h2>회원가입 Form 예제</h2>
<form method="post" action="a.register.php">
    <table>
        <tr>
            <td style='width:100px'>이름</td>
            <td><input type="text" size=37 name="userNM" value=""></td>
        </tr>
        <tr>
            <td>E-Mail</td>
            <td>
                <input type="text" size=25 name="email" value="">
                <input type="button" id="checkid" value="중복체크">
            </td>
        </tr>
        <tr>
            <td>휴대폰번호</td>
            <td><input type="text" size=37 name="mobileNO" value=""></td>
        </tr>
        <tr>
            <td>비밀번호</td>
            <td><input type="password" size=37 name="Password"></td>
        </tr>
        <tr>
            <td>비밀번호(확인)</td>
            <td><input type="password" size=37 name="rePassword"></td>
        </tr>
        <tr>
            <td colspan='2' align='center'>
                <input type="button" id="join-submit" value="회원가입">
            </td>
        </tr>
    </table>
</fom>
</div>
</body>
</html>

 === 회원가입 a.register.php ===

<?php
if (!isset($_SESSION)) {
    session_start();
}

// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    //echo '<pre>';print_r($_POST);echo '</pre>'; // 전송받은 배열을 확인하고 싶을 때 사용
    if(isset($userID) && !empty($userID) && isset($password) && !empty($password)) {
        include_once 'dbController.php';
        require_once 'loginClass.php';
        $c = new LoginClass();
        if ($c->isUserExisted($userID)==1) {
            echo '{"result":"0"}'; // echo json_encode(array('result' => '0')); 과 동일
        } else {
            $mobileNO = preg_replace("/[^0-9]/", "", $mobileNO); //전화번호 숫자만 남기고 제거
            // 회원 등록
            $user = $c->storeUser($userID, $userNM, $password, $mobileNO);
            if ($user) {// 회원 등록 성공
                $_SESSION['userID'] = $user['userID'];
                $_SESSION['admin'] = $user['admin'];
                echo json_encode(array('result' => '1'));
            } else {
                // 회원 등록 실패
                echo json_encode(array('result' => '-1'));
            }
        }
    }else {// 입력받은 데이터에 문제가 있을 경우
        echo json_encode(array('result' => '-2'));
    }
}
?>

 === 로그인 폼 ===

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="form.css"  />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function(){
    $('#login-submit').click(function(e){
        var userID = $("input[name='userID']");
        if( userID.val() =='') {
            alert("아이디를 입력하세요");
            userID.focus();
            return false;
        }

        var password = $("input[name='password']");
        if(password.val() =='') {
            alert("비밀번호를 입력하세요!");
            password.focus();
            return false;
        }
        $.ajax({
            url: 'a.loginChk.php',
            type: 'POST',
            data: {userID:userID.val(), password:password.val()},
            dataType: "json",
            success: function (response) {
                if(response.result == 1){
                    //alert('로그인 성공');
                    location.href='index.php';
                } else if(response.result == -2){
                     alert('입력한 값에 문제가 있습니다');
                } else {
                    alert('로그인 실패');
                }
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert("arjax error : " + textStatus + "\n" + errorThrown);
            }
        });
    });
});
</script>
</head>
<body>
<div class="container">
<h2>로그인 Form 예제</h2>
<form method="post" action="a.loginChk.php">
    <table>
        <tr>
            <td style="font-size:14px">로그인 ID</td>
            <td><input type="text" name="userID" value=""></td>
        </tr>
        <tr>
            <td style="font-size:14px">패스워드</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan='2' align=left>
                <input type="button" id="login-submit" value="로그인">
            </td>
        </tr>
        <tr>
            <td colspan='2'><p>Don't have an account? <a href="joinForm.php">Sign up now</a>.</p></td>
        </tr>
    </table>
</fom>
</div>
</body>
</html>

 === 로그인 체크 ===

<?php
// 파일을 직접 실행하는 비정상적 동작을 방지 하기 위한 목적
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    if(isset($userID) && !empty($userID) && isset($password) && !empty($password)) {
        include_once 'dbController.php';
        require_once 'loginClass.php';
        $c = new LoginClass();
        $user = $c->getUser($userID, $password);
        if ($user != false) {
            $_SESSION['userID'] = $user['userID'];
            $_SESSION['userNM'] = $user['userNM'];
            $_SESSION['admin'] = $user['admin'];
            echo json_encode(array('result' => '1'));
        } else {
            echo json_encode(array('result' => '0'));
        }
    }else {// 입력받은 데이터에 문제가 있을 경우
        echo json_encode(array('result' => '-2'));
    }
} else { // 비정상적인 접속인 경우
    echo 0; // a.loginChk.php 파일을 직접 실행할 경우에는 화면에 0을 찍어준다.
    exit;
}
?>

 == 가입여부 체크 ===

<?php
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    // db 접속파일 include 및 정상 로그인 여부 체크하는 함수 실행후 결과 반환처리 하면 된다.
    include_once 'dbController.php';
    require_once 'loginClass.php';
    $d = new DBController();
    $c = new LoginClass();
    $rs = $c->isUserExisted($userID);
    if($rs == '0'){
        echo '{"result":"1"}';
    } else {
        echo '{"result":"0"}'; // echo json_encode(array('result' => '0')); 과 동일
    }
}
?>


주요코드는 위에 열거하였고 나머지 코드에 대해서는 첨부파일을 참조하면 된다.

첨부파일에는 SQL 테이블 구조까지 포함되어 있고, 100% 동작되는지 여부를 확인한 코드다.

login.zip


인터넷에 올려진 코드가 잘 동작하지 않는 것은 변수명을 살짝 바꾸거나, 한두개 핵심적인 것을 빼먹어서 그런 경우가 참 많다. 코드가 제대로 동작 안되는 것은 본문에 있는 내용과 어떤 부분이 약간 다른지 확인하면 해결될 문제인데, 일단 시도를 안해보는 거 같다. 첨부한 예제는 오래된 책에 나온 구닥다리 PHP 와는 비교되지 않는 좋은 샘플 예제다.

무조건 실행해서 결과가 OK 되는 걸 원하는 건 초보자로서 당연할 수 있다.

하지만 개념 이해를 위한 공부를 해야 내 것이 된다.

login.zip 파일과 login_2.zip 파일 내용을 비교하면서 뭐가 틀려서 안되었던 걸까 하고 이해를 해야 한다.

이해를 하기 위해서는 파일 비교하는 방법을 알아야 한다.

https://link2me.tistory.com/1327 에 AcroDiff.exe 라는 파일이 있다.

최근에 개발자들이 Visual Studio Code 를 선호하여 AcroEdit 라는 툴이 있는지 모를 수 있지만, 이 에디터에 포함된 AcroDiff.exe 파일은 파일의 내용 비교에는 최고의 툴이다.

이걸 이용해서 파일을 서로 비교해보면 어디가 달라져 있고, 무엇 때문에 질문을 했는지 이해가 될 것이다.

login_2.zip


블로그 이미지

Link2Me

,
728x90

회원가입폼과 기가입여부를 체크하는 기능이 포함된 Form 예제다.

jQuery, Ajax 를 사용하여 입력 필드 체크와 휴대폰번호 및 비밀번호 유효성 검사, 이미 가입된 ID인지 여부를 체크하는 기능, 회원가입 자료 저장, 로그인 처리 기능이 포함되어 있다.

첨부파일에는 테이블 구조 SQL 까지 포함되어 있으므로 윈도우 기반 Autoset9 에서 직접 테스트해볼 수 있다.


간략하게 사용법 순서를 적어보면....

1. 테이블 구조 SQL 문을 autoset9 에서 테이블 생성한다.

    members 테이블과 member_data 테이블 두개의 테이블로 나누었고 PHP에서 JOIN 으로 데이터를 처리한다.

 CREATE TABLE IF NOT EXISTS `members` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `userID` varchar(50) NOT NULL DEFAULT '',
  `userNM` varchar(20) NOT NULL,
  `passwd` varchar(80) NOT NULL,
  `salt` varchar(10) NOT NULL,
  `admin` tinyint(2) NOT NULL DEFAULT '0',
  `phoneSE` varchar(80) DEFAULT NULL,
  `OStype` tinyint(2) NOT NULL DEFAULT '0',
  `regdate` char(14) NOT NULL DEFAULT '',
  `curdate` char(14) DEFAULT NULL COMMENT '최근접속',
  PRIMARY KEY (`uid`),
  UNIQUE KEY `userID` (`userID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

 CREATE TABLE IF NOT EXISTS `member_data` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `relateduid` int(11) NOT NULL DEFAULT '0',
  `level` int(11) NOT NULL DEFAULT '0',
  `userNM` varchar(60) NOT NULL COMMENT '이름',
  `email` varchar(60) NOT NULL COMMENT '메일',
  `mobileNO` varchar(20) NOT NULL,
  `officeNO` varchar(20) DEFAULT NULL,
  `homeNO` varchar(20) DEFAULT NULL,
  `home_addr1` varchar(100) DEFAULT NULL,
  `home_addr2` varchar(50) DEFAULT NULL,
  `office_addr1` varchar(100) DEFAULT NULL,
  `office_addr2` varchar(50) DEFAULT NULL,
  `sex` tinyint(2) NOT NULL DEFAULT '0',
  `point` int(11) NOT NULL DEFAULT '0',
  `smart` tinyint(2) NOT NULL DEFAULT '0',
  `modifydate` char(14) NOT NULL DEFAULT '',
  PRIMARY KEY (`uid`),
  KEY `relateduid` (`relateduid`),
  KEY `mobileNO` (`mobileNO`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;


2. 회원가입 폼

    - E-Mail 을 userID 로 사용하고 가입여부를 체크하는 중복체크 jQuery가 포함되어 있다.

    - E-Mail 주소 유효성 체크 기능이 포함되어 있다.

    - 휴대폰 번호 유효성 체크 기능이 포함되어 있다.

    - 비밀번호는 영문, 숫자, 특수문자 등을 혼용하여 가입하도록 했다.



3. 회원가입 처리 로직은 직접 구현해보도록 하여 입력값이 넘어오는지 여부만 체크하는 걸 주석처리했다.

    회원가입 결과를 확인할 수 있도록 내용을 수정 보완했다.

4. LoginForm.php 에서는 로그인 화면을 구현했다.

    로그인 체크 jQuery 와 Ajax 처리하는 걸 추가했다.

5. 회원가입/로그인이 성공하면 index.php 에서 SESSION을 검사하여 main.php 파일로 이동하도록 했다.


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="form.css"  />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function(){
    // 회원가입처리
    $("form").submit(function(){
        var userNM = $("input[name='userNM']");
        if( userNM.val() =='') {
            alert("성명을 입력하세요");
            userNM.focus();
            return false;
        }

        var email = $("input[name='email']");
        if(email.val() == ''){
            alert('이메일을 입력하세요');
            email.focus();
            return false;
        } else {
            var emailRegex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
            if (!emailRegex.test(email.val())) {
                alert('이메일 주소가 유효하지 않습니다. ex)abc@gmail.com');
                email.focus();
                return false;
            }
        }

        var mobileNO = $("input[name='mobileNO']");
        if(mobileNO.val() ==''){
            alert('휴대폰 번호를 입력하세요');
            mobileNO.focus();
            return false;
        } else if(!/^[0-9]{10,11}$/.test(mobileNO.val())){
            alert("휴대폰 번호는 숫자만 10~11자리 입력하세요.");
            return false;
        } else if(!/^(01[016789]{1}|02|0[3-9]{1}[0-9]{1})([0-9]{3,4})([0-9]{4})$/.test(mobileNO.val())){
            alert("유효하지 않은 전화번호 입니다.");
            return false;
        }

        var password = $("input[name='Password']");
        var repassword = $("input[name='rePassword']");
        if(password.val() =='') {
            alert("비밀번호를 입력하세요!");
            password.focus();
            return false;
        }
        if(password.val().search(/\s/) != -1){
            alert("비밀번호는 공백없이 입력해주세요.");
            return false;
        }
        if(!/^[a-zA-Z0-9!@#$%^&*()?_~]{8,20}$/.test(password.val())){
            alert("비밀번호는 숫자, 영문, 특수문자(!@$%^&*?_~ 만 허용) 조합으로 8~20자리를 사용해야 합니다.");
            return false;
        }
        // 영문, 숫자, 특수문자 2종 이상 혼용
        var chk=0;
        if(password.val().search(/[0-9]/g) != -1 ) chk ++;
        if(password.val().search(/[a-z]/ig)  != -1 ) chk ++;
        if(password.val().search(/[!@#$%^&*()?_~]/g) != -1) chk ++;
        if(chk < 2){
            alert("비밀번호는 숫자, 영문, 특수문자를 두가지이상 혼용하여야 합니다.");
            return false;
        }
        // email이 아닌 userID 인 경우에는 체크하면 유용. email은 특수 허용문자에서 걸러진다.
        /*
        if(password.val().search(userID.val())>-1){
            alert("userID가 포함된 비밀번호는 사용할 수 없습니다.");
            return false;
        }
        */
        if(repassword.val() =='') {
            alert("비밀번호를 다시 한번 더 입력하세요!");
            repassword.focus();
            return false;
        }
        if(password.val()!== repassword.val()){
            alert('입력한 두 개의 비밀번호가 일치하지 않습니다');
            return false;
        }

    });

    // userID(e-mail) 가입여부 검사
    $("#checkid").click(function(e){
        e.preventDefault();
        var email = $("input[name='email']");
        if(email.val() == ''){
            alert('이메일을 입력하세요');
            email.focus();
            return false;
        } else {
            var emailRegex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
            if (!emailRegex.test(email.val())) {
                alert('이메일 주소가 유효하지 않습니다. ex)abc@gmail.com');
                email.focus();
                return false;
            }
        }

        $.ajax({
            url: 'a.joinChk.php',
            type: 'POST',
            data: {userID:email.val()},
            dataType: "json",
            success: function (msg) {
                //alert(msg); // 확인하고 싶으면 dataType: "text" 로 변경한 후 확인 가능
                if(msg.result == 1){
                    alert('사용 가능합니다');
                } else if(msg.result == 0){
                     alert('이미 가입된 아이디입니다');
                    email.val('');
                }
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert("arjax error : " + textStatus + "\n" + errorThrown);
            }
        });
    });

});
</script>
</head>
<body>
<div class="container">
<h2>회원가입 Form 예제</h2>
<form method="post" action="a.register.php">
    <table>
        <tr>
            <td style='width:100px'>이름</td>
            <td><input type="text" size=37 name="userNM" value=""></td>
        </tr>
        <tr>
            <td>E-Mail</td>
            <td>
                <input type="text" size=25 name="email" value="">
                <input type="button" id="checkid" value="중복체크">
            </td>
        </tr>
        <tr>
            <td>휴대폰번호</td>
            <td><input type="text" size=37 name="mobileNO" value=""></td>
        </tr>
        <tr>
            <td>비밀번호</td>
            <td><input type="password" size=37 name="Password"></td>
        </tr>
        <tr>
            <td>비밀번호(확인)</td>
            <td><input type="password" size=37 name="rePassword"></td>
        </tr>
        <tr>
            <td colspan='2' align='center'><input type="submit" value="회원가입"></td>
        </tr>
    </table>
</fom>
</div>
</body>
</html>

 body {    
    font-family: 나눔바른고딕, NanumBarunGothic, 맑은고딕, "Malgun Gothic", 돋움, Dotum, "Apple SD Gothic Neo", sans-serif;
    font-size: 12px;
    color: rgb(33, 33, 33);
    letter-spacing: 0.03em;
}

table {
    width: 600px;
}

tr {
    height: 50px;
}

input[type=text], input[type=password] {
    padding: 5px 10px; /* 상하 우좌 */
    margin: 3px 0; /* 상하 우좌 */
    font-family: inherit; /* 폰트 상속 */
    border: 1px solid #ccc; /* 999가 약간 더 진한 색 */
    font-size:14pt;
    box-sizing: border-box;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
    -webkit-transition: width 0.4s ease-in-out;
    transition: width 0.4s ease-in-out;
}

input[type=text]:focus, input[type=password]:focus {
    border: 2px solid #555;
}

input[type=submit],input[type=button] {
    margin-top: 10px;
    width:100px;
    height:40px;
    line-height: 22px;
    padding: 5px, 10px; /* 상하 우좌 */
    background: #E6E6E6;
    color: #000000;
    font-size: 15px;
    font-weight: normal;
    letter-spacing: 1px;
    border: none;
    cursor: pointer;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
}

input[type=submit]:hover {
    background-color: #FFBF00;
}

 <?php
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    // db 접속파일 include 및 정상 로그인 여부 체크하는 함수 실행후 결과 반환처리 하면 된다.
    include_once 'dbController.php'; // http://link2me.tistory.com/1478 참조
    require_once 'loginClass.php';
    $c = new LoginClass();
    $rs = $c->isUserExisted($userID);
    if($rs == '0'){
        echo '{"result":"1"}';
    } else {
        echo '{"result":"0"}'; // echo json_encode(array('result' => '0')); 과 동일
    }
}
?>

<?php
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    echo '<pre>';print_r($_POST);echo '</pre>';
}
?>


LoginClass 함수 만든 예제다.

<?php
class LoginClass extends DBController {
    // 회원 가입 여부 체크
    public function isUserExisted($u) {
        if(!isset($u) || empty($u)) {
            return '-1';
        } else {
            $sql ="SELECT count(userID) from members WHERE userID='".$u."'";
            $result = mysqli_query($this->db, $sql);
            if($row = mysqli_fetch_array($result)){
                return $row[0]; // 미가입이면 0 반환, 가입이면 1 반환
            } else {
                return -1;
            }
        }
    }

}//end class LoginClass

?> 


직접 테스트해보고 싶은 개발자의 타이핑 노고를 덜어주고자 테스트한 파일을 첨부한다.

Last Update : 2019.04.12 첨부파일 내용 보강(초보자를 위한 배려)


ex02.zip


https://link2me.tistory.com/1481 읽어보고 첨부된 예제를 받아서 분석하고 이해하면 많은 도움이 될 것이다.


도움되신 분은 공감 팍팍 눌러주세요.

블로그 이미지

Link2Me

,
728x90

PHP 에서 내용을 다른 파일로 전달하는 방법으로 가장 기본 예제가 로그인 예제다.

네이버 지식인에 올라오는 공통적인 질의사항에 대한 개념이해를 돕기 위함이다.

HTML5 기반 jQuery 입력 필드 체크, 모바일 환경을 고려했다.

<meta name="viewport" content="width=device-width, initial-scale=1"> 이 부분이 모바일 환경을 고려한 부분이다.

bootstrap 폼 예제는 아니다. 기본 Form 에 대한 이해를 하고 나면 bootstrap Form으로 변경하는 것도 쉽다.

Form 을 작성하려면 <form> 태그를 사용한다.

<form> 태그는 두가지 주요 속성을 가진다.

- action 속성 : 어디에 데이터를 보낼지의 URL을 지정

- method 속성 : 어떻게 데이터를 전송할지의 방법을 지정. method 속성값에는 POST와 GET의 두가지 방법이 있다.


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="form1.css"  />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function(){
    $("form").submit(function(){
        var loginID = $("input[name='loginID']").val();
        var loginPW = $("input[name='loginPW']").val();

        if( loginID =='') {
            alert("아이디를 입력하세요");
            $("input[name='loginID']").focus();
            return false;
        }

        if(loginPW =='') {
            alert("패스워드를 입력하세요!");
            $("input[name='loginPW']").focus();
            return false;
        }
    });
});
</script>
</head>
<body>
<div class="container">
<h2>Form 예제1</h2>
<form method="post" action="form.action.php">
    <table>
        <tr>
            <td>로그인 ID</td>
            <td><input type="text" name="loginID" value=""></td>
        </tr>
        <tr>
            <td>패스워드</td>
            <td><input type="password" name="loginPW"></td>
        </tr>
        <tr>
            <td colspan='2' align='center'><input type="submit" value="전송"></td>
        </tr>
    </table>
</fom>
</div>
</body>
</html>

 body {    
    font-family: 나눔바른고딕, NanumBarunGothic, 맑은고딕, "Malgun Gothic", 돋움, Dotum, "Apple SD Gothic Neo", sans-serif;
    font-size: 12px;
    color: rgb(33, 33, 33);
    letter-spacing: 0.03em;
}

table {
    width: 300px;
}

tr {
    height: 50px;
}

input[type=text], input[type=password] {
    width: 100%;
    padding: 5px 10px; /* 상하 우좌 */
    margin: 3px 0; /* 상하 우좌 */
    font-family: inherit; /* 폰트 상속 */
    border: 1px solid #ccc; /* 999가 약간 더 진한 색 */
    font-size:14pt;
    box-sizing: border-box;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
    -webkit-transition: width 0.4s ease-in-out;
    transition: width 0.4s ease-in-out;
}

input[type=text]:focus, input[type=password]:focus {
    border: 2px solid #555;
}

input[type=submit] {
    margin-top: 10px;
    width:100px;
    height:40px;
    line-height: 22px;
    padding: 5px, 10px; /* 상하 우좌 */
    background: #E6E6E6;
    color: #000000;
    font-size: 15px;
    font-weight: normal;
    letter-spacing: 1px;
    border: none;
    cursor: pointer;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
}

input[type=submit]:hover {
    background-color: #FFBF00;
}

<?php
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    // POST 배열로 넘어온 값 확인 목적
    echo '<pre>';print_r($_POST);echo '</pre>';
    // db 접속파일 include 및 정상 로그인 여부 체크하는 함수 실행후 결과 반환처리 하면 된다.

    $ref = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER']:'';
    // 만약 사용자가 이전 페이지의 링크를 거치지 않고 브라우저에 URL을 직접 입력하여 접속한 경우는 ''
} else { // 비정상적인 접속인 경우
    echo 0; // form.action.php 파일을 직접 실행할 경우에는 화면에 0을 찍어준다.
    exit;
}
?>


클라이언트에서 Javascript 로 검증하고 있으면 서버에서 재검증할 필요가 없다고 생각할 수도 있지만 이것은 크게 잘못된 생각이다. 공격자는 모든 요청을 자유롭게 서버에 전송할 수 있으므로, Javascript에서의 검증을 쉽게 빠져나갈 수 있다.


만약 사용자가 이전 페이지의 링크를 거지치 않고 직접 접속한 경우에는 브라우저에 URL을 직접 입력하여 접속한 경우에는 참조원 정보뿐만아니라 $_SERVER['HTTP_REFERER'] 도 없다.


테스트한 파일을 첨부한다.

form-01.zip


블로그 이미지

Link2Me

,
728x90

부트스트랩 기반으로 회원관리 테이블을 작성중이다.

PDO 기반으로 Class 함수를 만드느라고 시행착오를 겪어가면서 수정 보완하면서 만들고 있다.



=== admin.php ===

<?php include_once '_common.php';?>
<!DOCTYPE html>
<html lang="en">
    <head>
        <?php
        require_once $g['path_layout'] . 'default/includes/_import.head.php';
        ?>
    </head>
    <body>
        <?php
        require_once $g['path_layout'] . 'default/includes/header.php';
        ?>

        <!-- 메인 화면 -->
        <main class="container-fluid">
            <div class="container main-container">
                <!-- body 화면 -->
                <div class="row">
                    <div class="col-md-12">
                        <div class="panel panel-default">
                            <div class="panel-body" id="panel_content">
                                <?php require_once $g['path_module'] . 'admin/adminMenu.php';?>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </main>
        <div id="ajaxPath" data-path="<?php echo $g['path_page'] . 'process/'; ?>" ></div>
        <div id="dialog"></div>
        <div id="actionPath" data-path="<?php echo $g['path_module'] . 'admin/'; ?>" ></div>
    </body>

</html>

=== _import.head.php ===

<meta charset="UTF-8">
<meta name="robots" content="noindex,nofollow"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="pragma" content="no-cache" />
<meta name="apple-mobile-web-app-capable" content="no" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<title><?=$_site['site_name'];?></title>
<link rel="stylesheet" href="<?php echo $g['path_bootstrap']?>css/bootstrap.min.css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" />
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" href="<?php echo $g['path_core']?>css/topmenu.css" />
<link rel="stylesheet" href="<?php echo $g['path_core']?>css/vertical-menu.css" />
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="<?php echo $g['path_bootstrap']?>js/bootstrap.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script src="<?php echo $g['path_core']?>js/jquery.tablednd.js"></script>
<script src="<?php echo $g['path_core']?>js/user.js"></script>
<script src="<?php echo $g['path_core']?>js/table_display.js"></script>
<!--[if lt IE 9]> <!-- 5. 인터넷익스플로러 9버전 이하일 경우 html5가 인식될 수 있게 해주는 스크립트 -->
<script src="<?php echo $g['path_bootstrap']?>js/respond.min.js"></script>

=== header.php ===

<nav class="navbar navbar-default">
  <div class="container">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
        <span class="sr-only">메뉴</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="<?php echo $g['path_page'];?>index.php">
          <?=isset($_bbs_info['bbs_name'])?$_bbs_info['bbs_name']:$_site['site_name']?>
      </a>
    </div>
    <div class="navbar-collapse collapse" id="navbar" >
        <ul class="nav navbar-nav">
            <?php require_once $g['path_layout'].'menu/item_topmenu.php';?>
        </ul>

        <ul class="nav navbar-nav navbar-right">
            <?php if(isset($_SESSION['userID']) && !empty($_SESSION['userID'])):?>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown"><? echo $_SESSION['userNM'] ?>
                    <b class="caret"></b></a>
                    <ul class="dropdown-menu">
                        <li><a href="#" onclick="document.getElementById('logout-form').submit();">로그아웃</a></li>
                        <li><a href="<?php echo $g['path_page'];?>member.php?m=info">정보수정</a></li>
                        <?php if(isset($_SESSION['admin']) && $_SESSION['admin']==1):?>
                        <li class="divider"></li>
                        <li><a href="<?php echo $g['path_page']; ?>admin.php">관리자</a></li>
                        <?php endif;?>
                    </ul>
                </li>
            <?php else:?>
                <li><a href="#modal-login" data-toggle="modal" >로그인</a></li>
                <li><a href="<?php echo $g['path_page'];?>member.php?m=join">회원가입</a></li>
            <?php endif;?>            
        </ul>

    </div><!--/.nav-collapse -->
    <div>
        <?php
            include_once $g['path_layout'].'default/includes/login_modal.php';           
        ?>
    </div>

  </div>
</nav>


=== memberList.php ===

<?php
if (!isset($_SESSION)) {
    session_start();
}
if(!isset($_SESSION['userID']) || empty($_SESSION['userID'])){
    echo 0;
    exit;
}

require_once $_SERVER['DOCUMENT_ROOT'].'/pdoclass/dbconfig.php';
$c = new LoginClass;
$b = new boardClass;
$link_url = $_SERVER['PHP_SELF']; // 현재 실행중인 파일명 가져오기
$page = isset($_GET['page'])? trim($_GET['page']):1;//페이지 변수 설정
$rowsPage = 10; // 한 화면에 표시되는 게시글 수
$curPage = isset($_GET['p']) ? $_GET['p'] : 1;
$table = "members m join member_data d on m.idx=d.relatedidx";
$cat1 = isset($cat1) ? $cat1: '';
$cat2 = isset($cat2) ? $cat2: '';
$where = isset($_GET['where']) ? $_GET['where']: '';
$keyword = isset($_GET['keyword']) ? $_GET['keyword']: '';
$xorderby= isset($xorderby) ? $xorderby : 'idx DESC';
if($where && $keyword) {
    if($where == 'userNM') $sql = "userNM LIKE '%".$keyword."%' ";
    if($where == 'userID') $sql = "userID LIKE '%".$keyword."%' ";
    if($where == 'mobileNO') $sql = "mobileNO LIKE '%".$keyword."%' ";
    if($where == 'unify') {
        $sql = "(userID LIKE '%".$keyword."%' OR userNM LIKE '%".$keyword."%' OR mobileNO LIKE '%".$keyword."%') ";
    }
} else {
    $sql ='';
}
$g['url_link']=($where?'where='.$where.'&amp;':'').($keyword?'keyword='.urlencode(stripslashes($keyword)).'&amp;':'').($cat1?'cat1='.$cat1.'&amp;':'').($cat2?'cat2='.$cat2.'&amp;':'');
$g['bbs_reset'] = $link_url;

$rows= $c->getDbArray($table,$sql,'*',$xorderby,$rowsPage,$curPage);
$NUM = $c->getDbRows($table,$sql); // 전체 게시글 수 또는 검색된 게시글 수
$TPG = $b->getTotalPage($NUM,$rowsPage); // 전체페이지수 및 현재 페이지수
?>

<div class="pull-left info">
    <?php if( $keyword ):?><strong>"<?php echo $keyword?>"</strong> 검색결과 : <?php endif?>
    <?php echo number_format($NUM)?>개 (<?php echo $curPage;?>/<?php echo $TPG;?>페이지)
</div>
<table id="membertable" class="table table-bordred table-striped table-hover">
   <thead>
        <th align="center" width="50"><strong>idx</strong></th>
        <th align="center"><strong>아이디</strong></th>
        <th align="center"><strong>성명</strong></th>
        <th align="center"><strong>휴대폰</strong></th>
        <th align="center"><strong>일반전화</strong></th>
        <th align="center"><strong>가입일</strong></th>
        <th align="center"><strong>접속일</strong></th>
        <th align="center"><strong>권한</strong></th>
        <th align="center"><strong>개인정보</strong></th>
   </thead>
   <tbody>

<?php
foreach($rows as $R):
    $A = $c->getDbDataFromIdx('member_data','relatedidx',$R['idx'] );
?>
<tr id="<?php echo $R['idx']; ?>">
    <td><?php echo $R['idx']; ?></td>
    <td><?php echo $R['userID']; ?></td>
    <td><?php echo $R['userNM']; ?></td>
    <td><?php echo $c->hyphen_telNo($R['mobileNO']); ?></td>
    <td><?php echo $c->hyphen_telNo($A['officeNO']); ?></td>
    <td><?php echo substr($R['regdate'], 0, 8); ?></td>
    <td><?php echo $R['curdate']; ?></td>
    <td><?php echo $R['admin'] == 1 ? '관리자' : ''; ?></td>
    <td><?php echo $A['smart'] == 1 ? '공개동의' : '비공개'; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class='searchbox'>
    <form name="membersearchf" class="form-inline" action="<?php echo $link_url;?>">
        <input type="hidden" name="orderby" value="<?php echo $xorderby;?>" />
        <select name="where" class="form-control input-sm">
            <option value="unify">통합</option>
            <option value="userNM">이름</option>
            <option value="userID">아이디</option>
            <option value="mobileNO">휴대폰</option>
        </select>
        <div class="input-group input-group-sm">
            <input type="text" name="keyword" value="" class="form-control input-search" placeholder="검색어">
            <span class="input-group-btn">
                <button type="button" class="btn btn-default" onclick="this.form.where.value='userNM';this.form.keyword.value='',this.form.submit();" title="리셋"><i class="glyphicon glyphicon-repeat"></i></button>
                <button type="submit" class="btn btn-info" title="검색"><i class="glyphicon glyphicon-search"></i></button>
            </span>
        </div>
    </form>
</div>
<div class="pull-right info">
    <a href="<?php echo $g['bbs_reset']?>" class="btn btn-default btn-sm pull-right">처음목록</a>
</div>
<?php $b->PageLinkView($link_url, $NUM, $rowsPage, $curPage, $g['url_link']); ?>


관련 작성 함수

※ PDO 조건에 맞게 구현되지 못한 함수도 있고 구현한 함수도 있다. (SQL 인젝션 관점)

   계속 시도를 하면서 방법을 찾을 생각이다.

// table 결과 조회 용도
public function getDbArray($table, $where, $flddata, $orderby, $rowsPage, $curPage) {
    $sql = 'select ' . $flddata . ' from ' . $table . ($where ? ' where ' . $this -> getSqlFilter($where) : '') . ($orderby ? ' order by ' . $orderby : '') . ($rowsPage ? ' limit ' . (($curPage - 1) * $rowsPage) . ', ' . $rowsPage : '');
    $stmt = $this -> db -> prepare($sql);
    $stmt -> execute();
    return $stmt;
}


// WhereArgs 조건에 일치하는 선택 DbData 가져오기
public function getDbDataFromIdx($table, $whereArgs, $idx) {
    $sql = 'select * from ' . $table . ' where ' . $whereArgs . '=?';
    $stmt = $this -> db -> prepare($sql);
    $stmt -> execute(array($idx));
    return $stmt -> fetch();
}


vertical-menu.css

/* Bootstrap 테이블 수정 */
.table > thead {
  background-color: #b3c6ff;
}
.table > thead > tr > th {
  text-align: center;
}
.table-hover > tbody > tr:hover {
  background-color: #e6ecff;
}
.table > tbody > tr > td {
  text-align: center;
}
.table > tbody > tr > #title {
  text-align: left;
}
div > #paging {
  text-align: center;
}

/* make sidebar nav vertical */
@media (min-width: 768px) {
  .sidebar .sidebar-nav .navbar .navbar-collapse {
    padding: 0;
    max-height: none;
  }
  .sidebar .sidebar-nav .navbar ul {
    float: none;
  }
  .sidebar .sidebar-nav .navbar ul:not {
    display: block;
  }
  .sidebar .sidebar-nav .navbar li {
    float: none;
    display: block;
  }
  .sidebar .sidebar-nav .navbar li a {
    padding-top: 12px;
    padding-bottom: 12px;
  }
}

 topmenu.css

/* 로그인 폼 */
.modal-header {
      color:black !important;
      text-align: center;
      font-size: 30px;
}
.modal-footer {
      background-color: #f9f9f9;
      padding: 5px 5px 5px 5px;
      border-top: 0px;
}
/* 로그인 폼 End */ 

/* 서브메뉴 */
.dropdown-submenu{position:relative;}
.dropdown-submenu>.dropdown-menu{
    top:0;
    left:100%;
    margin-top:-6px;
    margin-left:-1px;
    -webkit-border-radius:0 6px 6px 6px;
    -moz-border-radius:0 6px 6px 6px;
    border-radius:0 6px 6px 6px;
}

.dropdown-submenu>a:after{
    display:block;
    content:" ";
    float:right;
    width:0;
    height:0;
    border-color:transparent;
    border-style:solid;
    border-width:5px 0 5px 5px;
    border-left-color:#cccccc;
    margin-top:5px;
    margin-right:-10px;
}
.dropdown-submenu:hover>a:after{
    border-left-color:#555;
}
.dropdown-submenu.pull-left{float:none;}
.dropdown-submenu.pull-left>.dropdown-menu{
    left:-100%;
    margin-left:10px;
    -webkit-border-radius:6px 0 6px 6px;
    -moz-border-radius:6px 0 6px 6px;
    border-radius:6px 0 6px 6px;
}

@media only screen and (max-width: 768px) {
   
}

@media only screen and (min-width: 768px) {
    .dropdown-submenu:hover>.dropdown-menu {
        display: block;
    }
}


블로그 이미지

Link2Me

,
728x90

부트스트랩 회원가입 폼 예제다.

PDO(PHP Data Object) Class 와 연계하여 회원가입 처리를 하는 입력 폼, jQuery 코드다.



registerForm.php

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>회원가입</title>
        <!-- Bootstrap -->
        <link href="../plugin/bootstrap/css/bootstrap.min.css" rel="stylesheet">
        <!-- font awesome -->
        <link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
        <!-- Custom style -->
        <link rel="stylesheet" href="../plugin/bootstrap/css/style.css" media="screen" title="no title" charset="utf-8">

        <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
        <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
        <!--[if lt IE 9]>
        <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
        <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
        <![endif]-->
        <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
        <!-- Include all compiled plugins (below), or include individual files as needed -->
        <script src="../plugin/bootstrap/js/bootstrap.min.js"></script>
        <script src="../config/js/join.js"></script>
    </head>
    <body>
        <article class="container">
            <div class="page-header">
                <div class="col-md-6 col-md-offset-3">
                <h3>회원가입 Form</h3>
                </div>
            </div>
            <div class="col-sm-6 col-md-offset-3">
                <form role="form">
                    <div class="form-group">
                        <label for="inputName">성명</label>
                        <input type="text" class="form-control" id="inputName" placeholder="이름을 입력해 주세요">
                    </div>
                    <div class="form-group">
                        <label for="InputEmail">이메일 주소</label>
                        <input type="email" class="form-control" id="InputEmail" placeholder="이메일 주소를 입력해주세요">
                    </div>
                    <div class="form-group">
                        <label for="inputPassword">비밀번호</label>
                        <input type="password" class="form-control" id="inputPassword" placeholder="비밀번호를 입력해주세요">
                    </div>
                    <div class="form-group">
                        <label for="inputPasswordCheck">비밀번호 확인</label>
                        <input type="password" class="form-control" id="inputPasswordCheck" placeholder="비밀번호 확인을 위해 다시한번 입력 해 주세요">
                    </div>
                    <div class="form-group">
                        <label for="inputMobile">휴대폰 번호</label>
                        <input type="tel" class="form-control" id="inputMobile" placeholder="휴대폰번호를 입력해 주세요">
                    </div>
                    <div class="form-group">
                        <label for="inputtelNO">사무실 번호</label>
                        <input type="tel" class="form-control" id="inputtelNO" placeholder="사무실번호를 입력해 주세요">
                    </div>

                    <div class="form-group">
                    <label>약관 동의</label>
                    <div data-toggle="buttons">
                    <label class="btn btn-primary active">
                    <span class="fa fa-check"></span>
                    <input id="agree" type="checkbox" autocomplete="off" checked>
                    </label>
                    <a href="#">이용약관</a>에 동의합니다.
                    </div>
                    </div>

                    <div class="form-group text-center">
                        <button type="submit" id="join-submit" class="btn btn-primary">
                            회원가입<i class="fa fa-check spaceLeft"></i>
                        </button>
                        <button type="submit" class="btn btn-warning">
                            가입취소<i class="fa fa-times spaceLeft"></i>
                        </button>
                    </div>
                </form>
            </div>

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


join.js

$(function(){
    // 회원 가입 처리
    $('#join-submit').click(function(e){
        e.preventDefault();
        if($("#inputName").val() ==''){
            alert('이름을 입력하세요');
            $("#inputName").focus();
            return false;
        }

        var email = $('#InputEmail').val();
        if(email == ''){
            alert('이메일을 입력하세요');
            $("#InputEmail").focus();
            return false;
        } else {
            var emailRegex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
            if (!emailRegex.test(email)) {
                alert('이메일 주소가 유효하지 않습니다. ex)abc@gmail.com');
                $("#inputEmail").focus();
                return false;
            }
        }

        if($("#inputPassword").val() ==''){
            alert('비밀번호를 입력하세요');
            $("#inputPassword").focus();
            return false;
        }

        if($("#inputPasswordCheck").val() ==''){
            alert('비밀번호를 다시 한번 더 입력하세요');
            $("#inputPasswordCheck").focus();
            return false;
        }
        
        if($("#inputPassword").val()!== $("#inputPasswordCheck").val()){
            alert('비밀번호를 둘다 동일하게 입력하세요');
            return false;
        }

        if($("#inputMobile").val() ==''){
            alert('휴대폰 번호를 입력하세요');
            $("#inputMobile").focus();
            return false;
        }
        
        if($("#agree").is(":checked") == false){
            alert('약관에 동의하셔야 합니다');
            return false;      
        }
        
        $.ajax({
            url: 'register.php',
            type: 'POST',
            data: {
                name:$("#inputName").val(),
                userID:$('#InputEmail').val(),
                email:$('#InputEmail').val(),
                password:$('#inputPassword').val(),
                telNO:$("#inputtelNO").val(),
                mobileNO:$("#inputMobile").val()
            },
            dataType: "json",
            success: function (response) {
                if(response.result == 1){
                    alert('가입 완료');
                    location.replace('../index.php'); // 화면 갱신
                } else if(response.result == 0){
                    alert('이미 가입된 아이디입니다');
                } else if(response.result == -2){
                    alert('입력된 값이 없습니다');
                } else {
                    alert('등록중에 에러가 발생했습니다');
                }
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert("arjax error : " + textStatus + "\n" + errorThrown);
            }
        });        
        
    });

});


register.php

<?php
extract($_POST);
if (isset($_POST)) {
    require_once '../config/config.php';
    $c = new MemberClass();
    // 동일한 userID(email) 등록되어 있는지 체크
    if ($c->isUserExisted($userID)) {
        echo '{"result":"0"}'; // echo json_encode(array('result' => '0')); 과 동일
    } else {
        // 회원 등록
        $user = $c->storeUser($userID, $name, $email, $password, $telNO, $mobileNO);
        if ($user) {// 사용자 등록 성공
            if (!isset($_SESSION)) {
                session_start();
            }
            $_SESSION['userID'] = $user['userID'];
            $_SESSION['admin'] = $user['admin'];
            echo json_encode(array('result' => '1'));
        } else {
            // 회원 등록 실패
            echo json_encode(array('result' => '-1'));
        }
    }
} else {// 입력받은 데이터에 문제가 있을 경우
    echo json_encode(array('result' => '-2'));
}
?>


코드는 100% 동작되는 코드다. 모두 테스트를 하고 올린 코드들이다.

PDO Class 는 http://link2me.tistory.com/1401 에 올려져 있다.


안드로이드 회원가입 처리 코드 예제는 https://link2me.tistory.com/1405 를 참조하시라.

블로그 이미지

Link2Me

,
728x90

PHP Connect to MySQL

- MySQL (procedural) : MySQL DB에서만 동작하며, PHP 7.0 이상에선 동작안됨. PHP 5.5에서 deprecated

- MySQLi (procedural) : MySQL DB에서만 동작한다.

- MySQLi (object-oriented) : MySQL DB에서만 동작한다.
- PDO(PHP Data Objects) :
Oracle, PostgreSQL, SQLite, MySQL 등 12가지의 DB에서 동작한다.


MySQLi 기반 Prepared Statements PHP 연동 방식이 SQL 인젝션에 강하다고 되어 있다.

그러면 절차지향 방식의 MySQLi 방식에서는 어떻게 보강하면 될까?

$userid = mysqli_real_escape_string($db, $userid);

와 같이 mysqli_real_escape_string 를 써주면 된다.

해킹 대비를 위해서는 INPUT 해킹 시도(SQL Injection), 출력 해킹 시도(Cross Site Scripting) 둘다 신경써야 한다.


기존 사용하던 MySQLi (procedural) 대신에 MySQLi (object-oriented) 로 연습을 해봤다.


https://www.androidhive.info/2012/01/android-login-and-registration-with-php-mysql-and-sqlite 에서 파일을 다운로드하여 좀 수정했다.


테스트 환경

- Windows10 기반 Autoset9 (PHP 5.6.0, MySQL 5.6.20) : 정상

- Windows10 기반 Autoset10(Apache 2.4.17, PHP 7.0, MariaDB 10.1.9 ) : 정상


리눅스 기반 PHP 5.6.30, MySQL 5.6.30 에서 테스트를 하다보니 일부 함수가 문제가 발생하는 걸 알았다.

mysqlnd 가 설치되지 않아서였더라.

phpize로 추가 설치하는 방법을 검색해봤는데 아직 해결책을 못찾았다.

정 안되면 APM(Apache + PHP + MySQL) 소스 설치를 다시 해볼 생각이다.


- SHA1 패스워드를 적용한 알고리즘이 좋더라. 그래서 이걸 수정/추가해서 코드를 보완

- 아이폰/안드로이드폰에서 접속시 폰의 고유장치번호를 인식하여 최초 등록 단말이 아닌 경우 로그인 불가처리

- 테이블명을 변경하고 칼럼을 추가하고 user 권한을 설정하는 걸 추가


테이블 구조

- phpMyAdmin SQL에서 아래코드를 붙여넣기 하면 자동으로 DB명과 테이블을 생성한다.

- DB명과 user 권한을 수정해서 이용하면 된다.


DB_Functions 클래스 및 DB 접속정보는 첨부파일 참조

- DB_Functions 클래스가 핵심 내용이다.

 <?php

class DB_Functions {
    private $conn;

    // 생성자
    function __construct() {
        require_once 'dbconnect.php';
        // DB 연결
        $db = new Db_Connect();
        $this->conn = $db->connect();
    }

    // 소멸자(destructor)
    function __destruct() {

    }

    // 회원 정보 신규 입력
    public function storeUser($userID, $name, $email, $password, $telNO, $mobileNO) {
        $hash = $this->hashSSHA($password);
        $encrypted_password = $hash['encrypted']; // encrypted password
        $salt = $hash['salt']; // salt

        $stmt = $this->conn->prepare("INSERT INTO members(userID, userNM, email, passwd, salt, telNO,mobileNO,created_at) VALUES(?, ?, ?, ?, ?, ?, ?, NOW())");
        $stmt->bind_param("sssssss", $userID, $name, $email, $encrypted_password, $salt, $telNO, $mobileNO);
        $result = $stmt->execute();
        $stmt->close();

        // check for successful store
        if ($result) {
            $stmt = $this->conn->prepare("SELECT * FROM members WHERE userID = ?");
            $stmt->bind_param("s", $userID);
            $stmt->execute();
            $user = $stmt->get_result()->fetch_assoc();
            $stmt->close();

            return $user;
        } else {
            return false;
        }
    }

    // 로그인 체크
    public function getUser($userID, $password) {
        $stmt = $this->conn->prepare("SELECT * FROM members WHERE userID = ?");
        $stmt->bind_param("s", $userID);

        if ($stmt->execute()) {
            $user = $stmt->get_result()->fetch_assoc();
            $stmt->close();

            // verifying user password
            $salt = $user['salt'];
            $encrypted_password = $user['passwd'];
            $hash = $this->checkhashSSHA($salt, $password);
            // check for password equality
            if ($encrypted_password == $hash) {
                // user authentication details are correct
                return $user;
            }
        } else {
            return NULL;
        }
    }

    // 안드로이드/아이폰 로그인 체크
    public function LoginUserChk($userID,$password,$deviceID){
        if(empty($userID) || empty($password)){
            return 0;
        } else {
            $user = $this->getUser($userID, $password);
            if($user['idx']>0){
                // 장치 일련번호 체크
                if($user['phoneSE'] == NULL){
                    // 신규 장비번호 입력(최초 로그인)
                    $this->LoginUserEquipInput($userID,$deviceID);
                    return $user['idx'];
                } else {
                    if($user['phoneSE'] === $deviceID){
                        return 1; // 일련번호 일치
                    } else {
                        return -1; //일련번호 불일치
                    }
                }
            } else {
                return 0; //계정오류
            }
        }

    }

    // 장치번호 업데이트
    public function LoginUserEquipInput($userID,$deviceID){
        if(strlen($deviceID)>0 && is_numeric($deviceID)){ // 안드로이드폰
            $ostype = 2;
        } else if(strlen($deviceID)>30){ // 아이폰
            $ostype = 1;
        } else { // 기타
            $ostype = 0;
        }

        $sql='update members set phoneSE=?, OStype=? where userID=?';
        $stmt = $this->conn->prepare($sql);
        $stmt->bind_param("sss", $deviceID, $ostype, $userID);
        $status = $stmt->execute();
        if($status == true){
            return 1;
        } else {
            return 0;
        }
    }//end

    // 장치번호 초기화
    public function EquipReset($userID){
        $ostype = 0;
        $sql='update members set phoneSE=NULL, OStype=? where userID=?';
        $stmt = $this->conn->prepare($sql);
        $stmt->bind_param("ss", $ostype, $userID);
        $status = $stmt->execute();
        if($status == true){
            return 1;
        } else {
            return 0;
        }
    }//end

    // 회원 가입 여부 체크
    public function isUserExisted($userID) {
        $stmt = $this->conn->prepare("SELECT userID from members WHERE userID = ?");

        $stmt->bind_param("s", $userID);
        $stmt->execute();
        //$stmt->store_result();
        $result = $stmt->get_result();

        if ($result->num_rows > 0) {
            // user existed
            $stmt->free_result();
            $stmt->close();
            return true;
        } else {
            // user not existed
            $stmt->free_result();
            $stmt->close();
            return false;
        }
    }

    // 회원 정보 삭제
    public function deleteUser($userID){
        $stmt = $this->conn->prepare("delete FROM members WHERE userID = ?");
        $stmt->bind_param("s", $userID);
        $stmt->execute();
        $stmt->close();
    }

    public function hashSSHA($password) {

        $salt = sha1(rand());
        $salt = substr($salt, 0, 10);
        $encrypted = base64_encode(sha1($password . $salt, true) . $salt);
        $hash = array("salt" => $salt, "encrypted" => $encrypted);
        return $hash;
    }

    public function checkhashSSHA($salt, $password) {
        $hash = base64_encode(sha1($password . $salt, true) . $salt);
        return $hash;
    }

}

?>


- user 권한 및 패스워드 설정을 다르게 한 경우에는 해당 파일을 수정해야 한다.


register.php

- 안드로이드에서 전송된 데이터라고 가정하고 값을 직접 입력하여 DB에 잘 저장되는지 테스트했다.


json 으로 넘길 데이터 형태는 개발자가 변경하고 싶은데로 변경하여 사용하면 된다.


login.php


등록된 폰과 다를 경우 관리자에게 문의하여 장치번호를 초기화하고 새로운 폰으로 다시 등록해야만 로그인이 가능해진다.


안드로이드 코드 부분은 다음에 기회되면 작성할 예정이다.

이미 기존에 로그인 처리하는 코드 예제가 있으므로 그걸 수정해서 활용하거나 PHP 코드를 일부 수정해서 사용해도 된다.


androidPHP_Login_and_Registration.zip



PDO 개념 및  사용법 : http://link2me.tistory.com/1332

PDO DB 연결방법 : http://link2me.tistory.com/1398

PDO MemberClass : http://link2me.tistory.com/1399

MVC 패턴 고려한 PDO Class 만들기 : http://link2me.tistory.com/1401

블로그 이미지

Link2Me

,
728x90

회원가입시 필요한 member Class 함수다.

적용하는 사이트에 따라 칼럼(필드)가 추가로 있기도 하고 없을 수도 있는 걸 고려한 회원 등록, 회원 정보 수정 기능이 포함되어 있다.

패스워드 저장방식은 원하는 걸로 수정하면 된다.


 <?php
class MemberClass {
    //회원 신규 입력
    function MemberRegister($arr){
        date_default_timezone_set('Asia/Seoul');
        $rs=$this->MemberIDCheck($arr['idx'],$arr['userID']); //아이디중복검사
        if($rs<0){ //아이디 중복
            return -1;
            exit;
        } else if($rs==1){ //아이디 입력가능
            $sql="insert into member (idx,userID,passwd,userNM,telNO,mobileNO,email,Cate1,Cate2,smart,";
            if(isset($arr['positionNM']))
                $sql.="positionNM,";
            $sql.="allowLogin,regdate) values(NULL,";
            $sql.="'".$arr['userID']."',";
            $sql.="md5('".substr($arr['mobileNO'],-4)."'),";
            $sql.="'".$arr['userNM']."',";
            $sql.="'".$this->SplitRemove($arr['telNO'])."',";
            $sql.="'".$this->SplitRemove($arr['mobileNO'])."',";
            $sql.="'".$arr['email']."',";
            $sql.="'".$arr['Cate1']."',";
            $sql.="'".$arr['Cate2']."',";
            $sql.="'".$arr['smart']."',";
            if(isset($arr['positionNM'])) // 변수가 있으면 추가하라
                $sql.="'".$arr['positionNM']."',";
            $sql.="'".$arr['allowLogin']."',";
            $sql.="'".date("Ymd")."'"; // 등록일자
            if($result=mysql_query($sql)){
                return mysql_insert_id();
            } else {
                return $sql;
            }

        } else {//알수없는 Db오류
            return $sql;
        }
    }

    //회원정보 수정
    function MemberModify($arr){
        global $db;
        date_default_timezone_set('Asia/Seoul');

        $rs=$this->MemberIDCheck($arr['idx'],$arr['userID']);
        if($rs<0){ //아이디 중복
            return -1;
            exit;
        } else if($rs==1){ //아이디 입력가능
            $sql="update member set ";
            $sql.="userID='".$arr['userID']."',";
            if(isset($arr['passwd'])){
                $sql.="passwd=md5('".$arr['passwd']."'),";
            }
            $sql.="userNM='".$arr['userNM']."',";
            $sql.="telNO='".$this->SplitRemove($arr['telNO'])."',";
            $sql.="mobileNO='".$this->SplitRemove($arr['mobileNO'])."',";
            $sql.="email='".$arr['email']."',";
            $sql.="Cate1='".$arr['Cate1']."',";
            $sql.="Cate2='".$arr['Cate2']."',";
            $sql.="smart='".$arr['smart']."',";
            if(isset($arr['positionNM'])){
                $sql.="positionNM='".$arr['positionNM']."',";  
            }
            if(isset($arr['allowLogin'])){
                $sql.="allowLogin='".$arr['allowLogin']."',";
            }
            if(isset($arr['hidden'])){
                $sql.="hidden='".$arr['hidden']."',";
            }
            $sql.="regdate='".date("Ymd")."'"; // 수정일자
            $sql.=" where idx=".$arr['idx'];
            if($result=mysql_query($sql)){
                return $arr['idx'];
            } else {
                return $sql;
            }

        } else {//알수없는 Db오류
            return 0;
        }

    } //end function


    //회원정보 삭제
    function
MemberDelete($idx){
        global $db;
        $sql="delete from member where idx=".$idx;
        if($result=mysql_query($sql)){
            return 1;
        } else {
            return $sql;
        }
    }

    //변경전 로그인ID 유일성체크
    function MemberIDCheck($idx,$userID){
        global $db;
        $sql="select idx,count(*) from member where userID='".$userID."'";
        if($result=mysql_query($sql)){
            $row=mysql_fetch_row($result);
            if($row[1]==1){ //id존재
                if($row[0]==$idx){ // id변경없음
                    return 1;
                } else { //id변경시도, 다른 id존재, 변경불가
                    return -1;
                }
            } else { //변경시도, 다른id없음, 변경가능
                return 1;
            }
        } else {
            return 0;
        }
    }

}
?>


사용법

<?php
require_once 'memberClass.php';
$m = new MemberClass(); // 객체 생성

// 회원가입시 입력 form 에서 넘겨받은 배열 정보를 넣는다.
$rs = $m->MemberRegister($_POST);

// 회원 정보 수정시 입력 form 에서 넘겨받은 배열 정보를 넣는다.
$rs = $m->MemberModify($_POST);
echo $rs; // Ajax 통신인 경우 결과값을 반환하여 데이터 수정 성공/실패 팝업 메시지를 띄운다.
?>

블로그 이미지

Link2Me

,
728x90

var email = $('#email').val();
if(email== ''){
    alert('이메일주소를 입력하세요');
    email.focus();
    return false;
} else {
    var emailRegex = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
    if (!emailRegex.test(email)) {
        alert('이메일 주소가 유효하지 않습니다. ex)abc@gmail.com');
        email.focus();
        return false;
    }
}



블로그 이미지

Link2Me

,
728x90

구글 아이디로 로그인하는 걸 해보려고 구글링해서 몇가지를 테스트해봤는데 이것은 동작된다.


https://www.sanwebe.com/2012/11/login-with-google-api-php

에 가면 파일을 데모를 실행해보고 다운로드 할 수 있다.


테이블 스키마는 변경해야 한다.

사진 경로 길이가 너무 길어서 위 사이트에 나온 스키마 구조로는 에러가 발생한다.

CREATE TABLE IF NOT EXISTS `google_users` (
  `uid` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `google_id` varchar(60) NOT NULL,
  `google_name` varchar(60) NOT NULL,
  `google_email` varchar(60) NOT NULL,
  `google_link` varchar(150) NOT NULL,
  `google_picture_link` varchar(200) NOT NULL,
  PRIMARY KEY (`uid`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;


테이블 스키마는 회원테이블 정보에 가져올 정보를 표시하는 것이므로 굳이 이 테이블이 아니어도 된다.

이 테이블은 구글 회원정보를 가져온다는 걸 보여주는 것이라고 보면 된다.


첨부파일을 받으면 index.php 파일 내용을 수정해야 한다.

// https://console.developers.google.com/ 에서 client ID and secret 생성
$client_id = 'xxxxxxxxxxxxxxxxxx';  // 구글에서 생성한 id 입력
$client_secret = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; // 비밀키 입력
$redirect_uri = 'http://abc.com/oauth/google/'; // 본인 도메인으로 수정하세요


$redirect_uri = 'http://localhost/oauth/google/';

localhost 로 설정하면 에러가 발생한다.

Error: redirect_uri_mismatch

The redirect URI in the request, http://localhost/oauth/google/, does not match the ones authorized for the OAuth client.

그러므로 반드시 도메인명을 기록해야 한다. (IP주소 입력한 것은 테스트 안해봤음)

Google OAuth 2.0 Client API Keys 생성방법

https://console.developers.google.com/

https://www.sanwebe.com/2012/10/creating-google-oauth-api-key 에도 설명은 나오고

http://phppot.com/php/php-google-oauth-login/ 에도 나온다.

내가 등록을 참조한 곳은 http://phppot.com/php/php-google-oauth-login/ 이다.

하지만 이 사이트에서 제공하는 소스로는 연동이 잘 안되었다.


생성된 결과 화면이다.


테스트에 사용했던 파일입니다.

MySQLi 가 아닌 MySQL 연동 테스트 입니다. MySQLi 연동방식은 블로그에 올려진 걸 찾아서 변경하면 됩니다.

받아서 도움되신 분은 공감 꾸욱 눌러주세요~~


google_oauth.zip



블로그 이미지

Link2Me

,
728x90

홈페이지에 접속시 index.php 처리 흐름도는 다음 그림과 같다.


프로그램에서 중요한 것은 어떤 로직으로 그릴것인가 하는 점이다.

사용하는 언어에 따른 문법, 사용법은 당연히 익혀야 한다.


1. index.php 파일에는 순수한 PHP 코드만으로 session 이 있으면 바로 메인페이지로 접속하도록 처리한다.

    session 정보가 없으면 로그인 화면을 띄우도록 한다.

    <?php
    if(!isset($_SESSION)) {
        session_start();
    }
    if(isset($_SESSION['userID']) && !empty($_SESSION['userID'])){
        // 세션 정보가 있으면
        include "main.php";       
    } else {
        // 세션 정보가 없으면
        include "loginForm.php";
    }
    ?>


    이 골격 기반위에 mobile 접속체크 기능을 추가할 수도 있다.

    추가한다면 loginClass.php 파일안에 mobile 체크 함수를 추가할 수 있다.


2. loginForm.php 파일에는 HTML header, body 정보가 들어가 있다.

    - 로그인 정보는 ID, PW 정보이므로 암호화하여 전송하는 것이 안전하다.

    - POST 전송방식으로 전송하여 ID,PW 정보 내용이 보이지 않도록 한다.

    - POST 전송방식 loginForm.php 파일

    <!DOCTYPE html>
    <html>
    <head>
        <title>로그인</title>
        <meta charset="utf-8" />    
        <meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, minimum-scale=1.0,user-scalable=no"/>
        <meta http-equiv="cache-control" content="no-cache" />
        <meta http-equiv="expires" content="0" />
        <meta http-equiv="pragma" content="no-cache" />

        <!-- Include jQuery Mobile stylesheets -->

        <link rel="stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.css">

        <!-- Include the jQuery library -->

        <script src="http://code.jquery.com/jquery-1.8.2.min.js"></script>

        <!-- Include the jQuery Mobile library -->

        <script src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script>

        <!-- 개발자 본인이 만든 jQuery -->

        <script src="js/loginScript.js"></script>
    </head>
    <body>
    <div id="content" data-role="content" style="margin-top:5%;">
        <center>
        <form id="loginForm" method="post" action="#" >
            <input type="text" name="userID" id="userID" value="" placeholder="아이디" style="width:50%" />
            <input type="password" name="password" id="password" value="" placeholder="패스워드" style="width:50%"/>
            <button type="submit" data-inline="true">로그인</button>
            <button type="reset" data-inline="true">다시작성</button>
        </form>
        </center>
    </div>
    </body>
    </html>


    === loginScript.js ===

    $(document).ready(function(){
        $("#loginForm").submit(function(){
            var loginID = $("#userID").val();
            var loginPW = $("#password").val();

            if(loginID =='') {
                alert("아이디를 입력하세요");
                $("#userID").focus();
                return false;
            }
           
            if(loginPW =='') {
                alert("패스워드를 입력하세요!");
                $("input[type='password']").focus();
                //$("#password").focus();
                return false;
            }    
            $.post('loginChk.php',{userID:loginID, password:loginPW}, function(msg) {
                if(msg==1){ // loginChk.php 파일에서 echo 결과값
                    window.location.href = 'main.php';
                } else {
                    alert('다시 로그인 하세요');
                }
            });
            return false;
        });

        $("input[type='reset']").click(function(){
            if(!confirm("정말 입력을 취소하시겠습니까?")){
                return false;
            }
        });
    });


3. loginChk.php 파일

<?php
if(empty($_POST['userID']) || empty($_POST['password'])){
    echo 0; // 실패
} else {
    $loginID=$_POST['userID'];
    $loginPW=$_POST['password'];

    require_once 'dbconnect.php';
    require_once 'phpclass/loginClass.php';

    $c=new LoginClass();
    $row=$c->UserAuthCheck($loginID,$loginPW);
    if(is_array($row)){
        if(!isset($_SESSION)) {
               session_start();
           }
        $_SESSION['userID'] = $row['userID'];
        $_SESSION['level'] = $row['level'];
        echo 1; // 로그인 성공
    } else {
        if(!isset($_SESSION)) {
               session_start();
           }
        session_destroy();
        echo 0; // 실패
    }
}
?>


※ dbconnect.php 파일 만드는 방법은 http://link2me.tistory.com/1110 참조하라.


=== loginClass.php 파일 발췌 ===

로그인 검사는 해킹 공격적인 요소를 필터링 하기 위한 것도 고려했다.

<?php
class LoginClass {
    function UserAuthCheck($u,$p) {
        if(!isset($u) || !isset($p) || empty($u) || empty($p)) {
            return 0;
        } else {
            global $db;           
            $u = preg_replace("/[\s\t\'\;\"\=\--]+/","", $u); // 공백이나 탭 제거(사용자 실수 방지)
            $p = preg_replace("/[\s\t\'\;\"\=\--]+/","", $p); // 공백이나 탭 제거, 특수문자 제거
            // SQL injection 검사
            $u = htmlentities($u); // <script>documnet.cookie();</script> 공격 방지, 한글인식 불가
            $p = htmlentities($p); // < 를 \< 로 바꿔준다.
            $u = str_replace(array("'",""","'",'"'), array("&#39;","&quot;","&#39;","&quot;"), $u);
            if(preg_match('/[\']/',$u)) return 0; // no quotes
            if(preg_match('/[\/\\\\]/', $u)) return 0; // no slashes
            if(preg_match('/(and|null|where|limit)/i', $u)) return 0; // i는 대소문자 구별하지 말라
            if(!preg_match('/^[0-9a-zA-Z\~\!\@\#\$\%\^\&\*\(\)]{7,}$/',$p)) return 0; // 7자리이상 허용 문자만 통과
            $sql = "select * from member where pw=md5('".$p."') and id= '".$u."' ";
            if($result = mysql_query($sql,$db)) { //성공
                $row = mysql_fetch_array($result);
                if($row == NULL) return 0;
                return $row;
            } else {
                return '-1';
            }
        }
    }

}//end class LoginClass

?>


4. main.php 파일에도 HTML header, body 정보가 들어가 있다.

   메인 페이지 내용을 작성하여 원하는 데이터를 출력한다.

   DB에서 조회하여 가져온 데이터를 테이블 형식으로 보여주고자 한다면

   너무 많은 자료를 가져오면 부하문제도 생기고 화면 스크롤 문제도 생기므로

   페이징 처리를 해서 보여줘야 한다.

   http://link2me.tistory.com/1112 참조


5. 변수가 제대로 넘어오는지 체크하고자 할 경우

아래 코드를 추가하고 exit; 를 넣은 이유는 다음 코드를 실행하지 말고 중단해서 변수가 제대로 넘어오는지 여부를 한번에 확인하고자 하는 목적이다.


@extract($_POST);
var_dump($_POST);
exit;

블로그 이미지

Link2Me

,
728x90

로그인 체크함수다.

Web에서 로그인하는 경우와 안드로이드에서 로그인하는 경우 2가지를 모두 수용할 수 있게 작성했다.

안드로이드폰 직접 접속시에는 폰의 장치정보를 인식하므로 deviceID 정보를 식별자로 사용하여 Web 접속과 Mobile 접속을 구분 처리했다.


<?php
if(!isset($_SESSION)) {
    session_start();
}
if(isset($_POST['loginID']) && !empty($_POST['loginID']) && isset($_POST['loginPW']) && !empty($_POST['loginPW'])) {
    $loginID = trim($_POST['loginID']);
    $loginPW = trim($_POST['loginPW']);
    $deviceID = trim($_POST['deviceID']);

    $deviceID = $deviceID ? $deviceID : '';

    if(empty($deviceID)){
        require_once 'dbconnect.php'; // db접속
        require_once 'phpclass/loginClass.php';

        $c=new LoginClass();

        $row = $c->WebUserAuthCheck($loginID,$loginPW);
        if(is_array($row)) {
            if($row['code'] > 0) {
                $_SESSION['userID'] = $row['id'];
                $_SESSION['userPW'] = md5($loginPW);
                $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
                $_SESSION['ua'] = $_SERVER['HTTP_USER_AGENT'];

                echo("<meta http-equiv='Refresh' content='0; URL=list.php'>");
            } else {
                echo '권한 불가';
            }
        } else if($row == '0'){
            $msg ='정보가 올바르지 않습니다';
            echo "<script>alert('".$msg."');history.go(-1);</script>";
        } else {
            $msg ='정보가 올바르지 않습니다';
            echo "<script>alert('".$msg."');history.go(-1);</script>";
        }

    } else {
        require_once 'db.info.php';
        require_once 'phpclass/dbClass.php';
        $conn=new MySQLDbClass();
        $DB_CONNECT = $conn->isConnectDb($DB); // 안드로이드폰에서는 반드시 객체로 생성해야 정상접속
        require_once 'phpclass/loginClass.php';
        $c=new LoginClass();

        $result = $c->MobileUserAuthCheck($loginID,$loginPW,$deviceID);
        if($result > 0 ) {
            session_save_path('./_tmp/session');

            $_SESSION['userID'] = $loginID;
            $_SESSION['userPW'] = md5($loginPW);
            $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
            $_SESSION['ua'] = $_SERVER['HTTP_USER_AGENT'];
            echo 'Login Success';
        } else if($result == 0) {
            echo 'Login Fail';
        } else {
            echo 'Phone Dismatch';
        }
    }
   
} else {
    echo("<meta http-equiv='Refresh' content='0; URL=loginForm.php'>");
}
?>


블로그 이미지

Link2Me

,
728x90

ajax 에 대한 기본 개념이 잘 설명된 곳은 https://www.w3schools.com/jquery/jquery_ajax_intro.asp 다.

ajax 의 강력한 특징은 페이지 전체를 refresh 하지 않고서도 수행되는 비동기성이다. 이러한 비동기성을 통해 사용자의 Event가 있으면 전체 페이지가 아닌 일부분만을 업데이트 할 수 있게 해준다.


jQuery ajax 의 다른 형태로 .get 또는 .post 처리가 가능하다.

형태가 심플하여 더 편할 수 있다. 아래 코드는 100% 동작하는 걸 확인하고 적어둔다.

대부분 자료들이 대략적인 컨셉만 적혀있고 참고로 사용할만한 샘플이 없는 거 같아서 적었다.

나와 같은 초보자들은 제대로 동작하는 샘플이 있어야만 그 다음에 응용 또는 활용이 가능하다.


$.get()함수, $.post() 함수는 url, url 파라미터, 요청의 성공시 응답을 받기 위한 콜백함수가 있다.

$.post(url, {userID:input_value}, function(data) {

     // 통신이 성공했을 때 실행하는 함수

}, 'json');


url 파라미터는 key 와 value 쌍으로 구성된다. 파라미터가 여러개이면 콤마로 구분한다.


===== idCheck.php ====

<!DOCTYPE html>
<head>
<meta charset=UTF-8" />
<meta name="robots" content="noindex,nofollow"/> <!-- 검색엔진에서 검색 제외 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>
<meta http-equiv="X-UA Compatible" control="IE=edge,chrome=1" /> <!--브라우저 호환성 설정 -->
<title>ID Join Check</title>
<!-- 모바일 웹 페이지 설정 -->
<link rel="shortcut icon" href="img/icon.png" />
<link rel="apple-touch-icon" href="img/icon.png" />
<!-- 모바일 웹 페이지 설정 끝 -->

<link rel="stylesheet" type="text/css" href="css/common.css"/>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script type="text/javascript" src="js/idCheck.js"></script>
</head>
<body>
    <h1 class="title">아이디 중복검사</h1>
    <div class="exec">
        <form>
            <input type="text" name="userID" />
            <input type="button" id="checkid" value="중복검사" />
        </form>
    </div>
    <div class="console"></div>
</body>
</html>


===== idCheck.js =====

$(function(){
    $("#checkid").click(function(e) {

        e.preventDefault(); // a 링크, form submit 실행 방지

        var input_value = $("input[name='userID']").val();
       
        // 입력 여부 검사
        if(!input_value) {
            alert("아이디를 입력하세요");
            $("input[name='userID']").focus();
            return false;
        }

        var url = 'idCheck_ok.php';
        $.post(url, {userID:input_value}, function(msg) {
            if (msg.result==1) { // idCheck_ok.php 의 결과값
                $(".console").html("<span style='color:blue'>사용할 수 있는 아이디 입니다.</span>");

                // alert('사용할 수 있는 아이디 입니다.'); 로 대신해도 된다.

            } else {
                $(".console").html("<span style='color:red'>사용할 수 없는 아이디 입니다.</span>");

                // alert('사용할 수 없는 아이디 입니다.'); 로 대신해도 된다.

                // 이 경우에는 $("input[name='userID']").val(''); 로 입력값을 초기화해도 좋다.

            }
        },'json');
       
    });
});



https://link2me.tistory.com/954 참조하면 javascript 로 Form 제어하는 개념을 이해할 수 있다.

위의 예제에는 form method, action 을 생략해서 원하는 결과를 얻지 못할 수 있다.

form method를 생략하면 GET방식으로 동작된다.

form action 을 추가하고 동작할 경우에는 반드시 e.preventDefault(); // a 링크, form submit 실행 방지

를 추가해줘야 제대로 원하는 결과를 얻을 수 있다.


===== idCheck_ok.php ====

# PHP 기본 개념 이해는 http://link2me.tistory.com/1110 게시글을 참조하시라.

# ajax 를 이용하면 파일 A --> 파일 B로 이동하지 않고 결과를 얻을  수 있다.

<?php
session_start();
if(isset($_POST['userID']) && !empty($_POST['userID'])) {
    $userID = trim($_POST['userID']);

    require_once $_SERVER['DOCUMENT_ROOT'].'/dbconnect.php'; // db접속 성공
    require_once $_SERVER['DOCUMENT_ROOT'].'/phpclass/loginClass.php';

    $c=new LoginClass();
    $rs = $c->UserIDJoinCheck($userID);
    if($rs == '0') {
        echo '{"result":"1"}';
    } else {
        echo '{"result":"0"}';
    }
}
?>

===== loginClass.php ====

<?php
class LoginClass {
    function UserIDJoinCheck($userID) {
        if(!isset($userID) || empty($userID)) {
            return '-1';
        } else {
            global $db;           
            $userID = preg_replace("/[\s\t]+/","", $userID); // 공백이나 탭 제거(사용자 실수 방지)

            $sql = "select id, count(id) ";
            $sql.= "from rb_s_mbrid where id= '".$userID."' ";
            if($result = mysqli_query($db,$sql)) { //성공
                $row = mysqli_fetch_row($result);
                if($row[1] == '0') return 0;
                return $row[1];
            } else {
                return '-1';
            }
        }
    }

}//end class LoginClass

?>


===== 테이블 구조 =====

CREATE TABLE IF NOT EXISTS `rb_s_mbrid` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `site` int(11) NOT NULL DEFAULT '0',
  `id` varchar(50) NOT NULL DEFAULT '',
  `pw` varchar(50) NOT NULL DEFAULT '',
  `code` int(6) NOT NULL DEFAULT '0',
  PRIMARY KEY (`uid`),
  KEY `site` (`site`),
  KEY `id` (`id`),
  KEY `code` (`code`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

도움되셨다면 공감 또는 ... 눌러주세요.

블로그 이미지

Link2Me

,
728x90

SQL 인젝션 공격을 방지하기 위한 로그인 함수 사용예이다.

공격코드로 사용하는 특수문자를 전부 공백으로 대체시켜 버리도록 했다.


function UserAuthCheck($userID,$passwd) {
    if(!isset($userID) || !isset($passwd) || empty($userID) || empty($passwd)) {
        return 0;
    } else {
        global $DB_CONNECT;           
        $userID = preg_replace("/[\s\t\'\;\"\=\-\-]+/","", $userID); // 공백이나 탭 제거(사용자 실수 방지)
        $passwd = preg_replace("/[\s\t\'\;\"\=\-\-]+/","", $passwd); // 공백이나 탭 제거, 특수문자 제거(",',;,=,--)
        // SQL injection 검사
        $userID = htmlentities($userID); // <script>documnet.cookie();</script> 공격 방지, 한글인식 불가
        $passwd = htmlentities($passwd); // < 를 \< 로 바꿔준다.
        //$userID = str_replace(array("'",""","'",'"'), array("&#39;","&quot;","&#39;","&quot;"), $userID);
        if(preg_match('/(and|null|where|limit)/i', $userID)) { $this->popup('비정상적 접근입니다');} // i는 대소문자 구별하지 말라
        if(!preg_match('/^[0-9a-zA-Z\~\!\@\#\$\%\^\&\*\(\)]{7,}$/',$passwd)) { // 최소7자리 이상 허용 문자만 통과
            $this->popup('정보가 올바르지 않습니다');
        }

        $sql = "select code, id ";
        $sql.= "from memberdata where pw=md5('".$passwd."') and id= '".$userID."' ";
        if($result = mysqli_query($DB_CONNECT,$sql)) { //성공
            $row = mysqli_fetch_array($result);
            if($row == NULL) $this->popup('정보가 올바르지 않습니다');
                return $row;
            } else {
                $this->popup('정보가 올바르지 않습니다');
        }
    }
}

블로그 이미지

Link2Me

,
728x90

jQuery를 이용해서 폼을 처리하기 위해서는, 'submit' 이벤트 리스너를 form 요소에 추가해야 한다.
$("form").on("submit", function() {
    // process form
});

$("form").submit(function(){
    // process form
});

폼 처리를 완전히 jQuery 내에서 한다면, 페이지를 다시 불러오는 것을 방지하기 위해 preventDefault()를 호출해야 한다.

$("form").on("submit", function(e) {
   e.preventDefault(); // 내장된 이벤트를 차단하는 명령
   // process form
});

$("form").submit(function(e){
    e.preventDefault(); 
// 내장된 이벤트를 차단하는 명령
    // process form
});



=== login.php ===

<!DOCTYPE html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=yes, width=device-width" />
<title>Login Form</title>
<link rel="stylesheet" type="text/css" href="css/style.css" />
<!-- Include the jQuery library -->
<script src="http://code.jquery.com/jquery.min.js" ></script>
<!--<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>-->
<script type="text/javascript" src="js/login.js"></script>
</head>
<body>
<div class="container">
    <section id="content">
        <form method="post" action="loginChk.php">
            <div>
                <input type="text" name="loginID" placeholder="아이디" required="" id="userid" />
            </div>
            <div>
                <input type="password" name="loginPW" placeholder="비밀번호" required="" id="password" />
            </div>
            <div>
                <input type="submit" value="로그인" />
            </div>
    </form><!-- form -->
    </section><!-- content -->
</div><!-- container -->
</body>

</html>

설명

method="post" 라고 지정한 부분을 생략하면 데이터 전송은 GET방식으로 처리된다. method="get" 이라고 지정해 줄 수도 있다.

GET방식으로 전송하면 URL의 파일명 뒤에 ? 를 명시한 뒤, key 와 value를 쌍으로 구성된 데이터가 포함될 수 있다.

전달할 수 있는 데이터의 총 크기는 1024 bytes 만 허용한다.

127.0.0.1/loginChk.php?loginID=test01&loginPW=1234 처럼 보이므로 보안에 취약하다.

key 인 loginID 는 <input> 태그의 name 속성에 지정한 값이 되고, value 는 <input> 태그의 value 속성값이 사용된다.



==== login.js ===

$(document).ready(function(){
    $("form").submit(function(e){
e.preventDefault(); // 내장된 이벤트를 차단하는 명령
        var loginID = $("#userid").val();
// var loginID = $("input[name='loginID']").val();
        var loginPW = $("#password").val();
 
        if( loginID =='') {
            alert("아이디를 입력하세요");
$("#userid").focus();
            return false;
        } 
        
        if(loginPW =='') {
            alert("패스워드를 입력하세요!");
$("#password").focus();
            return false;
        } 
        
    });

});


==== loginChk.php ===
PHP 5.5 이상에서는
$_POST['loginID'] 를 정확하게 입력해줘야 값을 인식한다.

PHP 버전이 낮은 경우에는 $_POST[loginID] 로 해도 값이 넘어가는데 높은 버전에서는 인식을 못하더라.


<?php
include_once 'dbconnect.php'; // 만드는 방법은 http://link2me.tistory.com/1110 참조
 
$workcode = LoginUserCheck(trim($_POST['loginID']),trim($_POST['loginPW']));
if($workcode == -1) {
    include "index.php";
else {
    // 로그인 후 처리할 내용 코드 입력
}
 
?>


jQuery 아이디 중복체크

http://link2me.tistory.com/1069


PHP 와 jQuery 값 전달이해
http://link2me.tistory.com/1124

블로그 이미지

Link2Me

,