Main DB(MySQL)과 Redis를 사용하며, 간단한 로그인 기능정도가 구현되어있는 Web API 서버를 구현
기본 구성은 다음과 같음
Program.cs
프로그램의 진입점(entry point) 역할
using APIServer.Services;
using ZLogger;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTransient<IAccountDb, AccountDb>();
builder.Services.AddSingleton<IRedisDb, RedisDb>();
builder.Services.AddControllers();
builder.Logging.ClearProviders();
builder.Logging.AddZLoggerConsole();
var app = builder.Build();
IConfiguration configuration = app.Configuration;
app.UseRouting();
app.MapControllers();
app.Run(configuration["ServerAddress"]);
- WebApplication.CreateBuilder(args)
웹 어플리케이션의 빌더 객체 생성 - builder.Services.AddTransient<IAccountDb, AccountDb>()
IAccountDb 인터페이스와 AccountDb간의 종속성 주입
서비스 생명주기를 Transient로 설정하였기 때문에 매 요청마다 새로운 인스턴스를 생성하여 제공 - builder.Services.AddSingleton<IRedisDb, RedisDb>()
IRedisDb 인터페이스와 RedisDb간의 종속성주입
서비스 생명주기를 Singleton으로 설정하였기 때문에 애플리케이션의 수명주기 동안 하나의 인스턴스만을 생성하여 제공 - builder.Services.AddControllers()
웹 API 컨트롤러 등록 - builder.Logging.ClearProviders(), builder.LoggingAddZLoggerConsole()
기존 로깅 공급자를 제거하고 ZLoggerConsole 공급자를 사용하도록 변경 - builder.Build()
애플리케이션을 빌드 - 소스 코드를 컴파일하고 실행 가능한 웹 애플리케이션 객체를 생성 - IConfiguration configuration = app.Configuration
애플리케이션의 구성 객체를 가져옴 - 일반적으로 appsettings.json 파일을 통해 설정 - app.UseRouting()
애플리케이션이 라우팅 기능을 사용하도록 설정 - app.MapControllers()
등록된 컨트롤러를 매핑하여 요청을 처리할 수 있도록 설정 - app.Run(configuration["ServerAddress"])
ServerAddress이라는 키에 해당하는 구성 정보를 사용하여 애플리케이션을 실행
AccountDb
계정정보는 메인 DB인 MySQL을 통해 관리되도록 설정
public class AccountDb : IAccountDb
{
readonly ILogger<AccountDb> _logger;
IDbConnection _dbConn;
QueryFactory _queryFactory;
public AccountDb(ILogger<AccountDb> logger, IConfiguration configuration)
{
_logger = logger;
var AccountDbConnectString = configuration.GetSection("DBConnection")["AccountDb"];
_dbConn = new MySqlConnection(AccountDbConnectString);
var compiler = new SqlKata.Compilers.MySqlCompiler();
_queryFactory = new SqlKata.Execution.QueryFactory(_dbConn, compiler);
_logger.ZLogInformation("MySQL Db Connected");
}
- 생성자를 통해 SqlKata로 쿼리를 처리하기 위한 QueryFactory 변수를 설정
- ASP.NET Core에서 제공하는 ILogger 인터페이스를 사용하여 로그를 기록
단, 실제 로그 기록에는 ZLogger를 사용하여 성능을 향상
public async Task<ErrorCode> CreateAccount(string email, string password)
{
try
{
await _queryFactory.Query("account").InsertAsync(new
{
Email = email,
Password = password
});
return ErrorCode.None;
}
catch (MySqlException ex)
{
if (ex.Number == 1062)
{
_logger.ZLogError(ex, $"[CreateAccount] ErrorCode: {ErrorCode.CreateAccountFailDuplicate}, Email: {email}, ErrorNum : {ex.Number}");
return ErrorCode.CreateAccountFailDuplicate;
}
else
{
_logger.ZLogError(ex, $"[CreateAccount] ErrorCode: {ErrorCode.CreateAccountFailException}, Email: {email}, ErrorNum : {ex.Number}");
return ErrorCode.CreateAccountFailException;
}
}
}
- Account 생성시 사용되는 함수
- InsertAsync() 메서드를 사용해 DB에 실제로 데이터를 저장, 이상이 없을시 에러코드 None 반환
- try-catch로 MySqlException 발생을 확인한 경우 헤당 오류에 맞게 처리하고 적절한 에러코드 반환
1062번은 이미 해당 키가 존재함을 뜻함
public async Task<Tuple<ErrorCode, Int64>> VerifyAccount(string email, string password)
{
try
{
var accountinfo = await _queryFactory.Query("account").Where("Email", email).FirstOrDefaultAsync<Account>();
if (accountinfo is null || accountinfo.AccountId == 0)
{
return new Tuple<ErrorCode, Int64>(ErrorCode.LoginFailUserNotExist, 0);
}
if (accountinfo.Password != password)
{
_logger.ZLogError($"[VerifyAccount] ErrorCode: {ErrorCode.LoginFailPwNotMatch}, Email: {email}");
return new Tuple<ErrorCode, Int64>(ErrorCode.LoginFailPwNotMatch, 0);
}
return new Tuple<ErrorCode, Int64>(ErrorCode.None, accountinfo.AccountId);
}
catch (MySqlException ex)
{
_logger.ZLogError(ex, $"[VerifyAccount] ErrorCode: {ErrorCode.VerifyAccountFailException}, Email: {email}, ErrorNum : {ex.Number}");
return new Tuple<ErrorCode, Int64>(ErrorCode.VerifyAccountFailException, 0);
}
}
}
- 로그인시 Account 정보 확인에 사용되는 함수
- 계정이 존재하는지 확인 후 비밀번호가 일치하는지 확인
- Tuple 형식으로 에러코드와 계정의 ID번호를 반환
RedisDb
인증과 관련한 작업은 Redis를 사용하도록 설정
public class RedisDb : IRedisDb
{
readonly ILogger<RedisDb> _logger;
RedisConnection _redisConn;
public RedisDb(ILogger<RedisDb> logger, IConfiguration configuration)
{
_logger = logger;
var RedisAddress = configuration.GetSection("DBConnection")["Redis"];
var Redisconfig = new RedisConfig("basic", RedisAddress);
_redisConn = new RedisConnection(Redisconfig);
_logger.ZLogInformation("Redis Db Connected");
}
- 생성자를 통해 cloudstructures 라이브러리로 redis 관련 작업을 하기 위한 _reidsConn 변수를 설정
- ASP.NET Core에서 제공하는 ILogger 인터페이스를 사용하여 로그를 기록
단, 실제 로그 기록에는 ZLogger를 사용하여 성능을 향상
public async Task<ErrorCode> RegistUser(string email, string authToken, Int64 accountId)
{
var key = "UID_" + accountId;
var user = new AuthUser
{
Email = email,
AuthToken = authToken,
AccountId = accountId,
State = UserState.Default.ToString()
};
try
{
var redis = new RedisString<AuthUser>(_redisConn, key, LoginTimeSpan());
if (await redis.SetAsync(user, LoginTimeSpan()) == false)
{
_logger.ZLogError($"[RegistUser] ErrorCode: {ErrorCode.LoginFailAddRedis}, Email: {email}");
return ErrorCode.LoginFailAddRedis;
}
}
catch (Exception ex)
{
_logger.ZLogError(ex, $"[RegistUser] ErrorCode: {ErrorCode.RegistUserFailException}, Email: {email}");
return ErrorCode.RegistUserFailException;
}
return ErrorCode.None;
}
public TimeSpan LoginTimeSpan()
{
return TimeSpan.FromMinutes(RediskeyExpireTime.LoginKeyExpireMin);
}
}
- Login에 성공한 이후의 유저 요청을 인증하기 위한 유저 등록 작업을 수행
- RedisString<> 클래스로 String 타입의 데이터를 다루는 객체를 생성
키는 유저의 ID에 따라 유니크하도록 설정하여 이후 인증작업에 사용
LoginTimeSpan() 함수로 지정한 데이터 만료시간이 지나면 해당 데이터가 자동으로 삭제됨 - SetAsync() 메서드로 미리 생성해놓은 AuthUser 객체를 Redis에 저장
CreateAccountController
CreateAccount 요청시 실행되는 컨트롤러
[ApiController]
[Route("[controller]")]
public class CreateAccount : ControllerBase
{
readonly ILogger<CreateAccount> _logger;
readonly IAccountDb _AccountDb;
public CreateAccount(ILogger<CreateAccount> logger, IAccountDb AccountDb)
{
_logger = logger;
_AccountDb = AccountDb;
}
[HttpPost]
public async Task<PkCreateAccountResponse> Post(PkCreateAccountRequest request)
{
var response = new PkCreateAccountResponse();
response.Result = ErrorCode.None;
var errorCode = await _AccountDb.CreateAccount(request.Email, request.Password);
if (errorCode != ErrorCode.None)
{
response.Result = errorCode;
return response;
}
_logger.ZLogInformation($"{request.Email} Account Created");
return response;
}
}
- AccountDb는 program.cs에서 등록한 서비스에서 생성된 인스턴스를 활용
- AccountDb의 CreateAccount() 메서드르 사용하여 계정 생성 작업을 수행
LoginController
Login 요청시 실행되는 컨트롤러
[ApiController]
[Route("[controller]")]
public class Login : ControllerBase
{
readonly ILogger<Login> _logger;
readonly IAccountDb _AccountDb;
readonly IRedisDb _redisDb;
public Login(ILogger<Login> logger, IAccountDb AccountDb, IRedisDb redisdb)
{
_logger = logger;
_AccountDb = AccountDb;
_redisDb = redisdb;
}
[HttpPost]
public async Task<PkLoginResponse> Post(PkLoginRequest request)
{
var response = new PkLoginResponse();
response.Result = ErrorCode.None;
var (errorCode, accountId) = await _AccountDb.VerifyAccount(request.Email, request.Password);
if (errorCode != ErrorCode.None)
{
response.Result = errorCode;
return response;
}
var authToken = CreateAuthToken();
errorCode = await _redisDb.RegistUser(request.Email, authToken, accountId);
if (errorCode != ErrorCode.None)
{
response.Result = errorCode;
return response;
}
_logger.ZLogInformation($"{request.Email} Login Success");
response.Authtoken = authToken;
return response;
}
public string CreateAuthToken()
{
const string AllowableCharacters = "abcdefghijklmnopqrstuvwxyz0123456789";
var bytes = new Byte[25];
using (var random = RandomNumberGenerator.Create())
{
random.GetBytes(bytes);
}
return new string(bytes.Select(x => AllowableCharacters[x % AllowableCharacters.Length]).ToArray());
}
}
- AccountDb와 RedisDb는 program.cs에서 등록한 서비스에서 생성된 인스턴스를 활용
- AccountDb의 VerifyAccount() 메서드와 RedisDb의 RegistUser() 메서드를 사용하여 로그인 작업을 진행
추가, 수정 및 보완이 필요한점
- 비밀번호를 입력받은 그대로 사용하기 때문에 보안상 문제가 발생
> hashing하여 사용하도록 구조 변경 - 로그를 단순하게 콘솔에 출력중
> 로그를 파일에 출력되도록 수정하고, 로그의 형식도 좀 더 정돈되도록 변경 - 로그인 이후의 요청이 발생할시 RegistUser에서 발급받은 AuthToken을 확인하여 인증 과정을 거치도록 추가
'개인공부 > Web API 게임 서버 공부' 카테고리의 다른 글
배경지식 - Web 서버 구조 (0) | 2023.04.23 |
---|---|
배경지식 - ZLogger (0) | 2023.04.20 |
배경지식 - Redis (0) | 2023.04.20 |
배경지식 - ORM (1) | 2023.04.20 |
배경지식 - C# (0) | 2023.04.20 |
댓글