Golang网站开发与重定向问题求助

Golang网站开发与重定向问题求助 大家好, 我刚刚构建了这个Go语言网站,数据库连接正常,会话记录正常,登录功能也正常,但重定向到预订页面不工作。你们能帮我看看吗?我不知道哪里出了问题。它只是重新加载登录页面而没有重定向,进一步的调试似乎也没有给我任何线索。

GitHub: GitHub - KSCHNAPPIEN/golang-web

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"

	_ "github.com/go-sql-driver/mysql"
)

type DatabaseConfig struct {
	Username string `json:"user"`
	Password string `json:"password"`
	Host     string `json:"host"`
	Port     int    `json:"port"`
	Database string `json:"dbname"`
}

var dbc string

func logError(err error) {
	if err != nil {
		log.Println(err)
	}
}

func main() {
	logFile, err := os.OpenFile("errors.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		log.Println(err)
	}
	defer logFile.Close()

	log.SetOutput(logFile)

	mux := http.NewServeMux()
	// route naar url
	mux.HandleFunc("/", RootHandler)

	mux.HandleFunc("/Locatie", LocatieHandler)

	mux.HandleFunc("/Login", LoginHandler)

	mux.HandleFunc("/Booking", BookingHandler)

	// read json file
	configBytes, err := ioutil.ReadFile("Db.json")
	if err != nil {
		logError(err)
	}

	// setting up new struct
	var config DatabaseConfig
	if err := json.Unmarshal(configBytes, &config); err != nil {
		log.Println(err)
	}

	// make connection database
	dbc = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", config.Username, config.Password, config.Host, config.Port, config.Database)
	db, err := sql.Open("mysql", dbc)
	if err != nil {
		logError(err)
	}
	defer db.Close()

	if err := db.Ping(); err != nil {
		log.Println(err)
	}

	http.ListenAndServe(":8080", mux)
}
func checkLogin(r *http.Request) bool {
	// get usename and password
	username := r.FormValue("username")
	password := r.FormValue("password")

	// Connect to the database
	db, err := sql.Open("mysql", dbc)
	if err != nil {
		log.Println(err)
		return false
	}
	defer db.Close()

	// Execute a SELECT query to check if the username and password are correct
	var userID int
	err = db.QueryRow("SELECT id FROM users WHERE username=? AND password=?", username, password).Scan(&userID)
	if err != nil {
		log.Println(err)
		return false
	}

	// If the username and password are correct, insert a new session into the database
	_, err = db.Exec("INSERT INTO sessions (user_id, start_time, end_time) VALUES (?, NOW(), NOW())", userID)
	if err != nil {
		log.Println(err)
		return false
	}

	return true
}
func RootHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, ` 		<h1>Fonteyn Vakantieparken</h1> 		<p>Vakantie parken voor een onvergeetelijke vakantie.</p> 		<ul> 			<li><a href="/Locatie">Locatie</a></li> 			<li><a href="/Login">Login</a></li> 		</ul> 	`)
}

func LocatieHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, ` 		<h1>Locatie</h1> 		<p>Wij zijn beschikbaar in meerder landen.</p> 		<ul> 			<li><a href="/">Home</a></li> 			<li><a href="/Login">Login</a></li> 		</ul> 	`)
}

func BookingHandler(w http.ResponseWriter, r *http.Request) {
	// Check if user is logged in
	if !checkLogin(r) {
		http.Redirect(w, r, "/Login", http.StatusSeeOther)
		return
	}
}
func LoginHandler(w http.ResponseWriter, r *http.Request) {
	// display login
	fmt.Fprintln(w, `        <h1>Fonteyn Vakantieparken</h1>         <h2>Inloggen</h2>         <form action="/Login" method="POST">             <label for="username">Gebruikersnaam:</label><br>             <input type="text" id="username" name="username"><br>             <label for="password">Wachtwoord:</label><br>             <input type="password" id="password" name="password"><br><br>             <input type="submit" value="Inloggen">         </form>    `)
	if r.Method == "POST" {
		if checkLogin(r) {
			http.Redirect(w, r, "/Booking", http.StatusSeeOther)
			return
		} else {
			fmt.Fprintln(w, "Invalid username or password.")
		}
	}
}

更多关于Golang网站开发与重定向问题求助的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

如何以安全的方式使用cookie来实现? 有示例吗?

更多关于Golang网站开发与重定向问题求助的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


除了使用Cookie之外,还有其他方法来追踪用户吗?我以为我添加的会话就能完成这个任务。

你的重定向确实有效。POST /Login 重定向到了 /Booking。 但是 /Booking 又重定向回了 /Login,因为它再次检查登录表单数据,而该数据已不存在。

你需要通过某种方式(例如使用 cookie)将会话保存到浏览器。

此外,你的 Login() 函数应该仅在请求方法是 GET 时才打印登录表单。

我复刻了你的仓库,并添加了一个用于跟踪用户的示例Cookie,地址如下:

GitHub - melato/example-golang-web

我将数据库替换为了硬编码的用户。

我给出了一个如何在Cookie中放置会话ID的示例。会话ID应足够长且随机,使其无法被猜测。它还应具有HttpOnly属性,以便无法通过Javascript访问。你可以生成这样的会话ID,将其保存在会话表中,并放入Cookie中。

关于Cookie的另一点是,在实现表单或其他可能更改只有用户才能更改的内容的页面时,要避免CSRF攻击。实现这一点的方法是在表单或URL中添加一个令牌,该令牌类似于会话ID(但不同)。

我自己也在学习如何做到这一点。以下是我找到的关于如何实现这一点的一些建议,但它是用PHP写的:

如何正确使用PHP添加跨站请求伪造(CSRF)令牌

有多种方法可以实现,但它们都需要与浏览器进行交互。否则,服务器如何知道是哪个用户发出的请求呢? 我最近发现了这篇文章,其中提到了一些机制:

RisingStack Engineering

Web Authentication Methods Explained - RisingStack Engineering

Web Authentication Methods

从HTTP基本认证开始,到签名结束,涵盖了Cookie、令牌和其他Web认证方法。

预计阅读时间:6分钟

如果你能接受其缺点,BasicAuth是一种简单的机制。密码检查甚至可以在应用程序外部完成,例如,通过位于你应用程序前面的nginx或haproxy。然后,应用程序需要做的就是从HTTP头部获取用户名。但是,你必须找到自己管理密码的方法。我将其用于个人单用户项目。

顺便说一下,在数据库中以明文形式存储密码是不好的做法。任何能够访问数据库的人,包括你自己,都将能够访问所有用户的密码。你应该存储哈希后的密码,使用类似 golang.org/x/crypto/bcrypt 这样的工具。

// 示例:使用 bcrypt 哈希密码
import "golang.org/x/crypto/bcrypt"

func HashPassword(password string) (string, error) {
    bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    return string(bytes), err
}

func CheckPasswordHash(password, hash string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
    return err == nil
}

Web认证很复杂,并且容易出现安全问题。我自己目前也在研究各种选项。

看了你的代码,重定向问题主要出现在BookingHandler函数中。问题在于checkLogin函数的设计方式,它总是从请求中读取表单值,但访问/Booking页面时并没有提交表单数据。

以下是修复后的代码:

package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

type DatabaseConfig struct {
    Username string `json:"user"`
    Password string `json:"password"`
    Host     string `json:"host"`
    Port     int    `json:"port"`
    Database string `json:"dbname"`
}

var dbc string
var db *sql.DB

func logError(err error) {
    if err != nil {
        log.Println(err)
    }
}

func main() {
    logFile, err := os.OpenFile("errors.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
    if err != nil {
        log.Println(err)
    }
    defer logFile.Close()

    log.SetOutput(logFile)

    // read json file
    configBytes, err := ioutil.ReadFile("Db.json")
    if err != nil {
        logError(err)
    }

    // setting up new struct
    var config DatabaseConfig
    if err := json.Unmarshal(configBytes, &config); err != nil {
        log.Println(err)
    }

    // make connection database
    dbc = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", config.Username, config.Password, config.Host, config.Port, config.Database)
    db, err = sql.Open("mysql", dbc)
    if err != nil {
        logError(err)
    }
    defer db.Close()

    if err := db.Ping(); err != nil {
        log.Println(err)
    }

    mux := http.NewServeMux()
    mux.HandleFunc("/", RootHandler)
    mux.HandleFunc("/Locatie", LocatieHandler)
    mux.HandleFunc("/Login", LoginHandler)
    mux.HandleFunc("/Booking", BookingHandler)
    mux.HandleFunc("/Logout", LogoutHandler)

    http.ListenAndServe(":8080", mux)
}

func checkLogin(r *http.Request) (bool, int) {
    // get session cookie
    sessionCookie, err := r.Cookie("session_id")
    if err != nil {
        return false, 0
    }

    // check if session exists and is valid
    var userID int
    var endTime time.Time
    err = db.QueryRow("SELECT user_id, end_time FROM sessions WHERE session_id = ?", sessionCookie.Value).Scan(&userID, &endTime)
    if err != nil {
        return false, 0
    }

    // check if session is still valid
    if time.Now().After(endTime) {
        return false, 0
    }

    return true, userID
}

func RootHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, `        <h1>Fonteyn Vakantieparken</h1>         <p>Vakantie parken voor een onvergeetelijke vakantie.</p>         <ul>             <li><a href="/Locatie">Locatie</a></li>             <li><a href="/Login">Login</a></li>         </ul>     `)
}

func LocatieHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, `        <h1>Locatie</h1>         <p>Wij zijn beschikbaar in meerder landen.</p>         <ul>             <li><a href="/">Home</a></li>             <li><a href="/Login">Login</a></li>         </ul>     `)
}

func BookingHandler(w http.ResponseWriter, r *http.Request) {
    // Check if user is logged in using session
    loggedIn, userID := checkLogin(r)
    if !loggedIn {
        http.Redirect(w, r, "/Login", http.StatusSeeOther)
        return
    }

    // Display booking page for logged in user
    fmt.Fprintf(w, `        <h1>Booking Page</h1>         <p>Welcome, User ID: %d</p>         <p>This is the booking page.</p>         <ul>             <li><a href="/">Home</a></li>             <li><a href="/Logout">Logout</a></li>         </ul>     `, userID)
}

func LoginHandler(w http.ResponseWriter, r *http.Request) {
    // If already logged in, redirect to booking page
    loggedIn, _ := checkLogin(r)
    if loggedIn {
        http.Redirect(w, r, "/Booking", http.StatusSeeOther)
        return
    }

    if r.Method == "GET" {
        fmt.Fprintln(w, `        <h1>Fonteyn Vakantieparken</h1>         <h2>Inloggen</h2>         <form action="/Login" method="POST">             <label for="username">Gebruikersnaam:</label><br>             <input type="text" id="username" name="username"><br>             <label for="password">Wachtwoord:</label><br>             <input type="password" id="password" name="password"><br><br>             <input type="submit" value="Inloggen">         </form>    `)
    } else if r.Method == "POST" {
        username := r.FormValue("username")
        password := r.FormValue("password")

        // Execute a SELECT query to check if the username and password are correct
        var userID int
        err := db.QueryRow("SELECT id FROM users WHERE username=? AND password=?", username, password).Scan(&userID)
        if err != nil {
            fmt.Fprintln(w, "Invalid username or password.")
            fmt.Fprintln(w, `<a href="/Login">Try again</a>`)
            return
        }

        // Create session
        var sessionID string
        err = db.QueryRow("INSERT INTO sessions (user_id, start_time, end_time) VALUES (?, NOW(), DATE_ADD(NOW(), INTERVAL 1 HOUR)) RETURNING session_id", userID).Scan(&sessionID)
        if err != nil {
            // If RETURNING is not supported, use alternative approach
            result, err := db.Exec("INSERT INTO sessions (user_id, start_time, end_time) VALUES (?, NOW(), DATE_ADD(NOW(), INTERVAL 1 HOUR))", userID)
            if err != nil {
                fmt.Fprintln(w, "Session creation failed.")
                return
            }
            
            sessionIDInt, _ := result.LastInsertId()
            sessionID = fmt.Sprintf("%d", sessionIDInt)
        }

        // Set session cookie
        http.SetCookie(w, &http.Cookie{
            Name:     "session_id",
            Value:    sessionID,
            Expires:  time.Now().Add(1 * time.Hour),
            HttpOnly: true,
        })

        http.Redirect(w, r, "/Booking", http.StatusSeeOther)
    }
}

func LogoutHandler(w http.ResponseWriter, r *http.Request) {
    // Clear session cookie
    http.SetCookie(w, &http.Cookie{
        Name:     "session_id",
        Value:    "",
        Expires:  time.Now().Add(-1 * time.Hour),
        HttpOnly: true,
    })
    
    http.Redirect(w, r, "/", http.StatusSeeOther)
}

主要修改:

  1. 将数据库连接设为全局变量,避免重复连接
  2. 修改checkLogin函数使用会话cookie验证,而不是每次都检查表单数据
  3. 在登录成功后创建会话记录并设置cookie
  4. 添加会话过期时间(1小时)
  5. 添加登出功能
  6. 修改数据库sessions表结构,需要添加session_id字段和end_time字段

需要更新数据库sessions表:

ALTER TABLE sessions ADD COLUMN session_id INT AUTO_INCREMENT PRIMARY KEY;
ALTER TABLE sessions ADD COLUMN end_time DATETIME;

这样修改后,登录流程会正常工作,重定向到预订页面也会按预期执行。

回到顶部