카테고리 없음

basic_03_예제 코드 분석

Wally's 2023. 4. 24. 23:10

Program.cs

// WebApplication 클래스의 CreateBuilder() 메서드를 호출하여 builder 인스턴스 생성 
// 이 객체는 애플리케이션의 구성을 빌드하는데 사용
var builder = WebApplication.CreateBuilder(args);


// 애플리케이션에 컨트롤러를 추가한다
// 웹 애플리케이션에서 컨트롤러를 사용하기 위한 서비스를 등록하는 역할
// 컨트롤러는 HTTP 요청에 대한 응답을 생성하는데 사용되며,
// 애플리케이션에서 사용할 수 있는 엔드포인트를 정의
builder.Services.AddControllers();


// WebApplicationBuilder 객체를 사용하여 애플리케이션을 빌드하고 
// WebApplication 객체를 반환
var app = builder.Build();


// ASP.NET Core의 라우팅 기능을 사용하기 위해 UseRouting() 메서드를 호출
app.UseRouting();


// 라우팅이 완료되면, 애플리케이션의 엔드포인트를 정의하기 위해 UseEndpoints() 메서드를 호출한다. 
// 여기에서는 컨트롤러를 매핑하기 위해 MapControllers() 메서드를 호출한다.
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });


// WebApplication 객체의 Configuration 속성을 사용하여 
// appsettings.json 파일과 같은 구성 파일에서 설정을 로드한다.
IConfiguration configuration = app.Configuration;


// DBManager 클래스의 Init() 메서드를 호출하여 데이터베이스 연결을 초기화한다.
DBManager.Init(configuration);


// WebApplication 객체의 Run() 메서드를 호출하여 애플리케이션을 실행
// 구성 파일에서 읽은 서버 주소를 사용한다.
app.Run(configuration["ServerAddress"]);

 

DBManager.cs

using CloudStructures; // CloudStructures는 Redis서비스를 사용하기 위한 라이브러리
using MySqlConnector; // MySqlConnector는 MySQl데이터베이스에 접근하기 위한 라이브러리
using SqlKata.Execution; // SqlKata는 SQL 쿼리 빌더와 실행 라이브러리

public class DBManager
{
    static string GameDBConnectString; //MySQL데이터베이스 연결 문자열을 저장하는 정적 변서
    static string RedisAddress; //Redis 서버 주소를 저장하는 정적 변수


    // Redis 서버와 연결을 가져오거나 설정할수 있는 RedisConnection 객체를 정적 프로퍼티로 선언
    public static RedisConnection RedisConn { get; set; }
    
    
    // 애플리케이션의 구성 파일에서 데이터베이스와 Redis 연결 정보를 읽어들이고 
    // RedisConnection 객체를 초기화하는 메서드
    public static void Init(IConfiguration configuration)
    {
    	// appsettings.json 파일에서 DBConnection 섹션의 MySqlGame 값을 읽어서 
        // GameDBConnectString 변수에 저장
        GameDBConnectString = configuration.GetSection("DBConnection")["MySqlGame"];
        
        // appsettings.json 파일에서 DBConnection 섹션의 Redis 값을 읽어서 
        // RedisAddress 변수에 저장
        RedisAddress = configuration.GetSection("DBConnection")["Redis"];
        
        
        // RedisConfig 객체를 생성
        // 이 객체는 Redis 서버 연결 정보를 설정하는데 사용
        var config = new RedisConfig("basic", RedisAddress);
        
        // RedisConfig 객체를 사용하여 RedisConnection 객체를 초기화
        RedisConn = new RedisConnection(config);
    }

	
    // MySQL 데이터베이스와 연결하여 SqlKata 라이브러리의 QueryFactory 객체를 반환하는 비동기 함수
    public static async Task<QueryFactory> GetGameDBQuery()
    {
    	// MySqlConnection 객체를 생성하고 
        // GameDBConnectString 변수의 값으로 MySQL 데이터베이스에 연결
        var connection = new MySqlConnection(GameDBConnectString);
        
        
        // 데이터베이스 연결을 비동기적으로 연다.
        await connection.OpenAsync();
		
        
        // MySQL 데이터베이스에서 실행할 SQL 쿼리를 컴파일할 MySqlCompiler 객체를 생성
        var compiler = new SqlKata.Compilers.MySqlCompiler();
        
        // MySqlConnection 객체와 MySqlCompiler 객체를 사용하여 
        // QueryFactory 객체를 생성
        var queryFactory = new SqlKata.Execution.QueryFactory(connection, compiler);
		
        
        // QueryFactory 객체를 반환
        return queryFactory;
    }

 

Security.cs

using System.Security.Cryptography;
using System.Text;

public class Security
{
	// "abcdefghijklmnopqrstuvwxyz0123456789" 문자열로 초기화되는 상수
    // 이 문자열은 salt 값과 인증 토큰을 생성할 때 사용
    private const String AllowableCharacters = "abcdefghijklmnopqrstuvwxyz0123456789";
	
    
    // alt 값과 비밀번호를 받아들이고, 이를 SHA-256 알고리즘을 사용하여 해싱된 비밀번호를 반환
    public static String MakeHashingPassWord(String saltValue, String pw)
    {
    	// HA256.Create()을 사용하여 SHA-256 해싱 객체 생성
        var sha = SHA256.Create();
        
        // sha.ComputeHash(Encoding.ASCII.GetBytes(saltValue + pw))는 
        // 입력 문자열의 해시 값을 계산한다
        var hash = sha.ComputeHash(Encoding.ASCII.GetBytes(saltValue + pw));
        
        // stringBuilder 객체를 사용하여 해시 된 바이트를 문자열로 변환하고 반환
        var stringBuilder = new StringBuilder();
        
        // hash 배열의 각 요소를 16진수 문자열로 변환하여 stringBuilder에 추가하는 반복문
        // {0:x2} 문자열 형식 지정자는 현재 반복에서 처리중인 바이트를 16진수로 표현
        // 2자리로 패딩하여 나타내도록 지정
        // 0xAB 바이트를 처리 할 때, 결과 문자열은 "ab"
        
        // 반복문의 결과로 hash 배열의 모든 요소를 16진수 문자열로 표현한 stringBuilder 객체가 반환
        foreach (var b in hash)
        {
            stringBuilder.AppendFormat("{0:x2}", b);
        }

        return stringBuilder.ToString();
    }
	
    // 64 바이트의 난수 배열을 생성
    // 이 배열을 사용하여 salt 문자열 만든다
    public static String SaltString()
    {
        var bytes = new Byte[64];
        
        // RandomNumberGenerator.Create()을 사용하여 임의의 바이트 시퀀스를 생성
        using (var random = RandomNumberGenerator.Create())
        {
            random.GetBytes(bytes);
        }
		
        // AllowableCharacters의 문자 중에서 랜덤으로 선택하여 인증 토큰 문자열을 만듬
        return new String(bytes.Select(x => AllowableCharacters[x % AllowableCharacters.Length]).ToArray());
    }
	
    
    // 길이가 25 바이트인 임의의 바이트 배열을 생성하고,
    // 이를 AllowableCharacters 문자열에서 선택된 문자로 변환하여 반환
    public static String CreateAuthToken()
    {
        var bytes = new Byte[25];
        
        // RandomNumberGenerator 클래스를 사용하여 
        // 무작위로 생성 된 바이트 값을 bytes 배열에 채운다
        using (var random = RandomNumberGenerator.Create())
        {
        	// 세 번째 줄에서는 bytes 배열의 각 바이트 값을 AllowableCharacters 문자열의 인덱스로 변환
            // x % AllowableCharacters.Length는 AllowableCharacters 문자열의 길이로 나눈 나머지를 구하여, 
            // 이 값을 AllowableCharacters 문자열에서의 인덱스로 사용
            // 이를 통해 bytes 배열의 각 요소는 AllowableCharacters 문자열에서 유효한 인덱스를 가진다
            random.GetBytes(bytes);
        }
		
        // Select 메서드와 ToArray 메서드를 사용하여 각 바이트 값을 AllowableCharacters 문자열에서 선택된 문자로 변환
        // 하나의 문자열로 결합하여 반환
        return new String(bytes.Select(x => AllowableCharacters[x % AllowableCharacters.Length]).ToArray());
    }

}

 

ErrorCode.cs

// ErrorCode라는 이름의 열거형 정의
// 연관된 상수들을 하나의 자료형으로 정의하는 방법
// 에러 코드를 열거형으로 정의하면 에러를 쉽게 구분하고,
// 코드 작성시 실수를 방지 할 수 있다.
// 이러한 에러 코드를 반환하는 것은 API의 에러 응답 등에 활용될 수 있다

public enum ErrorCode : int
{
    None = 0,
    
    Create_Account_Fail_Duplicate = 11,
    Create_Account_Fail_Exception = 12,
    
    Login_Fail_NotUser = 16,
    Login_Fail_PW = 17,
}

 

 

LoginController.cs

using CloudStructures.Structures;
using Microsoft.AspNetCore.Mvc;
using SqlKata.Execution;


[ApiController]
[Route("[controller]")]
public class Login : ControllerBase
{
	// HTTP POST 요청에 대한 핸들러 선언
    // PkLoginRequest 객체가 전송되며, 핸들러는 PkLoginREsponse객체를 반환한다.
    
    [HttpPost]
    public async Task<PkLoginResponse> Post(PkLoginRequest request)
    {
     	// 새로운 PkLoginResponse 객체 생성
        var response = new PkLoginResponse();
        
        // ErrorCode.None으로 결과 속성을 초기화
        response.Result = ErrorCode.None;
        
		
        // DBManager.GetGameDBQuery()메서드를 사용하여 데이터 베이스 커넥션을 가져온다
        // account 테이블에서 이메일 주소가 request.Email인 사용자 정보를 가져온다.
        // 가져온 사용자 정보에 대해 해싱된 비밀번호를 만들고,
        // 이 비밀번호가 사용자 정보와 일치하는지 확인한다.
        // 사용자 정보나 해싱된 비밀번호가 없는 경우,
        // 로그인 실패 코드를 반환함
        // 마지막으로, 데이터베이스 커넥션을 폐기한다.
        
        using (var db = await DBManager.GetGameDBQuery())
        {
            var userInfo = await db.Query("account").Where("Email", request.Email).FirstOrDefaultAsync<DBUserInfo>();

            if (userInfo == null || string.IsNullOrEmpty(userInfo.HashedPassword))
            {
                response.Result = ErrorCode.Login_Fail_NotUser;
                return response;
            }
                        
            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.Dispose();
        }

		// ecurity.CreateAuthToken() 메소드를 사용하여 인증 토큰(tokenValue)을 생성한 후, 
        // Redis 서버에 해당 토큰 값을 저장
        
        // Security.CreateAuthToken() 메서드를 호출하여, tokenValue 변수에 저장
        string tokenValue = Security.CreateAuthToken();
        
        var idDefaultExpiry = TimeSpan.FromDays(1);
        
        // Redis 서버에 해당 이메일 주소(request.Email)를 키로 사용하여 RedisString<string> 객체를 생성
        // 이 객체는 Redis 서버에서 해당 키에 대한 값을 문자열로 저장하고, 
        // 만료 시간(idDefaultExpiry)을 1일로 지정하여 만료 시간이 지난 후 해당 값이 자동으로 삭제되도록 설정
        var redisId = new RedisString<string>(DBManager.RedisConn, request.Email, idDefaultExpiry);
        
        // SetAsync() 메소드를 사용해 Redis 서버에 해당 이메일 주소를 키로 사용하여 인증 토큰 값을 저장
        await redisId.SetAsync(tokenValue);
		
        // response.Authtoken 속성에 tokenValue 값을 할당
        response.Authtoken = tokenValue;
        
        // response 객체를 반환
        return response;
        
        // 인증 토큰이 생성되고, 해당 토큰 값이 Redis 서버에 저장되며, 
        // 클라이언트에게도 해당 토큰 값이 반환
    }
}


public class PkLoginRequest
{
    public string? Email { get; set; }
    public string? Password { get; set; }
}

public class PkLoginResponse
{
    public ErrorCode Result { get; set; }
    public string Authtoken { get; set; }
}

class DBUserInfo
{
    public string Email { get; set; }
    public string HashedPassword { get; set; }
    public string SaltValue { get; set; }
}