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
如何以安全的方式使用cookie来实现? 有示例吗?
更多关于Golang网站开发与重定向问题求助的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
除了使用Cookie之外,还有其他方法来追踪用户吗?我以为我添加的会话就能完成这个任务。
你的重定向确实有效。POST /Login 重定向到了 /Booking。 但是 /Booking 又重定向回了 /Login,因为它再次检查登录表单数据,而该数据已不存在。
你需要通过某种方式(例如使用 cookie)将会话保存到浏览器。
此外,你的 Login() 函数应该仅在请求方法是 GET 时才打印登录表单。
我给出了一个如何在Cookie中放置会话ID的示例。会话ID应足够长且随机,使其无法被猜测。它还应具有HttpOnly属性,以便无法通过Javascript访问。你可以生成这样的会话ID,将其保存在会话表中,并放入Cookie中。
关于Cookie的另一点是,在实现表单或其他可能更改只有用户才能更改的内容的页面时,要避免CSRF攻击。实现这一点的方法是在表单或URL中添加一个令牌,该令牌类似于会话ID(但不同)。
我自己也在学习如何做到这一点。以下是我找到的关于如何实现这一点的一些建议,但它是用PHP写的:
有多种方法可以实现,但它们都需要与浏览器进行交互。否则,服务器如何知道是哪个用户发出的请求呢? 我最近发现了这篇文章,其中提到了一些机制:
Web Authentication Methods Explained - RisingStack Engineering

从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)
}
主要修改:
- 将数据库连接设为全局变量,避免重复连接
- 修改
checkLogin函数使用会话cookie验证,而不是每次都检查表单数据 - 在登录成功后创建会话记录并设置cookie
- 添加会话过期时间(1小时)
- 添加登出功能
- 修改数据库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;
这样修改后,登录流程会正常工作,重定向到预订页面也会按预期执行。

