고양국제고/셰어텍

[셰어텍] 6. SQL 서버와 회원가입 / 로그인 구현

카루-R 2021. 10. 13. 18:00
반응형

환영합니다, Rolling Ress의 카루입니다.

와... 진짜 어마어마하네요. 저로서는 그렇습니다.

사실 저는 욕심이 크거든요. 진짜 앱 개발이란 게 무엇인지, 고양국제고에 똑똑히 보여주도록 합시다.

힘내자고요, 다들.

카톡으로 계속 말씀드렸지만 오늘은 SQL 삽질을 했습니다.

자, 오전에 서버를 하나 빌렸습니다. 참고로 특정 유형에 속하는 서버가 아니라서, 여기에서 홈페이지도 만들고, FTP 올려서 파일 공유도 하고, SQL도 같이 할 수 있습니다. 쉽게 설명드리자면, YEEP 홈페이지 말고 우리가 아예 처음부터 만드는 것도 가능하다는 거. 물론 그럴려면 HTML을 배워야 하지만... 저는 귀찮아서 아직 손을 안 대고 있어요. 일주일이면 배우는데, 이놈의 귀차니즘.

회원가입과 로그인 기능을 만드려면 서버에서 사용자들의 정보를 어떻게 저장할지 양식을 지정해줘야 합니다. 저는 프로그램에서 쓰인 그대로 닉네임, 아이디, 비밀번호, 지역으로 설정했습니다. 참고로 닉네임과 지역은 한글이 사용되기 때문에 자료형에 'n'을 붙여야 합니다. 안 그러면 다 깨져요.

그 다음, 새 XML 문서로 app.config 파일을 만들어줍니다. 여기에 서버에서 받은 연결 문자열을 넣고, 코드에서는 따로 참조를 할 겁니다.

자, Conet 메인페이지를 살짝 바꾸었습니다. 처음 사용하는 경우 왼쪽에서 회원가입을 해야 하고, 기존 사용자의 경우 오른쪽에서 로그인을 할 수 있습니다. 우선 설명을 위해 회원가입을 새로 하겠습니다.

몇 가지의 오류 검사를 거쳐 항목이 모두 적합한지 확인합니다. 중간에 알 수 없는 외계어는 정규표현식으로 숫자와 문자를 검출하기 위한 장치입니다. 혹시 모르죠 싹 다 외계어인 것 같기도

자, 아무튼 이렇게 개인정보를 입력하고 '회원가입' 버튼을 누르면 회원가입이 진행됩니다. 이 뒤에서 SQL 서버와 통신이 이루어지는 거고요.

using SqlConnection sql = new(); 
sql.ConnectionString = connectionString; 
await sql.OpenAsync(); 

UserInfoDAC dac = new(sql); 
if (await dac.IDExistsAsync(IdRegBox.Text)) 
{ 
    RegisterProgress.Visibility = Visibility.Collapsed; 
    RegisterText.Text = "ID가 이미 존재합니다.";
    return; 
}

if (await dac.NickNameExistsAsync(NickNameRegBox.Text)) 
{ 
    RegisterProgress.Visibility = Visibility.Collapsed; 
    RegisterText.Text = "닉네임이 이미 존재합니다."; 
    return; 
}

당연하지만, ID나 닉네임이 이미 존재하는 경우 가입을 거부합니다. 이 경우 즉시 통신이 중단됩니다.

그렇지 않은 경우, 우선 비밀번호를 암호화합니다. 여기서는 SHA-256을 사용하였습니다. 무슨 값을 넣든, 64자리의 암호화된 문자열로 나오게 됩니다. 참고로 이건 단방향 암호화라 처음 비밀번호가 뭔진 저도 몰라요. 웹사이트에서도 이러한 방식을 쓰는데, 어차피 암호화된 값만 같으면 되니 굳이 원래의 비밀번호를 알 필요가 없기 때문입니다.

성공한 경우, 위와 같은 화면이 뜨면서 메인페이지로 넘어가게 됩니다.

참고로 프로그램과 서버 사이에 통신을 이어주는 래퍼 클래스가 존재합니다. (다른 파일입니다) 계정 정보를 받아 .NET 타입에서 SQL 타입으로 변환시켜줍니다. SQL 명령어를 직접 전달해주기도 하죠.

통신에 성공하면, 이처럼 서버에 표가 남게 됩니다. 등록한 순서대로 차곡차곡 쌓일 거예요. "비밀번호가 왜 저렇게 생겼어?"라고 하시는 분들도 계실텐데, 아까 말씀드렸죠. 저건 암호화된 문자열입니다. 그리고 SHA는 해시 생성이기 때문에 복호화도 불가능하다고 말씀드렸어요. 그래서, 제가 이렇게 여러분의 암호화된 비밀번호를 보고 있어도 뭔지 모릅니다. 안심하시고 사용하셔도 됩니다. 혹시 모르지 뭐 갑자기 이걸 SHA에서 AES로 바꾸면...


자, 그럼 이제 암호화된 비밀번호를 가지고 로그인을 해봅시다. 참고로, 원래는 ID와 비밀번호를 통째로 대조하여 로그인을 하지만, 그럼 사용자들이 불편해요. ID가 있는지 먼저 확인을 하고, 그 다음 비밀번호를 대조하는 식으로 진행할 겁니다.

프론트엔드 코드는 간단합니다. 그냥 ID가 있는지 조회를 하고, 없으면 유저 데이터를 통째로 끌어온 다음에 비밀번호를 비교하면 됩니다. 원래는 비밀번호를 먼저 대조하고 유저 데이터를 꺼냈는데, 그렇게 하니까 2중 참조 오류가 나더라고요. 아, 제가 실수했습니다. 스트림을 정리하지 않아서 그런 거라, 앞에 using만 붙였습니다.

 

public async Task<UserInfo> GetUserAsync(string ID) 
{
    string txt = "SELECT * FROM UserInfo WHERE ID = @ID";
    SqlCommand cmd = new(txt, sql);
    SqlParameter paramID = new("ID", SqlDbType.Char, 20); 
    paramID.Value = ID; 
    cmd.Parameters.Add(paramID); 
    
    using SqlDataReader reader = await cmd.ExecuteReaderAsync(); 
    reader.Read(); 
    return new(reader.GetString(0).Trim(), ID, reader.GetString(2).Trim(), new()); 
}

참고로 유저 데이터를 끌어오는 건 이런 방식으로 할 수 있습니다. 어차피 가입 시에 ID는 유일하게 결정했으니, 저렇게 끌어와도 끌려오는 Row는 한 줄 뿐입니다. 그럼 그냥 Read() 한 번만 호출해주면 각각의 Column에서 데이터를 가져올 수가 있는 거죠. 물론, 저렇게 끌어오는 데이터의 패스워드도 이미 해시값입니다.

private string EncryptPassword(string input) 
{ 
    using SHA256 sha256 = SHA256.Create(); 
    byte[] encryptBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input)); 
    return Convert.ToBase64String(encryptBytes); 
}

 

그 암호화는 바로 이렇게 생겼습니다. 생각보다 간단하죠? 네. 이미 암호화를 해주는 함수들이 널렸기 때문에, 굳이 제가 만들어 쓸 필요가 없습니다. 마이크로소프트에서 이건 떠먹여줘요. 매우 좋습니다. 다만 그래도 UTF8 -> byte -> string으로 변환해야 하는 귀찮음은 있지만.

로그인이 되면 이렇게 뜹니다. 제 닉네임을 어떻게 알까요? 아까 유저 데이터를 끌어왔죠. 거기서 가져온 겁니다.

TMI를 하나 작성해보겠습니다.

닉네임이 카루로 뜨는 게 보이죠. 라에 계정으로 들어가보겠습니다.

라에 계정으로 글을 작성하면 닉네임이 라에로 뜹니다. 어때요, 이정도면 꽤 괜찮지 않나요?

반응형