前情:
大家好上次發問安卓連接資料庫的問題,感謝各位大大們的解答與幫助
最後我決定使用php來連接MySQL與Android,這次要試著做登入系統(沒有註冊是因為APP上並沒有需要註冊的功能),由於我是第一次接觸php,撰寫Android的時間也不長,以下的問題是我先模仿Github上的開放程式碼試著做時碰到的
正題:
我參考的範例原本有自己的資料庫,但因為我有資料庫就換成自己的嘗試做做看,我在打完所有程式碼與看完他的實作範例影片後,試著run了一次,卻一直顯示如下圖的照片,我試過不輸入數值、打錯帳密、輸入正確的帳密都是一樣的結果,我設置登入正確會轉跳其他頁面,但它就只有一直卡住,因為我不太熟悉php、Android,深怕改了哪裡導致邏輯錯誤,還希望有大大能協助我解決問題!
範例的登入畫面:
範例的結果(有轉跳畫面並下方顯示登入成功):
我嘗試的結果(畫面上是正確的帳密,登入後卻沒有轉跳也顯示錯誤字樣還跑亂碼):
以下是我的程式碼&資料庫(只使用到users這個資料表的資料):
1.users資料表的結構
2.users資料表的資料內容
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 照片比對:
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>
傳送的是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語法。要不然會取不到正常編碼的中文值。
這個畫面是 PHP 執行錯誤
對於開發的流程我有個建議
既然今天用 PHP 寫 API
那麼可以先用一些工具測試這些 API
(例如 Postman 或 curl)
測試正確後再讓 Android 程式來接這些 API
海綿寶寶大大
我後來發現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;
}
}
?>
但是結果還是會跑帳號密碼錯誤(確認輸入的帳密是對的),有可能是因為編碼問題導致輸入的帳密讓系統判斷錯誤嗎?
淺水員大大,好的非常謝謝你! 我會去下載來偵錯
不好意思打擾了
恕我眼拙 以及未使用過
if ($DBusername == $users_staffId && password_verify($users_Password, $DBpassword)) {
$login = true;
}
當中的password_verify
是自帶的函式還是您額外寫在哪裡了?
DataBase.php Line 50
if ($DBusername == $users_staffId && password_verify($users_Password, $DBpassword)) {
這邊你用 password_verify
函式,這是把 $users_Password
取 hash 後,跟 $DBpassword
比較,而不是純粹比較兩者是否相等。
$users_Password === $DBpassword
一般伺服器儲存密碼時,並不會直接儲存明文,而是取過 hash 之後才存入資料庫。
登入時,會把使用者輸入的密碼,取完 hash 後才跟資料庫儲存的資料比對。
這樣的作法可以保護使用者的原始密碼。
謝謝淺水員大大有很詳細的解釋,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>
我看的範例當中註冊時就有使用password_hash去儲存密碼
這就是我納悶的地方
你資料庫密碼看起來顯示的就明文
但你去利用password_verify功能取hash後比對
建議放您參考的範例,方便前輩們進行比對
我覺得你還是在 GitHub 找另一個範例比較好
找一個「在一個字都不改的前提下,安裝後就能正確運作」的範例
會比較適合你
archer9080和海綿寶寶大,謝謝你們,我會更加注意!