[Android Studio] 안드로이드에서 로그인 기능 구현하기 [2]

         



안녕하세요? 오늘은 지난 포스팅에서 마무리 하지 못한 회원가입/로그인 마무리를 해보겠습니다.

지난 포스팅에서는 다음과 같은 기능을 구현했었어요.


1. 아이디, 비밀번호 입력하는 화면 만들기

2. 비밀번호와 비밀번호검증칸 만들기

3. AsyncTask를 이용해 DB에 아이디, 패스워드 입력하기

지난번 포스팅 바로가기



이번 포스팅에서는 회원가입 마무리와 로그인 처리를 개발 할 예정입니다. 구체적으로는 다음과 같습니다.


1. 서버와 클라이언트 사이의 통신. 리턴값 셋팅하기

2. 아이디와 비밀번호를 이용해 로그인 하기

3. 회원가입시 이메일 보내기


그럼 시작해 볼까요?



 1. 서버와 클라이언트 사이의 통신. 리턴값 셋팅하기


지난 포스팅에서 회원가입을 호출하면 DB에 아이디와 비밀번호를 저장하는 기능을 만들었습니다. 언뜻보면 다 만든 것 처럼 생각 될 수도 있지만, 사실 거의 완성되지 않은 프로세스라고 생각합니다. 그 이유는???


학교다니면서 코딩할 때와 실제로 회사에와서 '실무'를 할 때의 차이점이 몇가지 있습니다. 그 중에 하나가 바로 '예외처리'와 '응답'인데요. 만약 학교에서 하는 간단한 프로젝트였다면 여기까지 개발하고 끝냈을 겁니다. 하지만 실무에서는 이제부터가 시작이죠. 우리는 이제부터 이 코드가 정상적으로 실행되지 않으면 어떻게 될지를 고민해야 합니다.


일단 우리가 만든 회원가입이 정상인지 비정상인지 어떻게 판단할까요? 지금은 판단 할 수 있는 것이 아무것도 없습니다. 서버에서 응답이 안오기 때문이죠. 우리는 어떤 기능을 개발할 때 항상 그것에 대한 응답(리턴값)을 생각해야합니다. 함수를 만들 때도 return으로 끝나듯이 말이에요.


응답은 여러가지 값이 될 수 있습니다. '정상', '오류', '에러', '중복값있음' 과 같이 말이죠. 이렇게 한글로 표현하는 것보다 코드에서는 숫자나 영어와 같은 '에러코드'를 사용합니다. C언어에서는 보통 0보다 크면 정상. 0보다 작은면 오류라고 표현합니다. SQL에서는 정상적으로 수행되었다면 sqlcode값을 0으로 보여주죠.


이렇게 우리도 우리가 만든 프로세스에 코드를 만들어 봅시다. 일단 정상이면 0000을 리턴하게 만들죠. 4자리인 이유는 그냥.. 4바이트가 왠지 안정적이자나요.


그렇다면 우리가 만든 회원가입이 정상이라는 것을 어떻게 확인할 수 있을까요. 이 기능이 하는 일은 아이디와 비밀번호를 받아서 DB에 INSERT하는 것이기 때문에 INSERT가 정상적으로 되었다면 우리는 이 프로세스가 정상적으로 종료되었다고 판단할 수 있습니다. 따라서 쿼리문의 결과를 확인할 수 있는 sqlcode값을 이용해 응답값을 만들어 봅시다.


1
2
3
4
 $sql = "INSERT INTO USERS(USERID, PASSWORD) VALUES('$id', '$pw')";
 
   $result = mysql_query($sql);
 
cs


위 코드에서 mysql_query()가 실행되면 result라는 결과값을 리턴합니다. 


설명 ¶

resource mysql_query ( string $query [, resource $link_identifier ] )

mysql_query()는 link_identifier로 지정한 데이터베이스 서버에 하나의 질의를 전송합니다. (다중 질의는 지원하지 않습니다)


반환값 ¶

mysql_query()는 SELECT, SHOW, DESCRIBE, EXPLAIN, 결과셋을 반환하는 기타 구문에서 성공시 resource를, 오류시 FALSE를 반환합니다.

mysql_query()는 다른 형식의 SQL 구문, INSERT, UPDATE, DELETE, DROP 등에서 성공하면 TRUE를, 실패하면 FALSE를 반환합니다.

좀 더 자세한 설명은 링크를 참고하세요.



mysql_query의 경우 INSERT쿼리에 대해 TRUE, FALSE만을 반환합니다. 

따라서 리턴 값에 조건을 걸어 쿼리가 성공적으로 실행되었는지 체크할 수 있습니다. 


1
2
3
4
5
6
7
8
9
   // result of sql query
   if(!$result)
   {
            die("mysql query error");
   }
   else
   {
            echo"0000";
   }
cs


위에서 result = mysql_query 로 리턴값을 result에 넣었기 때문에 if문으로 쿼리의 정상 실행 여부를 체크 할 수 있습니다.

정상실행되면 0000을 실패하면 mysql query error를 화면에 출력합니다. 이 값들이 안드로이드에서 서버에 쿼리를 호출했을 때의 응답값이 됩니다. 


/* 서버에서 응답 */
Log.e("RECV DATA",data);

if(data.equals("0000"))
{
Log.e("RESULT","성공적으로 처리되었습니다!");
}
else
{
Log.e("RESULT","에러 발생! ERRCODE = " + data);
}


코드를 위처럼 짜놨습니다. 서버에서 받은 데이터가 0000이면 성공적으로 처리되었습니다!! 메시지를, 0000이 아니라면 에러 발생! 이라는 문구와 서버에서 받은 코드를 출력하도록 말이죠. 이렇게 에러코드를 출력하면 에러가 발생했을때 에러코드를 서버담당자에게 전달함으로써 어떤에러인지 쉽게 파악 가능합니다.


하지만 지금 서버에서는 쿼리의 성공/실패만 알고 성공시 코드가 0000으로만 설정되있습니다. 에러코드가 분류가 되어있지 않습니다. 따라서 쿼리 실행 결과 코드를 반환하는 함수를 이용해 에러코드까지 출력하도록 바꿔봅시다.


1
2
3
4
5
6
 $sql = "INSERT INTO USERS(USERID, PASSWORD) VALUES('$id', '$pw')";
 
   $result = mysql_query($sql);
 
   // result of sql query
   echo mysql_errno($connect);
cs


쿼리 성공여부를 판단하는 소스부분을 위와같이 변경했습니다. 여기서 사용한 mysql_errno는 마지막에 실행된 쿼리의 코드를 반환하는 함수입니다.


/* 서버에서 응답 */
Log.e("RECV DATA",data);

if(data.equals("0"))
{
Log.e("RESULT","성공적으로 처리되었습니다!");
}
else
{
Log.e("RESULT","에러 발생! ERRCODE = " + data);
}

mysql_errno는 쿼리가 정상 실행 되었을때 '0' 값을 리턴하기 때문에 소스도 약간 수정합니다.



어플을 다시 실행해서 아까와 같은 값을 넣으면 위와같이 에러코드가 떨어집니다. 에러코드 1062는 중복되는 값을 테이블에 입력했을 때 발생하는 에러코드입니다. 여기서 발생하는 에러코드는 sqlcode값이므로 구글링 해보시면 각 에러코드 별 에러내용을 쉽게 찾을 수 있습니다.


실제로 실무에서 SQL을 사용하는 프로세스에서는 이와 같은 방법으로 errcode를 설정해 놓습니다. sqlcode != 0 일경우를 정상실행으로 식별하고 나머지를 에러로 판단하곤 합니다. SQL을 사용하는 경우 에러코드를 sqlcode로 사용하지만, 쿼리사용이 없을경우 개발자가 임의로 에러코드를 생성하여 사용하는 경우도 있습니다. 


서버에서 넘어오는 코드를 개발자만 알고있으면 안되겠죠?

에러코드에 따라 적절한 메시지를 띄우는 코드를 추가합시다.


@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);

/* 서버에서 응답 */
Log.e("RECV DATA",data);

if(data.equals("0"))
{
Log.e("RESULT","성공적으로 처리되었습니다!");
alertBuilder
.setTitle("알림")
.setMessage("성공적으로 등록되었습니다!")
.setCancelable(true)
.setPositiveButton("확인", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
AlertDialog dialog = alertBuilder.create();
dialog.show();
}
else
{
Log.e("RESULT","에러 발생! ERRCODE = " + data);
alertBuilder
.setTitle("알림")
.setMessage("등록중 에러가 발생했습니다! errcode : "+ data)
.setCancelable(true)
.setPositiveButton("확인", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
AlertDialog dialog = alertBuilder.create();
dialog.show();
}
}

aSynkTask에서 onPostExecute 메소드를 오버라이드합시다. doInBackground에서는 객체를 생성하는게 안되니 후처리는 onPostExecute에서 해주셔야 됩니다. (전처리는 onPreExecute) 서버에서 내려오는 코드를 가지고 0일경우에는 성공 메시지를, 실패일 경우에는 에러코드와 실패 메시지를 뿌려줍니다.

보통 대고객서비스인 경우는 에러코드 별로 메시지를 다르게 뿌려주지만, 여기서는 하지 않습니다!! (예제이므로..)





 2. 아이디와 비밀번호를 이용해 로그인 하기



 로그인 기능을 구현하는 것은 회원가입 기능과 유사합니다. 다만 다른점은 DB 쿼리에서 INSERT문 대신 SELECT문을 사용하는 것 뿐입니다. 회원가입과 별개로 로그인 php코드를 작성합니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?
  header('content-type: text/html; charset=utf-8'); 
  // 데이터베이스 접속 문자열. (db위치, 유저 이름, 비밀번호)
  $connect=mysql_connect"db서버주소""아아디""비밀번호") or die( "SQL server에 연결할 수 없습니다.");
 
  mysql_query("SET NAMES UTF8");
  // 데이터베이스 선택
  mysql_select_db("db이름",$connect);
 
  // 세션 시작
  session_start();
 
  $id = $_POST[u_id];
  $sql = "SELECT password FROM USERS WHERE USERID = '$id'";
 
  $result = mysql_query($sql);
 
  // result of sql query
  if($result)
  {
    $row = mysql_fetch_array($result);
    if(is_null($row[password]))
    {
      echo "Can not find ID";
    }
    else
    {
      echo "$row[password]";
    }
  }
  else
  {
   echo mysql_errno($connect);
  }
?>
cs


크롬에서는 php코드가 이쁘게 안나왔는데, 브라우저를 사파리로 바꿨더니 이쁘게 나오네요. 

회원가입과 똑같이 클라이언트에서 POST형식으로 ID를 받고, DB에서 해당 ID의 PASSWORD를 찾아서 리턴해주는 소스입니다.

쿼리 아래 if문은 쿼리가 정상실행이 되지 않았을 때의 예외처리입니다. 회원가입과 다른 점은 SELECT쿼리에서 해당하는 쿼리가 없을 경우에도 정상처리를 출력하기 때문에, SELECT쿼리의 값이 없을때(0개의 row를 리턴했을때)에는 ID를 찾을 수 없다는 메시지를 넣었습니다. 보통 sqlcode에서는 1403을 리턴하면 쿼리가 없는 건데.. PHP는 해당하는 쿼리를 못찾아도 0을 반환하더군요.


아래는 클라이언트에서 로그인php를 호출하는 소스입니다.


public void bt_Login(View v)
{
try{
sId = etId.getText().toString();
}catch (NullPointerException e)
{
Log.e("err",e.getMessage());
}

loginDB lDB = new loginDB();
lDB.execute();

}


public class loginDB extends AsyncTask<Void, Integer, Void> {

@Override
protected Void doInBackground(Void... unused) {

/* 인풋 파라메터값 생성 */
String param = "u_id=" + sId + "";
Log.e("POST",param);
try {
/* 서버연결 */
URL url = new URL(
"http://웹서버주소/login.php");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.connect();

/* 안드로이드 -> 서버 파라메터값 전달 */
OutputStream outs = conn.getOutputStream();
outs.write(param.getBytes("UTF-8"));
outs.flush();
outs.close();

/* 서버 -> 안드로이드 파라메터값 전달 */
InputStream is = null;
BufferedReader in = null;
String data = "";

is = conn.getInputStream();
in = new BufferedReader(new InputStreamReader(is), 8 * 1024);
String line = null;
StringBuffer buff = new StringBuffer();
while ( ( line = in.readLine() ) != null )
{
buff.append(line + "\n");
}
data = buff.toString().trim();

/* 서버에서 응답 */
Log.e("RECV DATA",data);

if(data.equals("0"))
{
Log.e("RESULT","성공적으로 처리되었습니다!");
}
else
{
Log.e("RESULT","에러 발생! ERRCODE = " + data);
}

} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

return null;
}

}

안드로이드에서 edittext에 값이 없을경우 getText() 메소드 때문에 앱이 튕기는 현상이 있습니다. getText()로 널을 빼오면 에러가 나기 때문에, try catch문을 사용해서 null값을 가져올경우를 예외처리 해줍니다.


지금은 회원가입쪽 소스를 복붙해와서 응답값을 에러코드로만 받고있는데, 로그인의 경우 ID로 password를 받아와서 실제 입력한 password와 일치하는지 비교해야 합니다.


여기서 조금 고민을 해야하는데, 비밀번호 비교를 클라이언트에서 해야하는지, 서버에서 해야하는지 입니다.

사실 이 부분에 대해서는 별 생각이 없었는데, 상용화 된 서비스를 (소스를)까보니 서버에서 비교하더라구요. 실질적으로 password라는 정보를 클라이언트에서 다룬다는게 위험할 수 도 있다는 생각이 듭니다. 


따라서 여기서는 서버에서 비교해서 성공여부를 반환하는 걸로 만들겠습니다. (지극히 내 생각 대로 ^오^)


서버에서 비교하려면.. 일단 비밀번호값도 서버로 보내야겠지용.


try{
sId = etId.getText().toString();
sPw = etPw.getText().toString();
}catch (NullPointerException e)
{
Log.e("err",e.getMessage());
}


try catch문에 위와같이 password를 가져오는 부분을 추가하고, 서버에 POST형식으로 보내는 곳에도 추가합니다.

뭐 그냥 회원가입에 쓴건 고대로 쓰면 됩니다. ㅎ_ㅎ


/* 인풋 파라메터값 생성 */
String param = "u_id=" + sId + "&u_pw=" + sPw + "";


자, 이제 클라이언트에서 보낸 값을 서버쪽에서 컨트롤 해 봅시다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?
// 비밀번호 비교
  $sql = "SELECT IF(strcmp(PASSWORD,'$pw'),0,1) pw_chk FROM USERS  WHERE USERID = '$id'";
 
  $result = mysql_query($sql);
 
  // 쿼리 결과
  if($result)
  {
    $row = mysql_fetch_array($result);
    if(is_null($row[pw_chk]))
    {
      echo "Can not find ID";
    }
    else
    {
      echo "$row[pw_chk]";   // 0이면 비밀번호 불일치, 1이면 일치
    }
  }
  else
  {
   echo mysql_errno($connect);
  }
?>
cs


PHP에서 위와같이 코드를 수정해줍니다. 비밀번호를 PHP코드를 이용하기 보다는 쿼리에서 비교할 수 있습니다.

IF문이 생소할 수 있을텐데 (저도 생소함) 오라클에서 사용하는 DECODE와 동일한 기능을 제공합니다. 입력한 패스워드와 DB의 패스워드가 일치하면 1을, 불일치하면 0을 반환합니다.

@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);

if(data.equals("1"))
{
Log.e("RESULT","성공적으로 처리되었습니다!");
alertBuilder
.setTitle("알림")
.setMessage("성공적으로 등록되었습니다!")
.setCancelable(true)
.setPositiveButton("확인", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(MainActivity.this, MainMenu.class);
startActivity(intent);
finish();
}
});
AlertDialog dialog = alertBuilder.create();
dialog.show();
}
else if(data.equals("0"))
{
Log.e("RESULT","비밀번호가 일치하지 않습니다.");
alertBuilder
.setTitle("알림")
.setMessage("비밀번호가 일치하지 않습니다.")
.setCancelable(true)
.setPositiveButton("확인", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//finish();
}
});
AlertDialog dialog = alertBuilder.create();
dialog.show();
}
else
{
Log.e("RESULT","에러 발생! ERRCODE = " + data);
alertBuilder
.setTitle("알림")
.setMessage("등록중 에러가 발생했습니다! errcode : "+ data)
.setCancelable(true)
.setPositiveButton("확인", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//finish();
}
});
AlertDialog dialog = alertBuilder.create();
dialog.show();
}
}


클라이언트에서는 위와같이 수정합니다. 회원가입에 사용했던 AlertDialog도 한꺼번에 적용해 줍니다. 패스워드가 일치할 경우 성공 메시지와 함께 메뉴 액티비티로 넘어갑니다.

비밀번호가 일치하지 않는경우와 다른 에러가 발생한경우는 그대로 머무릅니다. 보통 비밀번호가 일치하지 않는경우 오류횟수를 누적하는 로직이 들어가는데, 여기서는 구현하지 않겠습니다. DB에 오류횟수 컬럼을 추가하고 오류횟수가 몇회 이상 올라가면 로그인을 차단하는 기능을 넣어볼 수 있습니다. 보통 사용서비스에는 모두 들어가 있지요~


위와 같이 알림이 정상적으로 뜨는지 체크하고 넘어갑시다.


3.회원가입시 이메일 보내기


이제 어느정도 회원가입/로그인 기능이 갖추어졌습니다. 하지만! 가장 복잡하고도 어려워보이는 기능인 회원가입시 이메일 보내기! 가 남았습니다.

이부분을 적용시키려면 회원가입로직도 조금 변경해야 합니다. 회원가입 신청시 고유의 키값을 메일로 보내서 그값을 인증할 경우에만 DB에 회원정보가 들어가게 만들어야 하기 때문이죠.


일단 메일을 보내는건 오픈소스를 이용합니다.


바로가기 - http://b.redinfo.co.kr/87


위 블로그에서 소스와 가이드를 잘 읽어보시면 아래와 같은 소스를 통해 간단히 메일을 보낼 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php //sendmail.php
/* 클래스 파일 로드 */
include "Sendmail.php";
 
/* 클래스 객체 변수 선언 */
$sendmail = new Sendmail();
 
/*
 + $to       : 받는사람 메일주소 ( ex. $to="hong <hgd@example.com>" 으로도 가능)
 + $from     : 보내는사람 이름
 + $subject  : 메일 제목
 + $body     : 메일 내용
 + $cc_mail  : Cc 메일 있을경우 (옵션값으로 생략가능)
 + $bcc_mail : Bcc 메일이 있을경우 (옵션값으로 생략가능)
*/
$to="tkdlek11112@gmail.com"
$from="Master";
$subject="메일 제목입니다.";
$body="메일 내용입니다.";
$cc_mail="";
$bcc_mail="";
 
/* 메일 보내기 */
$sendmail->send_mail($to$from$subject$body,$cc_mail,$bcc_mail)
?>
cs


위 소스를 실행하면 실제로 메일이 와있습니다!!



두둥! 이렇게 쉽게 구현되다니 ^오^

하지만 여기서 끝이 아니죠. 메일을 보내는데 메일 내용으로 사용자를 인증하는 기능을 넣어야합니다.

이 부분에 대해서는 보안이 상당히 중요한데요, 일반적인 방법은 메일로 URL을 보내 해당 URL을 클릭하면 DB에 접근해 해당하는 ID에 대해 인증되었다고 업데이트를 해주는 로직이기 때문입니다. 실제로 URL을 통해 DB에 접근하기 때문에 악용될 수가 있습니다.


따라서 해당 URL에 대해 접근 가능한 시간을 두어 1시간 이후 자동 소멸되던지.. 같은 방법을 사용하는데, 뭐 제가 만드는건 그렇게 보안이 필요한게 아니기 때문에 간단하게 만들어 보겠습니다. (암호화따윈 없습니다!)


일단 기본적인 로직은 이렇습니다. 사용자를 관리하는 DB에는 다음과 같이 VERIFY라는 컬럼이 있습니다.


VERIFY컬럼은 기본값으로 N으로 설정되어있는데, 이메일 인증을 하면 Y로 바뀌도록 할 예정입니다. 

로그인할 경우에도 VERIFY값이 'Y'일 때만 로그인 되도록 설정할 예정이구요. 이렇게 되면 회원가입을 해도 인증하지 않는다면 로그인이 되지 않겠지요?


일ㄷ나 저 VERIFY값을 Y로 업데이트치는 소스를 만들어봅시다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 <? //verify.php
    header('content-type: text/html; charset=utf-8'); 
 
    // 데이터베이스 접속 문자열. (db위치, 유저 이름, 비밀번호)
    $connect=mysql_connect"""""") or  
        die( "SQL server에 연결할 수 없습니다.");
 
    
    mysql_query("SET NAMES UTF8");
   // 데이터베이스 선택
   mysql_select_db("DB",$connect);
   // 세션 시작
   session_start();
   $id = $_REQUEST[u_id];
 
   echo "$id";
 
   $sql = "UPDATE USERS SET VERIFY = 'Y' WHERE USERID = '$id'";
 
   $result = mysql_query($sql);
 
   if($result)
   {
    echo "인증되었습니다.";
   }
   else
   {
    echo mysql_errno($connect);
   }
?>
 
cs


URL로 바로 날릴꺼니까 GET방식으로 만들었습니다. 이제 http://서버/verify.php?u_id=인증ID 형식으로 URL을 날리면 DB에 있는 VERIFY값이 Y로 변하는 것을 볼 수 있습니다.



자 이제 회원가입을 하면 mail을 보내는것만 만들면 됩니다.

이전에 만들었던 회원가입 소스를 살짝 고칩시다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 
 <?
    header('content-type: text/html; charset=utf-8'); 
 
      /* 클래스 파일 로드 */
      include "Sendmail.php";
      /* 클래스 객체 변수 선언 */
      $sendmail = new Sendmail();
 
    // 데이터베이스 접속 문자열. (db위치, 유저 이름, 비밀번호)
    $connect=mysql_connect"""""") or  
        die( "SQL server에 연결할 수 없습니다.");
    
    mysql_query("SET NAMES UTF8");
   // 데이터베이스 선택
   mysql_select_db("",$connect);
 
   // 세션 시작
   session_start();
 
   $id = $_POST[u_id];
   $pw = $_POST[u_pw]; 
 
   $sql = "INSERT INTO USERS(USERID, PASSWORD) VALUES('$id', '$pw')";
 
   $result = mysql_query($sql);
 
   $to="$id@gmail.com"
   $from="Master";
   $subject="인증메일입니다.";
   $body="http://choits.iptime.org/snclib_verify.php?u_id=$id";
   $cc_mail="";
   $bcc_mail="";
 
   // result of sql query
   echo mysql_errno($connect);
 
   $sendmail->send_mail($to$from$subject$body,$cc_mail,$bcc_mail);
?>
 
cs

고친다고는 했지만 사실 메일 보내는거만 추가했을 뿐입니다. 저는 id를 메일주소로 받을 생각이었기 때문에 따로 메일주소를 입력하지 않고 id를 메일주소로 사용했습니다. (실제로 사용할 때는 gmail.com이 아니라 회사도메인을 사용할 예정이지만요.) 위와같이 만들면 이제 회원가입을 할 때 인증메일을 함께 보냅니다.


자 이렇게 메일이 오고 선택하면~! N이었던 VERIFY값이 Y로 변합니다.

아까 말한것처럼 GET방식의 호출이기 때문에 URL에 아이디와 서버주소가 드러나게 됩니다. 악용될 경우 해킹의 위험이 있지요. 따라서 암호화나 임의의 난수를 만들어 비비꼬아서 사용합니다 보통은.. 여기서는 안해요 ^오^


생각보다 메일 보내기가 쉽게(?) 끝났지만, 아직 로그인이 정확이 모두 구현된 것은 아닙니다. VERIFY값에 따른 로그인 처리나 비밀번호 오류횟수 등에 대해서는 다음 포스팅에서 다루도록하겠습니다.


아직 기능만 구현한 것이니 마무리는 다음번에 ... ^^


댓글(59)

  • 이전 댓글 더보기
  • 안녕하세요
    2017.07.31 15:20

    public class validate extends AsyncTask<Void,Void,Void>{


    @Override
    protected Void doInBackground(Void... voids) {

    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("http://skarnddhfl.cafe24.com/";)
    .build();
    service service = retrofit.create(service.class);
    Call<ResponseBody> response = service.putid(id);
    response.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
    try {

    Log.v("test",response.body().string());
    String asdf = response.body().string();
    Log.v("test1",asdf);
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {

    }
    });

    return null;
    }
    }


    로그창
    07-31 06:17:41.942 4818-4818/? I/zygote: Not late-enabling -Xcheck:jni (already on)
    07-31 06:17:41.957 4818-4818/? W/zygote: Unexpected CPU variant for X86 using defaults: x86
    07-31 06:17:42.176 4818-4818/com.example.bong.baseballmanager I/InstantRun: starting instant run server: is main process
    07-31 06:17:42.368 4818-4852/com.example.bong.baseballmanager D/OpenGLRenderer: HWUI GL Pipeline
    07-31 06:17:42.396 4818-4852/com.example.bong.baseballmanager I/OpenGLRenderer: Initialized EGL, version 1.4
    07-31 06:17:42.396 4818-4852/com.example.bong.baseballmanager D/OpenGLRenderer: Swap behavior 1
    07-31 06:17:42.397 4818-4852/com.example.bong.baseballmanager W/OpenGLRenderer: Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...
    07-31 06:17:42.397 4818-4852/com.example.bong.baseballmanager D/OpenGLRenderer: Swap behavior 0
    07-31 06:17:42.406 4818-4852/com.example.bong.baseballmanager D/EGL_emulation: eglCreateContext: 0x97178480: maj 2 min 0 rcv 2
    07-31 06:17:42.423 4818-4852/com.example.bong.baseballmanager D/EGL_emulation: eglMakeCurrent: 0x97178480: ver 2 0 (tinfo 0x983f2a30)
    07-31 06:17:42.442 4818-4852/com.example.bong.baseballmanager W/android.hardware.graphics.mapper@2.0::Mapper: getService: found null hwbinder interface
    07-31 06:17:42.445 4818-4852/com.example.bong.baseballmanager I/vndksupport: sphal namespace is not configured for this process. Loading /system/lib/hw/gralloc.ranchu.so from the current namespace instead.
    07-31 06:17:42.461 4818-4852/com.example.bong.baseballmanager D/EGL_emulation: eglMakeCurrent: 0x97178480: ver 2 0 (tinfo 0x983f2a30)
    07-31 06:17:43.863 4818-4823/com.example.bong.baseballmanager I/zygote: Do partial code cache collection, code=17KB, data=22KB
    07-31 06:17:43.863 4818-4823/com.example.bong.baseballmanager I/zygote: After code cache collection, code=17KB, data=22KB
    07-31 06:17:43.863 4818-4823/com.example.bong.baseballmanager I/zygote: Increasing code cache capacity to 128KB
    07-31 06:17:45.511 4818-4823/com.example.bong.baseballmanager I/zygote: Do partial code cache collection, code=61KB, data=51KB
    07-31 06:17:45.512 4818-4823/com.example.bong.baseballmanager I/zygote: After code cache collection, code=61KB, data=51KB
    07-31 06:17:45.512 4818-4823/com.example.bong.baseballmanager I/zygote: Increasing code cache capacity to 256KB
    07-31 06:17:49.158 4818-4852/com.example.bong.baseballmanager D/EGL_emulation: eglMakeCurrent: 0x97178480: ver 2 0 (tinfo 0x983f2a30)
    07-31 06:17:49.948 4818-4823/com.example.bong.baseballmanager I/zygote: Do full code cache collection, code=90KB, data=96KB
    07-31 06:17:49.949 4818-4823/com.example.bong.baseballmanager I/zygote: After code cache collection, code=88KB, data=81KB
    07-31 06:17:50.026 4818-4852/com.example.bong.baseballmanager D/EGL_emulation: eglMakeCurrent: 0x97178480: ver 2 0 (tinfo 0x983f2a30)
    07-31 06:17:51.436 4818-4823/com.example.bong.baseballmanager I/zygote: Do partial code cache collection, code=118KB, data=98KB
    07-31 06:17:51.436 4818-4823/com.example.bong.baseballmanager I/zygote: After code cache collection, code=118KB, data=98KB
    07-31 06:17:51.436 4818-4823/com.example.bong.baseballmanager I/zygote: Increasing code cache capacity to 512KB
    07-31 06:17:52.945 4818-5022/com.example.bong.baseballmanager D/NetworkSecurityConfig: No Network Security Config specified, using platform default
    07-31 06:17:53.239 4818-4818/com.example.bong.baseballmanager V/test: f


    로그 test1은 안찍히네요..

    • 2017.07.31 15:36 신고

      Log.v("test",response.body().string());
      String asdf = response.body().string();
      Log.v("test1",asdf);

      response.body().string() 이거는 여러번 사용할 수 없습니다 ㅇ_ㅇ!!
      두 번쓰면 두번째에는 공백이 들어가서 에러가 나서 catch문으로 이동, 로그도 찍히지 않는것 같네요

  • 안녕하세요
    2017.07.31 15:21

    php상 코드

    $id = $_POST[u_id];






    // $sql = "INSERT INTO USERDATA(userid, userpw) VALUES('$id', '$pw')";
    $sqli = "SELECT * FROM MEMBER WHERE memberID = '$id'";


    $result = mysql_query($sqli);
    while($row = mysql_fetch_row($result)){


    // $checkid=$row[1];
    if($id==$row[1]){

    echo 'f';
    }

    }



    아이디중복체크를 만드는데 같은 값이 row[1]<<아이디가담겨있는부분이고 같으면 f를 표시해서 안드로이드상에서 f를 받으면 아이디를 사용불가능하게 하는거였습니다..

  • 안녕하세요
    2017.07.31 15:35

    비교했던 java상 코드입니다

    response.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
    try {

    String asdf = response.body().string();

    if (asdf.equals("f";)){
    Log.v("test","같다";);
    }

    } catch (IOException e) {
    e.printStackTrace();
    }

    }

  • 안녕하세요
    2017.07.31 15:41

    네 첫번째꺼는 이해했습니다 세번째가 제가원래 하려했던 질문입니다.ㅠ 로그가 안찍히네요 ,,,

  • 안녕하세요
    2017.07.31 15:43

    아 trim()을 사용하니 같다고 로그가찍히네요 ㅠㅠ

  • 안녕하세요
    2017.07.31 15:44

    전에도 trim() 을써썼는데 두번째 리스폰스(공백)에 트림을써서 안됐었나봐요 ㅎ 답변감사합니다

    • 2017.07.31 15:45 신고

      아닙니다~ ㅎㅎ
      즐거운 코딩하세요~

  • 감사합니다.
    2017.08.02 16:57

    다음 강의 기다리고 있습니다!! 기대하겠습니다. 빨리 만나고 싶어요!!

  • 안녕하세요
    2017.08.08 19:16

    안녕하세요, 저는 php를 이번에 처음으로 접하고 있습니다.
    다름이 아니라 저는 cafe24를 사용하는데 메일 발송 방법을 알 수 없어서 질문드립니다. 제가 생각해도 너무 어이없고 대책 없지만,
    가입 시 메일 보낼 때에 미리 테스트 하는 부분에서 어떻게 하시는지 이해가 안가서요ㅜㅜ
    저는 안드로이드 스튜디오와 cafe24만 사용하고 있습니다.
    혹시 smtp? 를 사용해야 하는 걸까요? cafe24에서는 smtp를 지원하지 않는다고 하던데 혹시 아시는 부분이실까요?ㅜㅜ 질문이 많아 죄송합니다. 너무나 초보인데 여쭤볼 곳이 없어 이렇게 주절주절하게되네요ㅜㅜ

    • 2017.08.08 21:36 신고

      지메일 계졍이 있으신가요~? 카페24를 서버로 사용하시는거 같은데 서버에 smtp는 사용하지 않아여~ 지메일자체에서 제공하는 smtp를 사용합니다. 링크를 타고 가셔서 사용법을 읽어보시면 구글smtp를 적는곳을 볼수잇을꺼에요~

    • 안녕하세요
      2017.08.09 14:46

      빠른답변감사합니다ㅠㅠ 그럼 이메일을 받는 대상도 gmail이어야 하는건가요? 너무 모른채로 질문해서 죄송헙니다..ㅜㅜ

    • 2017.08.09 15:38 신고

      아니요~ 보내는 메일만 지메일이고 받는건 상관없어요~

    • 2017.08.09 17:27

      비밀댓글입니다

  • 아하
    2017.08.09 03:12

    aSynkTask에서 onPostExecute 메소드를 오버라이드합시다. doInBackground에서는 객체를 생성하는게 안되니 후처리는 onPostExecute에서 해주셔야 됩니다. (전처리는 onPreExecute)

    이게 무슨말씀인지 하나도 모르겠습니다..
    지금 계속 에러떠가지고 ㅠ

    • 2017.08.09 15:40 신고

      자바에 객채사용법을 먼저 숙지하셔야 할것같아요~ 오버라이드 같은 ㅎㅎ asynktask에 기본형도 아시면 이해가 빠를겁니다. 구글링해보시면 자세히 나와요~ 레퍼런스 문서 보셔두 되구요

  • 아하
    2017.08.13 18:31

    로그인액티비티하고 회원가입액티비티 따로 만들어야 되는 것인가요??
    bt_login, LoginDB 얘네들
    1장에서 블로깅하신 부분에서의 액티비티랑은 별개의 액티비티로 만들어서 코딩해주어야하는건가ㅛㅇ?

    • 아하
      2017.08.13 19:25

      ++ onPostExecute 오버라이딩이 안됩니다..;; 빨간줄 뜨네요ㅠㅠ 뭐지
      registDB extends AsyncTask ...
      여기 안에다가 오버라이드했는데 왜 안될까요

    • 안녕
      2017.08.13 19:26

      package com.example. . ;

      import android.os.AsyncTask;
      import android.os.Bundle;
      import android.support.v7.app.AppCompatActivity;
      import android.util.Log;
      import android.view.View;
      import android.widget.EditText;

      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.InputStreamReader;
      import java.io.OutputStream;
      import java.net.HttpURLConnection;
      import java.net.MalformedURLException;
      import java.net.URL;

      public class JoinActivity extends AppCompatActivity {

      EditText et_id, et_pw, et_pw_chk;
      String sId, sPw, sPw_chk;

      @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_join);

      et_id = (EditText) findViewById(R.id.IdText);
      et_pw = (EditText) findViewById(R.id.PasswordText);
      et_pw_chk = (EditText) findViewById(R.id.PasswordCheckText);
      }
      public void JoinButton(View view)
      {
      sId = et_id.getText().toString();
      sPw = et_pw.getText().toString();
      sPw_chk = et_pw_chk.getText().toString();

      if(sPw.equals(sPw_chk))//근데 비밀번호를 다르게 하면 애초부터 else에 걸림
      {
      /* 패스워드 확인이 정상적으로 됨 */
      registDB rdb = new registDB();
      rdb.execute();
      }
      else
      {
      /* 패스워드 확인이 불일치 함 */

      }

      }


      public class registDB extends AsyncTask<Void, Integer, Void> {

      @Override
      protected Void doInBackground(Void... unused) {

      /* 인풋 파라메터값 생성 */
      String param = "u_id=" + sId + "&u_pw=" + sPw + "";
      try {
      /* 서버연결 */
      URL url = new URL(
      "http://172.30.1.20/Register.php";);
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded";);
      conn.setRequestMethod("POST";);
      conn.setDoInput(true);
      conn.connect();

      /* 안드로이드 -> 서버 파라메터값 전달 */
      OutputStream outs = conn.getOutputStream();
      outs.write(param.getBytes("UTF-8";));
      outs.flush();
      outs.close();

      /* 서버 -> 안드로이드 파라메터값 전달 */
      InputStream is = null;
      BufferedReader in = null;
      String data = "";

      is = conn.getInputStream();
      in = new BufferedReader(new InputStreamReader(is), 8 * 1024);
      String line = null;
      StringBuffer buff = new StringBuffer();
      while ( ( line = in.readLine() ) != null )
      {
      buff.append(line + "\n";);
      }
      data = buff.toString().trim();

      @Override
      protected void onPostExecute(Void aVoid){
      super. onPostExecute(aVoid);
      Log.e("RECV DATA", data);//data는 php상 echo값임

      if (data.equals("0";))//그래서 반환값이 0이라면 성공, 이외의 값이라면 에러코드출력
      {
      Log.e("RESULT", "성공적으로 처리되었습니다!";);
      } else {
      Log.e("RESULT", "에러 발생! ERRCODE = " + data);
      }
      }

      } catch (MalformedURLException e) {
      e.printStackTrace();
      } catch (IOException e) {
      e.printStackTrace();
      }

      return null;
      }


      }


      }

    • 2017.08.13 19:40 신고

      액티비티 따로 만드셔야되요~ 오버라이딩은 doinbackground 끝나고 새로 클래스 열어서 하셔야 되는데 안에다가 하신거 같은데용...?

  • 아하
    2017.08.13 20:26

    하..ㅠ 어느정도 해결 된 것 같습니다. 회원가입 시 onPostExecute 기능이 출력되는 것을 확인했습니다.
    그런데 else부분은 어떤 기준으로 출력이 되는것인가요? php에서 0 값밖에 들어오지 않는 것 같습니다..
    회원가입시 비밀번호를 다르게하거나, 이전에 DB에 등록했던 값과 동일하게 해줄 때 해당 else부분이 나오지 않습니다

    • 아하
      2017.08.13 20:27

      package com.example. . ;

      import android.app.AlertDialog;
      import android.content.Context;
      import android.content.DialogInterface;
      import android.os.AsyncTask;
      import android.os.Bundle;
      import android.support.v7.app.AppCompatActivity;
      import android.util.Log;
      import android.view.View;
      import android.widget.EditText;

      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.InputStreamReader;
      import java.io.OutputStream;
      import java.net.HttpURLConnection;
      import java.net.MalformedURLException;
      import java.net.URL;

      public class JoinActivity extends AppCompatActivity {
      final Context context = this;//이거 onPostExecute부분에서 필요한 것이었음

      EditText et_id, et_pw, et_pw_chk;
      String sId, sPw, sPw_chk;

      @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_join);

      et_id = (EditText) findViewById(R.id.IdText);
      et_pw = (EditText) findViewById(R.id.PasswordText);
      et_pw_chk = (EditText) findViewById(R.id.PasswordCheckText);
      }
      public void JoinButton(View view)
      {
      sId = et_id.getText().toString();
      sPw = et_pw.getText().toString();
      sPw_chk = et_pw_chk.getText().toString();

      if(sPw.equals(sPw_chk))//근데 비밀번호를 다르게 하면 애초부터 else에 걸림
      {
      /* 패스워드 확인이 정상적으로 됨 */
      registDB rdb = new registDB();
      rdb.execute();
      }
      else
      {
      /* 패스워드 확인이 불일치 함 */

      }

      }


      public class registDB extends AsyncTask<Void, Integer, Void> {

      String data = "";
      @Override
      public Void doInBackground(Void... unused) {

      /* 인풋 파라메터값 생성 */
      String param = "u_id=" + sId + "&u_pw=" + sPw + "";
      try {
      /* 서버연결 */
      URL url = new URL(
      "http://172.30.1.20/Register.php";);
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded";);
      conn.setRequestMethod("POST";);
      conn.setDoInput(true);
      conn.connect();

      /* 안드로이드 -> 서버 파라메터값 전달 */
      OutputStream outs = conn.getOutputStream();
      outs.write(param.getBytes("UTF-8";));
      outs.flush();
      outs.close();

      /* 서버 -> 안드로이드 파라메터값 전달 */
      InputStream is = null;
      BufferedReader in = null;


      is = conn.getInputStream();
      in = new BufferedReader(new InputStreamReader(is), 8 * 1024);
      String line = null;
      StringBuffer buff = new StringBuffer();
      while ( ( line = in.readLine() ) != null )
      {
      buff.append(line + "\n";);
      }
      data = buff.toString().trim();
      Log.e("RECV DATA", data);//data는 php상 echo값임


      } catch (MalformedURLException e) {
      e.printStackTrace();
      } catch (IOException e) {
      e.printStackTrace();
      }

      return null;
      }

      @Override// doInBackground종료 뒤 호출된다. 아무튼 바깥으로 빼야됐으
      protected void onPostExecute(Void aVoid) {
      super.onPostExecute(aVoid);
      /* 서버에서 응답 */
      Log.e("RECV DATA",data);

      AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context);
      if(data.equals("0";))
      {
      Log.e("RESULT","성공적으로 처리되었습니다!";);
      alertBuilder
      .setTitle("알림";);
      alertBuilder
      .setMessage("성공적으로 등록되었습니다!";)
      .setCancelable(true)
      .setPositiveButton("확인", new DialogInterface.OnClickListener() {
      @Override
      public void onClick(DialogInterface dialog, int which) {
      finish();
      }
      });
      AlertDialog dialog = alertBuilder.create();
      dialog.show();
      }
      else
      {
      Log.e("RESULT","에러 발생! ERRCODE = " + data);
      alertBuilder
      .setTitle("알림";)
      .setMessage("등록중 에러가 발생했습니다! errcode : "+ data)
      .setCancelable(true)
      .setPositiveButton("확인", new DialogInterface.OnClickListener() {
      @Override
      public void onClick(DialogInterface dialog, int which) {
      finish();
      }
      });
      AlertDialog dialog = alertBuilder.create();
      dialog.show();
      }
      }




      }


      }

    • 아하
      2017.08.13 20:35

      ++ 지금 로그인 구현부 액티비티에서도

      Log.e("RECV DATA",data);

      if(data.equals("0";))//연결이 되면 0으로 반환이 된다.
      {
      Log.e("RESULT","성공적으로 처리되었습니다!";);
      }
      else
      {
      Log.e("RESULT","에러 발생! ERRCODE = " + data);
      }

      ------------------------------------------
      아이디,비밀번호를 DB에 없는 값을 넣어줘도 log에 성공이 찍힙니다..

    • 아하
      2017.08.13 20:55

      아하. 중복에 대한 문제는 phpmyadmin에서 primary 키값을 지정해주지 않아서 발생하지 않은 것이었군요

      그런 둘째치고 로그인부분 php에서
      SELECT IF가 올바르게 동작하지 않는 것 같습니다.
      AVD에서 로그인시 id, pw각각 데이터베이스에 있는 값과 다르게 입력하게 되어도 무조건 0을 반환합니다.

      ++수정하겠습니다. 지금 제가 자꾸 변수 data값이 에러가 떠서 loginDB class내 전역변수로 바꾸었습니다. 혹시 이것이 문제 될 요소인지 확인해주셨으면합니다.



      package com.example. . ;

      import android.app.AlertDialog;
      import android.content.Context;
      import android.content.DialogInterface;
      import android.content.Intent;
      import android.os.AsyncTask;
      import android.os.Bundle;
      import android.support.v7.app.AppCompatActivity;
      import android.util.Log;
      import android.view.View;
      import android.widget.EditText;

      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.InputStreamReader;
      import java.io.OutputStream;
      import java.net.HttpURLConnection;
      import java.net.MalformedURLException;
      import java.net.URL;

      public class LoginActivity extends AppCompatActivity {
      EditText et_id, et_pw;
      String sId, sPw;


      final Context context = this;//이거 onPostExecute부분에서 필요한 것이었음
      @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_login);
      et_id = (EditText) findViewById(R.id.IdText);
      et_pw = (EditText) findViewById(R.id.PasswordText);
      }
      public void LoginButton(View view){
      try{
      sId = et_id.getText().toString();
      sPw = et_pw.getText().toString();
      }catch (NullPointerException e)
      {
      Log.e("err",e.getMessage());
      }

      loginDB lDB = new loginDB();
      lDB.execute();

      }

      public class loginDB extends AsyncTask<Void, Integer, Void> {

      String data = "";//이 값이 지금 class 내 전역변수로 작용하기 때문에 문제가 발생하는건가?
      @Override
      protected Void doInBackground(Void... unused) {

      /* 인풋 파라메터값 생성 */
      String param = "u_id=" + sId + "&u_pw=" + sPw + "";
      Log.e("POST",param);//현재 적은 id, pw 값을 log에 띄우는 것 뿐
      try {
      /* 서버연결 */
      URL url = new URL(
      "http://172.30.1.20/login.php";);
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded";);
      conn.setRequestMethod("POST";);
      conn.setDoInput(true);
      conn.connect();

      /* 안드로이드 -> 서버 파라메터값 전달 */
      OutputStream outs = conn.getOutputStream();
      outs.write(param.getBytes("UTF-8";));
      outs.flush();
      outs.close();
      /* 서버 -> 안드로이드 파라메터값 전달 */
      InputStream is = null;
      BufferedReader in = null;


      is = conn.getInputStream();
      in = new BufferedReader(new InputStreamReader(is), 8 * 1024);
      String line = null;
      StringBuffer buff = new StringBuffer();
      while ( ( line = in.readLine() ) != null )
      {
      buff.append(line + "\n";);
      }
      data = buff.toString().trim();

      /* 서버에서 응답 */
      Log.e("RECV DATA",data);

      if(data.equals("0";))//연결이 되면 0으로 반환이 된다.
      //지금 비밀번호 틀려도 0이 출력되다 문제 있음.
      {
      Log.e("RESULT","성공적으로 처리되었습니다!";);
      }
      else
      {
      Log.e("RESULT","에러 발생! ERRCODE = " + data);
      }

      } catch (MalformedURLException e) {
      e.printStackTrace();
      } catch (IOException e) {
      e.printStackTrace();
      }

      return null;
      }

      @Override
      protected void onPostExecute(Void aVoid) {
      super.onPostExecute(aVoid);

      AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context);//이거 넣어줘야 builder을 사용가능
      if(data.equals("1";))
      {
      Log.e("RESULT","성공적으로 처리되었습니다!";);
      alertBuilder
      .setTitle("알림";)
      .setMessage("성공적으로 등록되었습니다!";)
      .setCancelable(true)
      .setPositiveButton("확인", new DialogInterface.OnClickListener() {
      @Override
      public void onClick(DialogInterface dialog, int which) {
      Intent intent = new Intent(LoginActivity.this, MainActivity.class);
      startActivity(intent);
      finish();
      }
      });
      AlertDialog dialog = alertBuilder.create();
      dialog.show();
      }
      else if(data.equals("0";))
      {
      Log.e("RESULT","비밀번호가 일치하지 않습니다.";);
      alertBuilder
      .setTitle("알림";)
      .setMessage("비밀번호가 일치하지 않습니다.";)
      .setCancelable(true)
      .setPositiveButton("확인", new DialogInterface.OnClickListener() {
      @Override
      public void onClick(DialogInterface dialog, int which) {
      //finish();
      }
      });
      AlertDialog dialog = alertBuilder.create();
      dialog.show();
      }
      else
      {
      Log.e("RESULT","에러 발생! ERRCODE = " + data);
      alertBuilder
      .setTitle("알림";)
      .setMessage("등록중 에러가 발생했습니다! errcode : "+ data)
      .setCancelable(true)
      .setPositiveButton("확인", new DialogInterface.OnClickListener() {
      @Override
      public void onClick(DialogInterface dialog, int which) {
      //finish();
      }
      });
      AlertDialog dialog = alertBuilder.create();
      dialog.show();
      }
      }


      }


      public void JoinButton(View view){//회원가입으로 넘어감
      Intent joinIntent = new Intent(LoginActivity.this, JoinActivity.class);//레지스터액티비티로 넘어갈 수 있도록
      LoginActivity.this.startActivity(joinIntent);
      }

      }

  • 안녕하세요
    2017.08.25 14:41

    또 질문있어 남깁ㄴ디ㅏ ㅠㅠ
    $sql = "UPDATE USERS SET VERIFY = 'Y' WHERE USERID = '$id'";
     
       $result = mysql_query($sql);
     
       if($result)
       {
        echo "인증되었습니다.";
       }
       else
       {
        echo mysql_errno($connect);
       }

    이 부분에서 제가 id값을 이메일로 해서 예를들어, 1234@naver.com이라고 치면
    verify?id=1234@naver.com 이라고 할 경우
    check the manual that corresponds to your MySQL server version for the right syntax to use near '@naver.com' at line 1
    요로코롬 에러가 납니다 ㅠㅠ 해결 방법이 있나요?ㅠㅠ

    해결했습니다...바보같았네요..

  • 안녕하세요
    2017.08.25 15:08

    smtp는 어떻게 설정하나요..? 올려주신 싸이트 봐도 안나와있어서 ㅠㅠ

    • 2017.08.25 15:10 신고

      smtp는 뭐 따로 설정할필요 없구요, 해당 메일서버에서 지원하는 smtp를 사용합니다. 링크타고 가보면 gmail의 smtp서버를 사용하는걸 볼 수 있을꺼에요~

    • 안녕하세요
      2017.08.25 15:39

      근데 왜 http500 오류가 날까요?ㅠㅠ
      혹시 sendmail.php 소스 좀 보여주시면 안될까요?

  • bemore
    2018.02.14 03:44

    안녕하세요
    좋은 강의 감사합니다

    한가지 의문점이 있습니다

    회원가입시에 ID 나 PW에서 사용자가 공백을 넣거나 띄어쓰기를 두게될때 이부분을 replace를 사용하여 공백이 없게 만드는게 좋은건가요?

    아니면 그 상태에서( 공백, 띄어쓰기 사용된 상태) 가입버튼을 눌렀을때 다이얼로그를 띄워서 공백,띄어쓰기 없이 기제하라고 알리는게 좋은건가요?

    제가 여러 어플을 보니 둘 다 쓰이는거 같은데 차이점이 먼지 궁금합니다

    • 2018.02.14 08:42 신고

      안녕하세요~ 차이점은 없습니다. 서비스 제공처의 기획 의도에따라 바뀔것같아요.
      예를 들면 공백이 절대 들어갈리 없는 상황에서 공백이 들어간경우 사용자의 미스라고 생각해서 붙여주는경우가 있고(생년월일같은거는 중간에 띄어쓰기가 있을리 없으므로..), 공백도 하나의 문자로 생각해서 공백이 있으니 다시 입력하라고 하는경우입니다.
      로그인에서는 보통 공백을 탐지해서 공백이 있으면 에러 팝업을 띄우도록 하고 있습니다.

    • bemore
      2018.02.14 12:55

      답변 감사합니다

      한가지만 더 질문드리고자 합니다

      회원가입을 할때 보통 아이디나 비밀번호 등을 기제하면 숫자와 문자만 사용해서 해달라는게 많은데요

      보통 특수문자를 넣지 않는 이유가 DB저장시에 문제점이 발생할 수 있어서 그런가요?

      특히 괄호 계열이나 따옴표가 DB저장시 문제가 될것같은데 php쪽에서 처리가 가능할텐데

      굳이 숫자와 문자만 기입하라는 특수한 의도가 있을까요?

    • 2018.02.14 13:10 신고

      네 맞습니다. DB에 특수문자가 입력되는경우 쿼리가 도중에 멈출수가 있습니다. 예를들어 쿼리를 강제종료하는 문자배열이 컬럼에 저장되있을경우, SELECT * FROM 테이블 할때 그 컬럼조회하고 나서 멈춥니다.

      추가로 특수문자가 입력될경우, 소스상에서도 처리가 상당히 껄끄러운데요, 대부분 클라이언트와 서버가 사용하는 코드셋(유니코드, euc-kr 등)을 통일하지만, 어쩔수없이 다른경우가 있습니다.(시스템적으로 지원하지 않는경우) 이런경우에도 특수문자 처리가 상당히 난해하기 때문에 영문자,숫자로만 처리하는 경우가 대부분입니다.

    • bemore
      2018.02.14 14:12

      명쾌한 답변 주셔서 감사합니다^^

      초보자로서 많은걸 배워가네요!!

      좋은 하루 되세요~

      ( 진행하다가 또 질문드리러 찾아뵙겠습니다 ㅎㅎ;;)

  • 유예
    2018.03.23 18:06

    안녕하세요 블로그를 보고 안드로이드 공부를 시작했습니다.
    아이디 중복검사 중 data에 db서버 echo"0000" 이 제대로 들어가는데 if(data.equals("0000";)~else를 사용하면 data에 "0000"이 들어가 있어도 무조건 else쪽 로그가 찍힙니다.;;
    뭐가 잘못된걸지 모르겠어서 이렇게 글을 올립니다.

    ============================mainactivity.java==================

    public class MainActivity extends Activity {

    EditText join_id, join_pw, join_pwch, join_mail;
    String id, pw, pwch, mail;
    TextView join_iderr;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    join_id = (EditText) findViewById(R.id.join_id);
    join_pw = (EditText) findViewById(R.id.join_pw);
    join_pwch = (EditText) findViewById(R.id.join_pwch);
    join_mail = (EditText) findViewById(R.id.join_mail);

    join_id.addTextChangedListener(new TextWatcher() { //아이디 중복 체크
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
    Log.d("중복체크",s.toString());
    HttpTask httptask = new HttpTask();
    httptask.execute();
    }

    @Override
    public void afterTextChanged(Editable s) {

    }
    });

    }

    class HttpTask extends AsyncTask<Void, Integer, String>{

    String iddata;
    String chid = join_id.getText().toString();


    @Override
    protected String doInBackground(Void... voids) {
    String param = "u_id="+chid;
    Log.e("아이디 전송", param);
    try{
    URL url = new URL("http://192.168.xxxx.xxxx/check.php";);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestProperty("Content-Type","application/x-www-form-urlencoded";);
    conn.setRequestMethod("POST";);
    conn.setDoInput(true);
    conn.connect();

    OutputStream outs = conn.getOutputStream();
    outs.write(param.getBytes("UTF-8";));
    outs.flush();
    outs.close();

    /* 서버 -> 안드로이드 파라메터값 전달 */
    InputStream is = null;
    BufferedReader in = null;
    iddata = "";

    is = conn.getInputStream();
    in = new BufferedReader(new InputStreamReader(is), 8 * 1024);
    String line = null;
    StringBuffer buff = new StringBuffer();
    while ( ( line = in.readLine() ) != null )
    {
    buff.append(line + "\n";);
    }
    iddata = buff.toString().trim();
    Log.e("RECV IDDATA",iddata);

    } catch (MalformedURLException e) {
    e.printStackTrace();
    } catch (IOException e) {
    e.printStackTrace();
    }
    return iddata;
    }

    @Override
    protected void onPostExecute(String s) {
    super.onPostExecute(s);

    Log.e("s",s);

    //join_iderr = (TextView) findViewById(R.id.join_iderr);
    //join_iderr.setText(s);
    if(s.equals("0000";)){
    Log.e("test","0000";);
    }else{
    Log.e("test","1111";);
    }

    }
    }
    ========================================================
    ======================Log================================
    03-23 18:00:32.853 20523-20523/com.example.j.j W/System: ClassLoader referenced unknown path: /system/framework/QPerformance.jar
    03-23 18:00:32.863 20523-20523/com.example.j.j E/BoostFramework: BoostFramework() : Exception_1 = java.lang.ClassNotFoundException: Didn't find class "com.qualcomm.qti.Performance" on path: DexPathList[[],nativeLibraryDirectories=[/system/lib64, /vendor/lib64]]
    03-23 18:00:32.863 20523-20523/com.example.j.j V/BoostFramework: BoostFramework() : mPerf = null
    03-23 18:00:32.929 20523-20523/com.example.j.j D/ViewRootImpl@c8475e0[MainActivity]: ViewPostImeInputStage processPointer 1
    03-23 18:00:32.943 20523-20523/com.example.j.j D/InputMethodManager: ISS - flag : 0Pid : 20523 view : com.example.j.j
    03-23 18:00:33.232 20523-20523/com.example.j.j D/ViewRootImpl@c8475e0[MainActivity]: MSG_RESIZED: ci=Rect(0, 96 - 0, 0) vi=Rect(0, 96 - 0, 1061) or=1
    03-23 18:00:33.752 20523-20523/com.example.j.j D/ViewRootImpl@c8475e0[MainActivity]: ViewPostImeInputStage processKey 0
    03-23 18:00:33.768 20523-20523/com.example.j.j D/중복체크: 1
    03-23 18:00:33.769 20523-20568/com.example.j.j E/아이디 전송: u_id=1 // 1은 db에 있는 값
    03-23 18:00:33.775 20523-20568/com.example.j.j D/NetworkSecurityConfig: No Network Security Config specified, using platform default
    03-23 18:00:33.779 20523-20568/com.example.j.j I/System.out: (HTTPLog)-Static: isSBSettingEnabled false
    03-23 18:00:33.779 20523-20568/com.example.j.j I/System.out: (HTTPLog)-Static: isSBSettingEnabled false
    03-23 18:00:33.800 20523-20568/com.example.j.j E/RECV IDDATA: 1111
    03-23 18:00:33.801 20523-20523/com.example.j.j D/ViewRootImpl@c8475e0[MainActivity]: ViewPostImeInputStage processKey 1
    03-23 18:00:33.811 20523-20523/com.example.j.j E/s: 1111
    03-23 18:00:33.811 20523-20523/com.example.j.j E/test: 1111
    03-23 18:00:34.120 20523-20523/com.example.j.j D/ViewRootImpl@c8475e0[MainActivity]: ViewPostImeInputStage processKey 0
    03-23 18:00:34.122 20523-20523/com.example.j.j D/중복체크: 12
    03-23 18:00:34.122 20523-20573/com.example.j.j E/아이디 전송: u_id=12
    03-23 18:00:34.123 20523-20573/com.example.j.j I/System.out: (HTTPLog)-Static: isSBSettingEnabled false
    03-23 18:00:34.124 20523-20573/com.example.j.j I/System.out: (HTTPLog)-Static: isSBSettingEnabled false
    03-23 18:00:34.126 20523-20523/com.example.j.j D/ViewRootImpl@c8475e0[MainActivity]: ViewPostImeInputStage processKey 1
    03-23 18:00:34.167 20523-20573/com.example.j.j E/RECV IDDATA: 0000
    03-23 18:00:34.168 20523-20523/com.example.j.j E/s: 0000
    03-23 18:00:34.168 20523-20523/com.example.j.j E/test: 1111
    03-23 18:00:34.426 20523-20523/com.example.j.j D/ViewRootImpl@c8475e0[MainActivity]: ViewPostImeInputStage processKey 0
    03-23 18:00:34.427 20523-20523/com.example.j.j D/중복체크: 123
    03-23 18:00:34.428 20523-20570/com.example.j.j E/아이디 전송: u_id=123
    03-23 18:00:34.429 20523-20570/com.example.j.j I/System.out: (HTTPLog)-Static: isSBSettingEnabled false
    03-23 18:00:34.429 20523-20570/com.example.j.j I/System.out: (HTTPLog)-Static: isSBSettingEnabled false
    03-23 18:00:34.429 20523-20523/com.example.j.j D/ViewRootImpl@c8475e0[MainActivity]: ViewPostImeInputStage processKey 1
    03-23 18:00:34.453 20523-20570/com.example.j.j E/RECV IDDATA: 0000
    03-23 18:00:34.453 20523-20523/com.example.j.j E/s: 0000
    03-23 18:00:34.453 20523-20523/com.example.j.j E/test: 1111

    ========================================================
    ================check.php=================================
    <?php
    header('content-type: text/html; charset=utf-8');
    // 데이터베이스 접속 문자열. (db위치, 유 이름, 비번호)
    $connect=mysql_connect( "localhost", "root", "";) or
    die( "SQL server에 연결할 수 없습니다.";);

    mysql_query("SET NAMES UTF8";);
    // 데이터베이스 선택
    mysql_select_db("app_db",$connect);

    // 세션 시작
    session_start();

    $id = $_POST['u_id'];

    $sql = "select * from user where id = '".$id."'";

    $result = mysql_query($sql);

    $count = mysql_num_rows($result);
    $a = "0000";
    $b = "1111";

    if($count == 0){
    echo "0000";
    }else{
    echo "1111";
    }

    mysql_close($connect);

    ?>
    =======================================================

  • bag_go
    2018.12.01 14:08

    안녕하세요
    혹시 파일좀 받을수있을 까요??
    shihis123@naver.com
    여기로 보내주시면 감사하겠습니다!

  • 2019.04.18 01:57

    비밀댓글입니다

    • 2019.04.22 15:19 신고

      안녕하세요~ 에러로그를 메일로 보내줘보시겠어요~ 전체다 긁어서요~

  • tp03
    2019.04.25 15:51

    메일로 보냈습니다!!한번 봐주시면 감사하겠습니다ㅜㅜ

  • 2019.05.17 12:54

    비밀댓글입니다

    • 2019.05.17 13:00 신고

      앱에서의 인터넷 접근이 막혀있을수도있습니다. 보통 앱을 설치할때 인터넷 접근 허가, 위치허가 등등이 되는데 코드상에 internet permission으로 허가하도록 할 수 있습니다. AVD에서 된다면 폰에서도 되야하는데 혹시 폰에서 따로 설치한 앱에 대한 권한이 막혀있는 것일수도 있구요.
      아니면 폰이 LTE가 아니라 와이파이일경우 해당 와이파이에서 호스팅하는 주소로 접근이 안될수도 있구요. 주소를 처본것이 혹시 폰 브라우져에서 처보신 건가요?

    • 으어
      2019.05.17 13:16

      매니패스트 상에 <uses-permission android:name="android.permission.INTERNET" />으로 인터넷 권한을 허용해 주었고..
      LTE랑 와이파이 상태로 둘다 체크해 보았는데
      등록중 에러가 발생했습니다! errcode : 만 뜹니다..
      주소를 쳐본것은 네이버 앱으로 쳐봤습니다..!

    • 으어
      2019.05.17 13:20

      어플리케이션 정보에 가보면 권한에 대해선 요청된 권한 없음 이라고 나오는데 이부분이 문제인걸까요..?

    • 2019.05.17 13:20 신고

      흠 지금 생각하기로는 AVD에 앱버전과 스맛폰에 깔린 앱버전이 다를수도 있을것같은데 스맛폰과 AVD에서 둘다 클린삭제하고 프로젝트 다시 저장후 빌드하고 다시 해보시겠어요~?

    • 으어
      2019.05.17 13:44

      클린삭제라 하심은...? 클린 프로젝트 같은것을 의미하나요...?
      그렇다면 스마트폰 앱의 경우는 그냥 삭제하면 되나요??

    • 2019.05.17 13:48 신고

      네~ 그냥 앱삭제하시면 됩니다. 앱삭제하고 데이터나 캐쉬가 쪼금 남는데 아마 캐쉬 쓰는게 없으니까 안남을꺼에요

    • 2019.05.17 13:54 신고

      그래도 안되고, 만약 AVD는 마쉬멜로우 전버전이고 테스트기기는 마쉬멜로우 이상버전이시면 마쉬멜로우 이상 버전에서 권한 설정하기 구글링해보셔서 적용해보세요~

    • 으어
      2019.05.17 14:04

      AVD상에서 삭제하려니 자꾸 중지되었다고 해서 삭제는 못했습니다.
      기존 쓰던 API 레벨은 26이고
      현재 스마트폰의 API 레벨은 28입니다.
      그리고 AVD에 API 28을 적용하고 실행하니 똑같은 문제가 나오네요...ㅇ...ㅠㅠ

    • 2019.05.17 14:09 신고

      28 버전부터 좀 많이 바껴서 몇가지 추가해야 됩니다.
      https://stackoverflow.com/questions/53995724/async-task-not-supporting-in-android-9-0-pie 일단 여기보시고 적용해보시고 android api 28 async 로 검색해보시면 됩니다~ api 28 인터넷연결도 해보시면 나오구요~ 인터넷연결 퍼미션 아니면 asynctask사용시 문제인거 같아요~

    • 으어
      2019.05.17 14:31

      일단 조금더 조사해봐야할것 같습니다..
      해결은 못했지만 실마리는 어느정도 잡은것 같습니다 답변 감사드립니다!

    • 으어
      2019.05.17 14:33

      오..! 주신 링크에서 적으라는 권한을 매니패스트에 추가했더니 정상작동합니다!!
      감사합니다 ㅠㅠㅠㅠ

  • 2019.05.19 13:45

    비밀댓글입니다

Designed by JB FACTORY