iT邦幫忙

0

Android+php+MySQL 練習實作登入

2021-01-13 01:53:2710052 瀏覽

前情:
大家好上次發問安卓連接資料庫的問題,感謝各位大大們的解答與幫助/images/emoticon/emoticon07.gif
最後我決定使用php來連接MySQL與Android,這次要試著做登入系統(沒有註冊是因為APP上並沒有需要註冊的功能),由於我是第一次接觸php,撰寫Android的時間也不長,以下的問題是我先模仿Github上的開放程式碼試著做時碰到的

正題:
我參考的範例原本有自己的資料庫,但因為我有資料庫就換成自己的嘗試做做看,我在打完所有程式碼與看完他的實作範例影片後,試著run了一次,卻一直顯示如下圖的照片,我試過不輸入數值、打錯帳密、輸入正確的帳密都是一樣的結果,我設置登入正確會轉跳其他頁面,但它就只有一直卡住,因為我不太熟悉php、Android,深怕改了哪裡導致邏輯錯誤,還希望有大大能協助我解決問題!

範例的登入畫面:
https://ithelp.ithome.com.tw/upload/images/20210113/20127840QC53ajwxSM.png

範例的結果(有轉跳畫面並下方顯示登入成功):
https://ithelp.ithome.com.tw/upload/images/20210113/20127840jaqDkj0BAr.png

我嘗試的結果(畫面上是正確的帳密,登入後卻沒有轉跳也顯示錯誤字樣還跑亂碼):
https://ithelp.ithome.com.tw/upload/images/20210113/20127840f5MxPpraeD.png

以下是我的程式碼&資料庫(只使用到users這個資料表的資料):
1.users資料表的結構
https://ithelp.ithome.com.tw/upload/images/20210113/20127840eVrl3seCN3.png
2.users資料表的資料內容
https://ithelp.ithome.com.tw/upload/images/20210113/20127840CRSkzpxY54.png

php程式碼如下:
1.config.php(定義資料庫)

<?php
class config{
	
	public $host;
	public $dbname;
	public $dbpassword;
	public $database;
	
	public function __construct()
    {

        $this->host = 'localhost';
        $this->dbname = 'root';
        $this->dbpassword = '1234';
        $this->database = 'dbsa';
    }
}
?>

2.DataBase.php(建立連線與function)

<?php
require "config.php";

class DataBase
{
    public $connect;
    public $data;
    private $sql;
    protected $host;
    protected $dbname;
    protected $dbpassword;
    protected $database;

    public function __construct()
    {
        $this->connect = null;
        $this->data = null;
        $this->sql = null;
        $dbc = new config();
        $this->host = $dbc->host;
        $this->dbname = $dbc->dbname;
        $this->dbpassword = $dbc->dbpassword;
        $this->database = $dbc->database;
    }

    function dbConnect()
    {
        $this->connect = mysqli_connect($this->host, $this->dbname, $this->dbpassword, $this->database);
        return $this->connect;
    }

    function prepareData($data)
    {
        return mysqli_real_escape_string($this->connect, stripslashes(htmlspecialchars($data)));
    }

    function logIn($table, $users_staffid, $users_Password)
    {
        $users_staffid = $this->prepareData($users_staffid);
        $users_Password = $this->prepareData($users_Password);
        $this->sql = "select * from " . $table . " where users = '" . $users_staffid . "'";
        $result = mysqli_query($this->connect, $this->sql);
        $row = mysqli_fetch_assoc($result);
        if (mysqli_num_rows($result) != 0) {
            $dbusername = $row['users_staffid'];
            $dbpassword = $row['users_Passwords'];
            if ($dbusername == $users_staffid && password_verify($users_Password, $dbpassword)) {
                $login = true;
            } else $login = false;
        } else $login = false;

        return $login;
    }
}

?>

3.login.php(判斷登入)

<?php
require "DataBase.php";

$db = new DataBase();

if (isset($_POST['users_staffid']) && isset($_POST['users_Password'])) {
    if ($db->dbConnect()) {
        if ($db->logIn("users", $_POST['users_staffid'], $_POST['users_Password'])) {
            echo "Login Success";
        } else echo "Username or Password wrong";
    } else echo "Error: Database connection";
} else echo "All fields are required";



?>

Android程式碼如下:
1.layout_login.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".login">

    <TextView
        android:id="@+id/logintitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="LOGIN"
        android:textSize="30sp"
        android:textStyle="bold"
        android:gravity="center"
        android:layout_marginTop="30sp"/>

    <LinearLayout
        android:id="@+id/LL1"
        android:layout_below="@+id/logintitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="80dp"
        android:layout_marginRight="30dp"
        android:layout_marginLeft="30dp">

        <TextView
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/username"
            android:textSize="25dp"/>

        <EditText
            android:id="@+id/users_staffId"
            android:layout_toRightOf="@+id/tv1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    </LinearLayout>

    <LinearLayout
        android:id="@+id/LL2"
        android:layout_below="@+id/LL1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="30sp">
        <TextView
            android:id="@+id/tv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/password"
            android:textSize="25dp"/>

        <EditText
            android:id="@+id/users_Password"
            android:layout_toRightOf="@+id/tv1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    </LinearLayout>

    <ProgressBar
        android:layout_below="@+id/LL2"
        android:id="@+id/progress"
        android:visibility="gone"
        android:layout_centerHorizontal="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/btn_login"
        android:layout_below="@+id/progress"
        android:layout_marginTop="50sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:text="登入"
        android:textSize="25sp"/>


</RelativeLayout>

layout_login.xml 照片比對:
https://ithelp.ithome.com.tw/upload/images/20210113/20127840wEcgTWfTux.png

login.java完整程式碼

package com.example.satest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.vishnusivadas.advanced_httpurlconnection.PutData;

public class login extends AppCompatActivity {

    EditText et1,et2;
    Button btn_login;
    ProgressBar progressBar;

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

        et1 = findViewById(R.id.users_staffId);
        et2 = findViewById(R.id.users_Password);
        btn_login = findViewById(R.id.btn_login);
        progressBar = findViewById(R.id.progress);

        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final String users_staffId,users_Password;

                users_staffId =String.valueOf(et1.getText());
                users_Password = String.valueOf(et2.getText());

                if(!users_staffId.equals("") && !users_Password.equals("")){
                    progressBar.setVisibility(View.VISIBLE);
                    Handler handler = new Handler();
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            String[] field = new String[2];
                            field[0] = "users_staffId";
                            field[1] = "users_Password";
                            String[] data = new String[2];
                            data[0] = users_staffId;
                            data[1] = users_Password;
                            PutData putData = new PutData("http://192.168.0.5/safinal/login.php","POST",field,data);
                            if(putData.startPut()){
                                if(putData.onComplete()){
                                    progressBar.setVisibility(View.VISIBLE);
                                   String result = putData.getResult();
                                   if(result.equals("Login Success")){
                                       Toast.makeText(getApplicationContext(),result,Toast.LENGTH_SHORT).show();
                                       Intent intent = new Intent(getApplicationContext(),MainActivity.class);
                                       startActivity(intent);
                                       finish();
                                   }
                                   else{
                                       Toast.makeText(getApplicationContext(),result,Toast.LENGTH_SHORT).show();
                                   }
                                }
                            }
                        }
                    });
                }
                else{
                    Toast.makeText(getApplicationContext(),"All fields required",Toast.LENGTH_SHORT).show();
                }
            }
        });

    }
}

AndroidManifest.xml程式碼(我有寫入uses-permission):

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.satest">

    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"
        android:theme="@style/Theme.Satest">
        <activity android:name=".login"
            android:theme="@style/Theme.Design.Light.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MainActivity" android:theme="@style/Theme.Design.Light.NoActionBar"/>
    </application>

</manifest>
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 個回答

3
海綿寶寶
iT邦大神 1 級 ‧ 2021-01-13 08:47:15

傳送的是users_staffId

field[0] = "users_staffId";

接收的是users_staffid

if (isset($_POST['users_staffid']) && isset($_POST['users_Password'])) 

因為「POST 的參數名稱大小寫有差別」
所以接收不到 users_staffid 這個值(isset==false)
而跳到 else 的「顯示 All fields are required.」

修改方法
把所有的 users_staffId 全部改成 users_staffid
再試試看

看更多先前的回應...收起先前的回應...

海綿太強了,這點小差異居然看出來了。

那我就補一下。

記得要下「set names utf8」的sql語法。要不然會取不到正常編碼的中文值。

iT邦新手 5 級 ‧ 2021-01-13 13:05:46 檢舉

首先先謝謝海綿寶寶大大與浩瀚星空大大的幫忙,我把users_staffid
都改成users_staffId後嘗試再run一次卻出現如下圖問題,好像是Type Error,但我不了解是哪裡的Type出現了錯誤,能麻煩大大幫助嗎

出錯圖片https://ithelp.ithome.com.tw/upload/images/20210113/20127840VdKCAVGHtt.png

另外要下「set names utf8」的sql語法是下在這個位子嗎,想做確認
,這是DataBase.php裡的function dbConnect()內:
https://ithelp.ithome.com.tw/upload/images/20210113/20127840EhBoMk1MQg.png

淺水員 iT邦大師 6 級 ‧ 2021-01-13 14:33:17 檢舉

這個畫面是 PHP 執行錯誤
對於開發的流程我有個建議
既然今天用 PHP 寫 API
那麼可以先用一些工具測試這些 API
(例如 Postman 或 curl)
測試正確後再讓 Android 程式來接這些 API

iT邦新手 5 級 ‧ 2021-01-13 14:47:04 檢舉

海綿寶寶大大
我後來發現DataBase.php的檔案裡面有變數與語法寫錯更改後如下

<?php
require "config.php";

class DataBase
{
    public $connect;
    public $data;
    private $sql;
    protected $host;
    protected $dbname;
    protected $dbpassword;
    protected $database;

    public function __construct()
    {
        $this->connect = null;
        $this->data = null;
        $this->sql = null;
        $dbc = new config();
        $this->host = $dbc->host;
        $this->dbname = $dbc->dbname;
        $this->dbpassword = $dbc->dbpassword;
        $this->database = $dbc->database;
    }

    function dbConnect()
    {
        $this->connect = mysqli_connect($this->host, $this->dbname, $this->dbpassword, $this->database);
		return $this->connect;
		mysqli_query($this->connect,"SET NAMES 'UTF8' ");
    }

    function prepareData($data)
    {
        return mysqli_real_escape_string($this->connect, stripslashes(htmlspecialchars($data)));
    }

    function logIn($table, $users_staffId, $users_Password)
    {
        $users_staffId = $this->prepareData($users_staffId);
        $users_Password = $this->prepareData($users_Password);
		
        $this->sql = "SELECT * FROM " . $table . " WHERE users_staffId = '" . $users_staffId . "'";
        $result = mysqli_query($this->connect, $this->sql);
        $row = mysqli_fetch_assoc($result);
		
        if (mysqli_num_rows($result) != 0) {
            $DBusername = $row['users_staffId'];
            $DBpassword = $row['users_Password'];
            if ($DBusername == $users_staffId && password_verify($users_Password, $DBpassword)) {
                $login = true;
            } else $login = false;
        } else $login = false;

        return $login;
    }
}
?>

但是結果還是會跑帳號密碼錯誤(確認輸入的帳密是對的),有可能是因為編碼問題導致輸入的帳密讓系統判斷錯誤嗎?
https://ithelp.ithome.com.tw/upload/images/20210113/20127840GdMZnNSy5L.png

iT邦新手 5 級 ‧ 2021-01-13 14:50:32 檢舉

淺水員大大,好的非常謝謝你! 我會去下載來偵錯

archer9080 iT邦研究生 4 級 ‧ 2021-01-13 15:46:27 檢舉

不好意思打擾了
恕我眼拙 以及未使用過

if ($DBusername == $users_staffId && password_verify($users_Password, $DBpassword)) {
    $login = true;
} 

當中的password_verify是自帶的函式還是您額外寫在哪裡了?

淺水員 iT邦大師 6 級 ‧ 2021-01-13 16:05:52 檢舉

DataBase.php Line 50

if ($DBusername == $users_staffId && password_verify($users_Password, $DBpassword)) {

這邊你用 password_verify 函式,這是把 $users_Password 取 hash 後,跟 $DBpassword 比較,而不是純粹比較兩者是否相等。

修正方式

  1. (不建議)直接用 $users_Password === $DBpassword
  2. 依據password_verify說明,密碼欄位儲存時,先用 password_hash函式處理過再存到資料庫。

說明

一般伺服器儲存密碼時,並不會直接儲存明文,而是取過 hash 之後才存入資料庫。
登入時,會把使用者輸入的密碼,取完 hash 後才跟資料庫儲存的資料比對。
這樣的作法可以保護使用者的原始密碼。

iT邦新手 5 級 ‧ 2021-01-13 20:12:28 檢舉

謝謝淺水員大大有很詳細的解釋,archer9080可以參考大大的說明!!

不過淺水員大大我是使用第一種方式,因為我並沒有設計會員註冊功能,而是使用原本就存在資料庫的檔案,利用password_hash與password_verify是不是要從註冊輸入資料時就先利用亂碼下去做保護,login去判斷時才能使用password_verify去做驗證呢?(我看的範例當中註冊時就有使用password_hash去儲存密碼)

還有一個問題是,我現在成功登入了,但預設是會轉跳到另一個介面,卻一直卡在登入畫面,請問會是因為什麼原因而卡到呢?

login.java(有設置Intent做跳轉):

btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final String users_staffId,users_Password;

                users_staffId  = String.valueOf(et1.getText());
                users_Password = String.valueOf(et2.getText());

                if(!users_staffId.equals("") && !users_Password.equals("")){
                    progressBar.setVisibility(View.VISIBLE);
                    Handler handler = new Handler();
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            String[] field = new String[2];
                            field[0] = "users_staffId";
                            field[1] = "users_Password";
                            String[] data = new String[2];
                            data[0] = users_staffId;
                            data[1] = users_Password;
                            PutData putData = new PutData("http://192.168.0.5/safinal/login.php","POST",field,data);
                            if(putData.startPut()){
                                if(putData.onComplete())
                                {
                                    progressBar.setVisibility(View.VISIBLE);
                                   String result = putData.getResult();
                                   if(result.equals("Login Success!"))
                                   {
                                       Toast.makeText(getApplicationContext(),result,Toast.LENGTH_SHORT).show();
                                       Intent intent = new Intent(getApplicationContext(),MainActivity.class);
                                       startActivity(intent);
                                       finish();
                                   }
                                   else
                                   {
                                       Toast.makeText(getApplicationContext(),result,Toast.LENGTH_SHORT).show();
                                   }
                                }
                            }
                        }
                    });
                }
                else{
                    Toast.makeText(getApplicationContext(),"All fields required",Toast.LENGTH_SHORT).show();
                }
            }
        });

預設起始畫面為.login:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.satest">

    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"
        android:theme="@style/Theme.Satest">
        <activity android:name=".login">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".MainActivity"/>

    </application>

</manifest>
archer9080 iT邦研究生 4 級 ‧ 2021-01-14 10:08:49 檢舉

我看的範例當中註冊時就有使用password_hash去儲存密碼

這就是我納悶的地方

你資料庫密碼看起來顯示的就明文

但你去利用password_verify功能取hash後比對/images/emoticon/emoticon10.gif

建議放您參考的範例,方便前輩們進行比對

我覺得你還是在 GitHub 找另一個範例比較好
/images/emoticon/emoticon13.gif

找一個「在一個字都不改的前提下,安裝後就能正確運作」的範例
會比較適合你

iT邦新手 5 級 ‧ 2021-01-14 13:06:29 檢舉

archer9080海綿寶寶大,謝謝你們,我會更加注意!

我要發表回答

立即登入回答