• RedisConnectionPool
  • 회원가입, 로그인 로직 구체적으로 정리 및 구현  - 쿼리바인더 만들고 업데이트 필요
  • SQL쿼리 바인더 - 현재는 sql인젝션에 취약한 상태임
  • 비밀번호 해싱과 관리
  • 엑세스 토큰 발급
  • 타이머 관리 - 현재 사용이 불편함. 근데 사용처는 거의없어서 그냥둬도 될지도?
  • 이메일 API 사용
  • 너무 느린 빌드 문제 해결하기
  • Client/Server PacketHandler.h 코드 자동생성 기능 만들기
    게임서버강의에서 배운 Protocol.proto 파일을 파싱해서 패킷핸들러 헤더를 자동으로 만들어주는 프로그램을 만들자.

회원가입과 로그인로직을 만들면서 비밀번호 헤싱과 엑세스토큰 발급같은 다른것들도 해결했다.

이제 쿼리바인더를 만들고 이에 따라 회원가입,로그인 로직에서 db사용 부분을 수정하면 인증서버는 이제 거의 마무리 단계로 보인다.


원래는 게임서버 강의에서 배운 Binder클래스를 만들려했는데, 굳이? 싶어서 postgres와 redis의 Connection 클래스들을 기존보다 쓰기 편하고, sql인젝션을 방지하는 형태로 수정했다.

 

Postgresql Connection 클래스 변경사항

기존 PGConnection의 execute 함수.

bool	PGConnection::Connect(const char* connectionString)
{
	_connection = PQconnectdb(connectionString);
	if (PQstatus(_connection) != CONNECTION_OK)
        return false;
	return true;
}

보다싶이 그냥 쿼리 string을 받아서 쿼리에 넣고 실행하는게 전부다.

string	pgsql = "SELECT user_id FROM auth.users WHERE nickname = '"
	+ nickname + "' OR email = '" + email + "'";
PGresult*	pgResult = pg->ExecuteSQL(pgsql.c_str());

외부에서 쿼리를 만들어 넣을 때, 위와 같이 string에 +를 덕지덕지 붙여서 Execute에 넣는데, 여기서 사용자가 nickname이나 email 이상하게 설정해서 의도하지 않은 동작이 일어나게 할 수 있기 때문에 반드시 수정해야 했다.

그 외로 PGConnection 래퍼 클래스가 있는데, 정작 pgResult나 에러체크를 외부에서 하는 캡슐화를 제대로 하지 못한 부분도 많아서 전체적으로 뜯어고쳤다.

 

변경된 PGConnection 클래스의 사용

string에 + 로 덕지덕지 붙이던 부분이 사라졌다.

string	pgGetUserIdSQL = "SELECT user_id FROM auth.users WHERE nickname = $1 OR email = $2";
PGConnection*	pg = GDBConnectionPool->PopPG();

pg->AddValue(nickname);
pg->AddValue(email);
if(pg->ExecuteSQL(pgGetUserIdSQL) == false)
{//TODO 실행실패 제대로 처리
    cout << "Fail To ExecuteSQL in Handle_C_SIGNUP" << endl;
    GDBConnectionPool->Push(&pg);
    return;
}
int	rowCount = pg->GetRowCount();
pg->Clear();

GDBConnectionPool->Push(&pg);

세세한건데, GDBConnectionPool->Push할 때 포인터로 집어넣어서 쓰임을 다한 connection 포인터를 null로 바꿔줘서 큐에 들어간 커넥션 포인터를 사용하는 실수를 범하지 않도록 수정했다.

변경된 Execute함수

PQExecParams 함수를 사용해서 인젝션 문제를 해결했다.

변수에 해당하는 부분을 $1, $2 이런식으로 써넣는데 PQexecParams함수의 인자로 values 문자열 배열을 넣어줘서 $1 위치에 정해진 문자열이 순서대로 들어가도록 해준다. 문자열 배열이 필요하기 때문에 PGConnection 클래스에 string vector 멤버를 추가하고, AddValue 함수로 추가할 수 있도록 만들었다.

bool	PGConnection::ExecuteSQL(const std::string& sql)
{
	std::vector<const char*>	value_ptrs(_values.size());
	for (int i = 0; i < _values.size(); ++i)
		value_ptrs[i] = _values[i].c_str();
	_result = PQexecParams(
		_connection,
		sql.c_str(),
		_values.size(),
		NULL,
		value_ptrs.data(),
		NULL,
		NULL,
		0
	);

	ClearValues();
	ExecStatusType	ret = PQresultStatus(_result);
	if (ret != PGRES_COMMAND_OK && ret != PGRES_TUPLES_OK)//실패했다면
	{
		std::cout << PQerrorMessage(_connection) << endl;
		Clear();
		return false;
	}
	return true;
}

 

Execute의 결과값도 기존에는 PGresult를 리턴해서 직접 PQgetvalue 를 사용해 가져와야 했던 부분을 멤버변수로 들고있게 만들어서 클래스에서 관리하도록 변경했다.

std::string	PGConnection::GetValue(int row, int col)
{
	return PQgetvalue(_result, row, col);
}

bool	PGConnection::IsNull(int row, int col)
{
	return PQgetisnull(_result, row, col);
}

 

Redis Connection 클래스 변경사항

기존 RedisConnection의 execute 함수 사용

q1 = base + ":nickname " + nickname;
q2 = base + ":password " + hashedPassword;
q3 = base + ":email " + email;

redisReply *reply;
RedisConnection* redis = GDBConnectionPool->PopRedis();
reply = redis->Execute(q1.c_str());//set 닉네임
if (RedisConnection::isReplyError(reply))
{//에러면 함수안에서 free해줌.
    GDBConnectionPool->Push(redis);
    return;
}
a1 = reply->str;
freeReplyObject(reply);
bool	RedisConnection::Connect(const char* ip, int port)
{
	_connection = redisConnect(ip, port);
	if (_connection == NULL || _connection->err) {
		if (_connection != NULL)
			std::cout << _connection->errstr << '\n';
		else
			std::cout << "Can't allocate redis context\n";
		return false;
	}
	return true;
}

레디스도 string에 +를 덕지덕지붙여서 쿼리를 만들어 넣었었다.레디스는 SQL인젝션이 불가능하다곤 하지만 string에 계속 복사 붙여넣기를 하고, size를 넘어가면 내부에서 새로 realloc을 해서 복사하는 과정이 있을텐데 그것도 별로 좋지 못한 것들이니 postgres와 비슷하게 수정해준다.

수정된 RedisConnection의 execute 함수 사용

redisCommand는 printf처럼 인자를 받는 식이라 forward를 사용해 가변인자 탬플릿을 만들어봤다.

string	redisSetNickname = "SET %s:nickname %s EX 300";
RedisConnection* redis = GDBConnectionPool->PopRedis();

if (false == redis->Execute(redisSetNickname, temp_id.c_str(), nickname.c_str()))
{
    redis->Clear();
    GDBConnectionPool->Push(&redis);
    return;
}
redis->Clear();
template<typename... Args>
bool	Execute(const std::string& query, Args&&... args)
{
    _reply = (redisReply*)redisCommand(_connection, query.c_str(), std::forward<Args>(args)...);
    if (IsReplyError())
    {
        Clear();
        return false;
    }
    return true;
}

 

pg와 마찬가지로 reply를 멤버변수로 갖고있고, 외부에서는 함수를 사용해 값을 얻을 수 있도록 변경했다.

다만 pg는 모든 데이터가 string으로 나오는데, redis는 같은 쿼리에도 서로 다른 타입의 결과가 나올 수 있어서 사용에 주의해야한다.

std::string	GetStr() { return _reply->str; }
size_t		GetStrLen() { return _reply->len; }
long long	GetInt() { return _reply->integer; }
double		GetFloat() { return _reply->dval; }
bool		IsNull() { return _reply->type == REDIS_REPLY_NIL; }

원래 목표인 Bind는 인증서버에는 좀 과한 느낌이라 안했는데, 나중에 게임서버를 처음부터 다시 만들때 필요하면 만들어보자.

'프로젝트 > Project_Island' 카테고리의 다른 글

22. 인증서버 마무리  (0) 2026.03.15
21. EmailAPI - SMTP  (0) 2026.03.13
19. 로그인  (0) 2026.03.11
18. 회원가입  (0) 2026.03.10
17. Session, EpollEvent 포인터 참조 문제 해결  (0) 2026.03.09

+ Recent posts