basic03
๊ฐ๊ด
MySQL๊ณผ Redis๋ฅผ ์ฌ์ฉํ์ฌ ๊ณ์ ์์ฑ๊ณผ ๋ก๊ทธ์ธ ์ฒ๋ฆฌ๋ฅผ ํฉ๋๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ: MySQL(Sqlkata), Redis(CloudStructures)
MySql ์คํค๋ง
CREATE TABLE IF NOT EXISTS account_db.`account`
(
AccountId BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '๊ณ์ ๋ฒํธ',
Email VARCHAR(50) NOT NULL UNIQUE COMMENT '์ด๋ฉ์ผ',
SaltValue VARCHAR(100) NOT NULL COMMENT '์ํธํ ๊ฐ',
HashedPassword VARCHAR(100) NOT NULL COMMENT 'ํด์ฑ๋ ๋น๋ฐ๋ฒํธ',
CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '์์ฑ ๋ ์ง'
)
Porgram.cs
๋น๋ ์์ฑ
var builder = WebApplication.CreateBuilder(args);
๋ฏธ๋ค์จ์ด-์ปจํธ๋กค๋ฌ ์ถ๊ฐ
builder.Services.AddControllers();
app ์์ฑ
var app = builder.Build();
์๋น์ค ์ถ๊ฐ-Routing
app.UseRouting();
์๋น์ค ์ถ๊ฐ-Endpoints ์ฌ์ฉ(MapControllers)
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
DBManager ์ด๊ธฐํ
config ํ์ผ์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ํ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ด
IConfiguration configuration = app.Configuration; DBManager.Init(configuration);
app ์คํ
app.Run(configuration["ServerAddress"]);
app.Run()์ ๋งค๊ฐ๋ณ์๋ก hostIp:port ๋ฃ์ผ๋ฉด ๊ทธ๊ฑฐ๋ก listenํ๊ฒ ๋จ
LoginController.cs
Request POST /login ์ ๋ํ ์ฒ๋ฆฌ
์๋ตํ ์ธ์คํด์ค ์์ฑ (์ดํ ๋ก์ง์์ ์ํฉ์ ๋ง๊ฒ ๊ฐ ์ค์ )
var response = new PkLoginResponse(); response.Result = ErrorCode.None;
SqlKata.QueryFactory์ ์๋ช ์ฃผ๊ธฐ ๋ธ๋ก์ ๋ง๋ค์ด์ค
using (var db = await DBManager.GetGameDBQuery()) { ... 3๋ฒ ๊ณผ์ ... }
2๋ฒ์ using๋ธ๋ก ์์์ QueryFactory๋ฅผ ํตํด user info ๊ฐ์ ธ์์ ์ ํจ์ฑ ํ์ธํ๊ธฐ (Authentication(์ธ์ฆ))
// parm : PkLoginRequest request using (var db = await DBManager.GetGameDBQuery()) { // user ์ ๋ณด๋ฅผ db์์ ๊ฐ์ ธ์ค๊ธฐ var userInfo = await db.Query("account").Where("Email", request.Email).FirstOrDefaultAsync<DBUserInfo>(); // db ์์ ๊ฐ๊ณ ์จ ์ ๋ณด์ ๋ก๊ทธ์ธ์ ํ์ํ ๋ด์ฉ์ด ์กด์ฌํ๋์ง ํ์ธ if (userInfo == null || string.IsNullOrEmpty(userInfo.HashedPassword)) { response.Result = ErrorCode.Login_Fail_NotUser; return response; } // ์ ์ ๊ฐ ์ ๋ ฅํ password๋ฅผ hash ๊ฐ์ผ๋ก ๋ฐ๊ฟ var hashingPassword = Security.MakeHashingPassWord(userInfo.SaltValue, request.Password); // ๋น๋ฐ๋ฒํธ ์ฒดํฌ. ์ ํจํ์ง ์์ผ๋ฉด ์๋ฌ ๋ฐํ Console.WriteLine($"[Request Login] Email:{request.Email}, request.Password:{request.Password}, saltValue:{userInfo.SaltValue}, hashingPassword:{hashingPassword}"); if (userInfo.HashedPassword != hashingPassword) { response.Result = ErrorCode.Login_Fail_PW; return response; } // ๊ฐ์ ธ์จ db ์ ๋ณด ๋์์ฃผ๊ธฐ (ํ ๋น ํด์ ) db.Dispose(); }
์ ํจํ ์ฌ์ฉ์๋ผ๊ณ ํ๋จ ๋์ผ๋ฉด, ํ ํฐ ๋ฐ๊ธํ๊ธฐ
string tokenValue = Security.CreateAuthToken(); var idDefaultExpiry = TimeSpan.FromDays(1);
์ฌ์ฉ์์๊ฒ ์ค ํ ํฐ ์ ๋ณด redis์ ๋ฃ๊ธฐ
var redisId = new RedisString<string>(DBManager.RedisConn, request.Email, idDefaultExpiry); await redisId.SetAsync(tokenValue);
์ดํ์ ํด๋ผ์ด์ธํธ๊ฐ ๋ฐ์ดํฐ ์์ฒญ์ ์ด ํ ํฐ ์ ๋ณด๋ฅผ ์๋ฒ์๊ฒ ๋ณด๋ผ ๊ฒ์ด๋ฉฐ, ์๋ฒ๋ ์ด ํ ํด์ Redis์ ์๋์ง ํ์ธํ์ฌ ์ฌ์ฉ์ ์ธ์ฆ ๋ฐ ๊ถํ์ ํ์ธ ํ ์ ์์
์ฌ์ฉ์์๊ฒ ํ ํฐ ๋ณด๋ด์ฃผ๊ธฐ
response.Authtoken = tokenValue; return response;
CreateAccount.cs
Request POST /CreateAccount ์ ๋ํ ์ฒ๋ฆฌ
์๋ตํ ์ธ์คํด์ค ์์ฑ (์ดํ ๋ก์ง์์ ์ํฉ์ ๋ง๊ฒ ๊ฐ ์ค์ )
var response = new PkCreateAccountResponse {Result = ErrorCode.None};
๋น๋ฐ๋ฒํธ๋ฅผ ์ ์ฅํ hash ๊ฐ ๋ง๋ค๊ธฐ
var saltValue = Security.SaltString(); var hashingPassword = Security.MakeHashingPassWord(saltValue, request.Password);
SqlKata.QueryFactory์ ์๋ช ์ฃผ๊ธฐ ๋ธ๋ก์ ๋ง๋ค์ด์ค
using (var db = await DBManager.GetGameDBQuery()) { ... 4๋ฒ๊ณผ์ ... }
db์ ์๋ก์ด ๊ณ์ ์ ๋ณด ๋ฑ๋ก(insert)
try { // db ์ ๊ณ์ ์ ๋ณด insert : Async(๋น๋๊ธฐ)๋ฐฉ์์ผ๋ก ๋ฃ์ var count = await db.Query("account").InsertAsync(new { Email = request.Email, SaltValue = saltValue, HashedPassword = hashingPassword }); // ์์ insert๊ฐ ์คํจ ํ์ ๊ฒฝ์ฐ. DB์์ Email์ constraint๋ฅผ ์ค๋ณต ๋ถ๊ฐ๋ก ์ค์ if (count != 1) { response.Result = ErrorCode.Create_Account_Fail_Duplicate; } Console.WriteLine($"[Request CreateAccount] Email:{request.Email}, saltValue:{saltValue}, hashingPassword:{hashingPassword}"); } // QueryFactory ์์ ์๋ฌ ๋ฐ์ catch(Exception ex) { Console.WriteLine(ex.ToString()); response.Result = ErrorCode.Create_Account_Fail_Exception; return response; } // ๊ฐ์ ธ์จ db ์ ๋ณด ๋์์ฃผ๊ธฐ (ํ ๋น ํด์ ) finally { db.Dispose(); }
์ try-catch๋ก? db์ฐ๊ฒฐ ์คํจ or QueryFactory ๊ธฐ๋ฅ ์คํจ์ ๊ฒฝ์ฐ๋ฅผ ๋๋นํ๊ฑด๊ฐ?
์๋ต ๋ฐํ
return response;
Security.cs
๋ณด์, ์ํธํ ๊ด๋ จ๋ ์์ ์ ์ํ ๋ฉ์๋ ๊ตฌํ
private const String AllowableCharacters = "abcdefghijklmnopqrstuvwxyz0123456789";
ํจ์ค์๋ ํด์ฑ ์ ์ฉ
public static String MakeHashingPassWord(String saltValue, String pw) { var sha = SHA256.Create(); var hash = sha.ComputeHash(Encoding.ASCII.GetBytes(saltValue + pw)); var stringBuilder = new StringBuilder(); foreach (var b in hash) { stringBuilder.AppendFormat("{0:x2}", b); } return stringBuilder.ToString(); }
ํด์ฑ์ ์ํ ์ํธ ํค ์์ฑ
public static String SaltString() { var bytes = new Byte[64]; using (var random = RandomNumberGenerator.Create()) { random.GetBytes(bytes); } return new String(bytes.Select(x => AllowableCharacters[x % AllowableCharacters.Length]).ToArray()); }
์ธ์ฆ ํ ํฐ ์์ฑ
public static String CreateAuthToken() { var bytes = new Byte[25]; using (var random = RandomNumberGenerator.Create()) { random.GetBytes(bytes); } return new String(bytes.Select(x => AllowableCharacters[x % AllowableCharacters.Length]).ToArray()); }
DBManager.cs
์ฐ๊ฒฐํ DB server ๋ค์ ๋ํ ip/port ์ ๋ณด ๋ฑ์ ๊ฐ๊ณ ์์
static string GameDBConnectString; static string RedisAddress; public static RedisConnection RedisConn { get; set; } public static void Init(IConfiguration configuration) { GameDBConnectString = configuration.GetSection("DBConnection")["MySqlGame"]; RedisAddress = configuration.GetSection("DBConnection")["Redis"]; var config = new RedisConfig("basic", RedisAddress); RedisConn = new RedisConnection(config); }
QueryFactory๋ฅผ ๋ฐํํ๋ ํจ์ ์ ๊ณต
public static async Task<QueryFactory> GetGameDBQuery() { var connection = new MySqlConnection(GameDBConnectString); await connection.OpenAsync(); var compiler = new SqlKata.Compilers.MySqlCompiler(); var queryFactory = new SqlKata.Execution.QueryFactory(connection, compiler); return queryFactory; }
๋น๋๋ ๋ผ๋๋ฐ, API์ ์ ๋๋ก ์๋ต ๋ชปํจ
์์ ์ฝ๋์ผ ๋ฟ, ์์ง DB server๋ ์ฐ๊ฒฐ๋์ด ์์ง ์์

Last updated