[webDev] 로그인 로직 구성 (식별과 인증)
식별과 인증을 구분하여, 로그인 로직을 구성하는 방법에 대해 자세한 코드와 함께 알아 보자.
로그인 로직 구성
<목차>
로그인 사이트 구성
로그인 사이트의 html 코드
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="logintest.css">
</head>
<body>
<div class="wrapper">
<form class="form-signin" method="POST" action="logintest.php">
<h2 class="form-signin-heading">Login Test</h2>
<input type="text" class="form-control" name="id" placeholder="User id" required="" autofocus="" />
<input type="password" class="form-control" name="pass" placeholder="Password" required=""/>
<label class="checkbox">
<input type="checkbox" value="remember-me" id="rememberMe" name="rememberMe"> Remember me
</label>
<button name="Submit" value="Login" class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
</body>
</html>
로그인 사이트의 css 코드
body {
background: #eee !important;
}
.wrapper {
margin-top: 80px;
margin-bottom: 80px;
}
.form-signin {
max-width: 300px;
min-height: 250px;
padding: 15px 35px 45px;
margin: 0 auto;
background-color: #fff;
border: 1px solid rgba(0,0,0,0.1);
.form-signin-heading,
.checkbox {
margin-top: 10px;
margin-bottom: 30px;
height: auto;
}
.checkbox {
top: 10px;
position: relative;
display: block;
font-weight: normal;
}
.btn-block {
display: block;
width: 96%;
}
.btn-group-lg>.btn, .btn-lg {
padding: 10px 10px;
font-size: 15px
line-height: 1;
border-radius: 6px;
}
.btn-primary {
color: #fff;
background-color: #337ab7;
border-color: #2e6da4;
}
.btn {
display: inline-block;
padding: 10px 12px;
margin-bottom: 0;
font-size: 14px;
font-weight: 400;
line-height: 1.42857143;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-ms-touch-action: manipulation;
touch-action: manipulation;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-image: none;
border: 1px solid transparent;
border-radius: 4px;
}
.form-control {
position: relative;
font-size: 16px;
width: 90%;
height: auto;
padding: 10px;
}
}
html {
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
로그인 사이트 화면
식별 / 인증 동시
<?php
//데이터베이스 연결 설정
$db_connect = mysqli_connect("localhost", "mysql_id", "mysql_pass", "member");
$db_connect-> set_charset("utf8");
//데이터베이스 연결 오류 확인
if ($db_connect->connect_error){
die("No DB Connection". $db_connect->connect_error);}
//POST 방식으로 사용자 입력 확인
$id = $_POST["id"];
$pass = $_POST["pass"];
echo "POST ok<br>";
//식별과 인증을 동시
function authenticateUser($db_connect, $id, $pass) {
//입력받은 데이터가 db에 존재하는지 확인
$sql = "SELECT * FROM member WHERE UserId='$id' AND Pass='$pass'";
$result = $db_connect -> query($sql);
//Query 오류 확인
if (!$result) {
die("Query Failed: " . $db_connect->error);
}
//사용자 인증 결과 확인
if($result->num_rows>0){
echo "login ok<br>";
echo "$id and $pass";
}else{
echo "login fail<br>";
echo "$id and $pass";
}
}
authenticateUser($db_connect, $id, $pass);
?>
아래 방식부터는, 데이터베이스 연결 및 POST 방식으로 데이터를 가져오는 방법은 동일함으로, 함수 부분만 포스팅 하겠다.
여기서 식별과 인증을 함수로 지정한 이유는, 같은 로그인 페이지에서 여러 가지 식별 / 인증 방식 실험을 용이하게 하기 위함이다.
어떠한 방법을 택할지 정했다면, 함수로 지정하지 않아도 된다.
식별 / 인증 분리
//식별과 인증을 분리
function authenticateUser2($db_connect, $id, $pass){
$sql = "SELECT * FROM member WHERE UserId='$id'";
$result = $db_connect -> query($sql);
//사용자 ID 검증
if($result->num_rows>0){
$row_pass = $result->fetch_assoc(); // 한 행의 데이터를 가져옴
//사용자 ID가 일치할 경우에만 비밀번호를 검증
if($row_pass['Pass']==$pass){
echo "login ok : 아이디와 비밀번호가 일치합니다. <br>";
}else{
echo "login fail : 비밀번호가 일치하지 않습니다. <br>";
}
}else{
echo "login fail : 아이디 또는 비밀번호가 일치하지 않습니다. <br>";
echo "$id and $pass";
}
}
authenticateUser2($db_connect, $id, $pass);
식별 / 인증 동시 (+ HASH)
Client가 입력한 비밀번호를 해쉬처리한 후 식별과 인증을 동시에 한다.
DB에 저장된 비밀번호는 평문으로 저장되어있다.
- password_hash ("password", hash_algorithm) : 비밀번호를 안전하게 해시화하기 위해 사용된다.
- 해쉬 알고리즘에는 2가지 매개변수를 작성할 수 있다.
- PASSWORD_DEFAULT : 현재 PHP 버전에서 권장하는 기본 알고리즘을 사용, 향후 PHP 버전에서 기본 알고리즘이 변경될 수 있으므로 보안 상 권장된다. 하지만, 현재로는 Bycrpt 알고리즘을 사용한다.
- PASSWORD_BCRYPT : Bcrypt 알고리즘을 명시적으로 사용한다.
- password_verify (password, hash) : 해시화된 비밀번호와 입력된 비밀번호가 일치하는지 확인하는 함수이다.
// 식별과 인증을 동시, 비밀번호 해쉬처리
function passhash($db_connect, $id, $pass) {
//입력받은 데이터가 db에 존재하는지 확인
$sql = "SELECT * FROM member WHERE UserId='$id' AND Pass='$pass'";
$hashed_pass = password_hash($pass, PASSWORD_DEFAULT); //입력받은 비밀번호를 해쉬로 변경
$result = $db_connect -> query($sql);
$row = $result->fetch_assoc();
password_verify($hashed_pass, $row['Pass']); //해쉬처리된 비밀번호가, DB에 저장된 평문 비밀번호와 일치하는지 확인
echo "verify ok<br>";
//Query 오류 확인
if (!$result) {
die("Query Failed: " . $db_connect->error);
}
//사용자 인증 결과 확인
if($result->num_rows>0){
echo "login ok <br>id: $id<br>hashed pass: $hashed_pass<br>pass: $pass<br>";
}else{
echo "login fail <br>id: $id<br>hashed pass: $hashed_pass<br>pass: $pass<br>";
}
}
passhash($db_connect, $id, $pass);
식별 / 인증 분리 (+ HASH)
// 식별과 인증 분리, 비밀번호 해쉬처리
function passhash2($db_connect, $id, $pass) {
//입력받은 데이터가 db에 존재하는지 확인
$hashed_pass = password_hash($pass, PASSWORD_DEFAULT);
$sql = "SELECT * FROM member WHERE UserId='$id'";
$result = $db_connect -> query($sql);
echo "id 가져오기 ok<br>";
if (!$result) {
die("Query Failed: " . $db_connect->error);
}
if($result->num_rows>0){
$row = $result->fetch_assoc(); // id가 일치하는 행의 데이터를 가져옴
//사용자 ID가 일치할 경우에만 비밀번호를 검증. DB의 비밀번호와, 해쉬처리된 비밀번호를 검증
if(password_verify($row['Pass'], $hashed_pass)){
echo "login ok <br>id: $id<br>hashed pass: $hashed_pass<br>pass: $pass<br>";
}else{
echo "login fail : 비밀번호가 일치하지 않습니다.<br>id: $id<br>hashed pass: $hashed_pass<br>pass: $pass<br>";
}
}else{
echo "login fail : 아이디 또는 비밀번호가 일치하지 않습니다. <br>id: $id<br>hashed pass: $hashed_pass<br>pass: $pass<br>";
}
}
passhash2($db_connect, $id, $pass);
PHP 객체 반환 문법
$sql을 선언하고, 객체를 가져오는 2가지 방법과 차이점을 알아보자.
$sql = "SELECT * FROM member WHERE UserId='$id'"; // 동일
$result = $db_connect -> query($sql); // 1번
$result =mysqli_fetch_array($db_connect, $sql); // 2번
1번
- 데이터베이스에 쿼리를 실행하고, 결과를 나타내는 mysqli_result 객체를 반환한다.
- 이 객체에는 쿼리에서 선택된 행들에 대한 정보가 포함되어 있습니다. 즉, $result 에는 여러 행이 포함될 수 있다.
2번
- 이 코드는 쿼리를 실행한 결과에서 한 행을 가져온다.
- mysqli_fetch_array() 함수는 쿼리 결과에서 한 행을 가져와 배열로 반환한다.
- 따라서 $db_id에는 쿼리 결과의 한 행만 포함된다.
첫 번째 코드에서는 $id 변수를 사용하여 WHERE 절에 조건을 지정했기 때문에, 해당 조건에 맞는 행들이 모두 선택된다.
따라서 $id가 아닌 다른 행의 정보도 불러올 수 있다.
예를 들어, $id가 특정한 사용자의 ID라고 가정하면, 해당 사용자의 정보만 가져오는 것이 아니라, 모든 member 테이블에서 $id와 일치하는 사용자들의 정보가 선택된다. 따라서 $id와 일치하는 여러 행이 있다면, 모든 행들의 정보가 $result 에 포함된다.
결론 : 1개의 데이터만 불러오고 싶다면 2번 문법을 / 여러 행을 모두 불러오고 싶다면 1번 문법을 선택하여 작성할 수 있다.
데이터 반환 함수의 차이 : fetch_array 와 fetch_assoc
둘 다 MySQL 결과 집합에서 데이터를 가져오는 데 사용되지만, 반환되는 데이터의 형식에 차이가 존재한다.
- mysqli_fetch_array():
- 이 함수는 결과 집합의 다음 행을 숫자 인덱스와 연관 배열 형태로 모두 포함하는 배열을 반환
- 반환된 배열은 숫자 인덱스와 열 이름 모두로 접근할 수 있다.
- 따라서 반환된 배열은 더 많은 메모리를 사용하며, 데이터를 처리하는 데에도 더 많은 시간이 소요될 수 있다.
- mysqli_fetch_assoc():
- 이 함수는 결과 집합의 다음 행을 연관 배열 형태로 반환
- 반환된 배열은 열 이름을 키(key)로 사용하며, 해당하는 값을 가져올 때 훨씬 직관적이다.
- 연관 배열 형태로 반환되기 때문에 일반적으로 메모리 사용량이 적고 데이터 처리 속도가 빠르다.
따라서 대부분의 경우, 연관 배열 형태로 데이터를 가져오는 mysqli_fetch_assoc() 함수가 더 효율적이고 사용하기 편리하다. 특히 열 이름을 직접 지정하여 데이터에 접근하는 경우에 유용하다.