Golang中API和基础HTTP认证的实现问题求助
Golang中API和基础HTTP认证的实现问题求助 我正在构建一个API,最终将从客户端接收JSON数据并保存到文件中,但我卡在了基本认证的实现上。中间件给我带来了问题。我反复阅读了能找到的相关资料,但似乎遗漏了什么。有人能看一下这段代码并解释我做错了什么吗?
package main
import (
"log"
"net/http"
"fmt"
)
func apitest(w http.ResponseWriter, r *http.Request) http.Handler{
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
})
}
func basicAuth(h http.Handler) http.Handler{
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, pass, _ := r.BasicAuth()
var myUser string = "kevin"
var myPass string = "kevin"
if myUser != user || myPass != pass {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, "Unauthorized.", http.StatusUnauthorized)
return
}
h.ServeHTTP(w, r)
})
}
func main() {
http.HandleFunc("/test", basicAuth(apitest))
err := http.ListenAndServeTLS(":443", "server.crt", "server.key", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
更多关于Golang中API和基础HTTP认证的实现问题求助的实战教程也可以访问 https://www.itying.com/category-94-b0.html
嗨拉斯,
感谢您的帮助。我会仔细研究一下!
谢谢,
凯文
更多关于Golang中API和基础HTTP认证的实现问题求助的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
约翰,
谢谢,我本想确保更好地理解这些函数,但我觉得还需要再多研究一下。
谢谢,
凯文
// 代码示例保留原样
你好Johan,
这个API是用于与另一家公司对接,他们要求使用HTTP基本认证。
谢谢,
Kevin
Johan,
感谢您的纠正。我想确认自己是否理解正确。将basicauth变量改为处理程序,是因为它在main函数中作为HandleFunc的一部分被调用吗?然后apitest函数是因为它被传递给匿名函数,对吗?
谢谢,
Kevin
我不太确定是否理解您的意思。您可以使用 http.Handler 或 http.HandleFunc,但 http.Handler 是一个必须实现 ServeHTTP(w, r) 方法的接口。所以通常使用 HandlerFunc 会更简单。我认为当您需要一个带有状态的对象时,Handler 会更方便(双关语)。我不确定我是否回答了您的问题 😕
func main() {
fmt.Println("hello world")
}
你好
你的代码有点难以阅读。请按照以下方式发布代码:
在本论坛发布代码时,最好采用以下方式之一:
作为 Go Playground 的链接 https://play.golang.org 输入或粘贴你的代码。点击 Format 按钮进行格式化。然后点击 Share,你会看到一个链接,可以复制并粘贴到本论坛。
或者你可以在论坛中写入或粘贴代码,用两行
go 和(三个反引号)包裹。像这样:a := b + c Fmt.Println("Answer is", a)这样代码就会漂亮地显示出来…
我认为如果你想要实现 REST API,需要使用其他方式进行身份验证。HTTP 简单认证不太适合这个场景。
我最近在一个项目中使用HTTP基本认证来控制应用页面的访问权限。
我发现它能满足我的需求,但需要注意以下三点:
- 请记住HTTP基本认证并不安全!请将服务部署在HTTP反向代理后面,这样TLS可以保护认证信息(这些信息通过HTTP头发送)
- 在现代浏览器中很难退出HTTP基本认证会话。浏览器会积极缓存HTTP头,需要使用JavaScript技巧来清除状态。别问我更多细节,我自己也不太懂 😊 但下面有相关讨论:
- 这不支持多个"用户"(不同角色等)。如果需要此功能,请使用更高级的账户机制。
参考:https://stackoverflow.com/a/14329930/1012159
我的完整示例:
https://play.golang.org/p/2rRaQY3Iu3f
注意在点击"退出"后应显式刷新页面(按CTRL-R)来验证会话确实已取消认证。
再次遇到问题
由于apitest没有实现httpServe方法,我遇到了错误。我稍微重写了代码,改用handleFuncs。并且我改用了http协议(只是因为我比较懒,不想生成证书和密钥)。不过现在它可以正常工作了:
package main
import (
"io"
"log"
"net/http"
)
func apitest(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello world")
}
func basicAuth(hf http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, pass, _ := r.BasicAuth()
var myUser string = "kevin"
var myPass string = "kevin"
if myUser != user || myPass != pass {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, "Unauthorized.", http.StatusUnauthorized)
return
}
hf(w, r)
}
}
func main() {
http.HandleFunc("/test", basicAuth(apitest))
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
还有两点说明:
var myUser string = "kevin"
var myPass string = "kevin"
不应该指定字符串类型。Go vet会对此发出警告。还有这一行:
http.Error(w, "Unauthorized.", http.StatusUnauthorized)
能否将"Unauthorized."替换为http.StatusText的调用,像这样:
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
你的代码中存在几个关键问题,我来逐一解释并提供修正后的代码。
主要问题分析:
- 函数签名错误:
apitest函数被声明为返回http.Handler,但实际应该是一个标准的 HTTP 处理函数 - 中间件使用不当:
http.HandleFunc期望的是func(http.ResponseWriter, *http.Request),而不是包装后的 handler
修正后的代码:
package main
import (
"log"
"net/http"
"encoding/json"
"os"
)
// 修正函数签名 - 移除返回类型
func apitest(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var data map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// 保存到文件
file, err := os.Create("data.json")
if err != nil {
http.Error(w, "Failed to create file", http.StatusInternalServerError)
return
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
if err := encoder.Encode(data); err != nil {
http.Error(w, "Failed to write file", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
}
// 基础认证中间件保持不变
func basicAuth(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, pass, _ := r.BasicAuth()
var myUser string = "kevin"
var myPass string = "kevin"
if myUser != user || myPass != pass {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, "Unauthorized.", http.StatusUnauthorized)
return
}
h.ServeHTTP(w, r)
})
}
func main() {
// 正确使用中间件 - 将 apitest 包装为 http.Handler
protectedHandler := basicAuth(http.HandlerFunc(apitest))
// 使用 Handle 而不是 HandleFunc
http.Handle("/test", protectedHandler)
err := http.ListenAndServeTLS(":443", "server.crt", "server.key", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
关键修正点:
- 修正
apitest函数签名:移除了返回类型,使其成为标准的 HTTP 处理函数 - 正确包装处理函数:使用
http.HandlerFunc(apitest)将函数转换为http.Handler - 使用
http.Handle而不是http.HandleFunc:因为中间件返回的是http.Handler接口 - 添加了 JSON 数据处理逻辑:演示如何接收和保存 JSON 数据
测试示例:
使用 curl 测试认证:
# 错误认证
curl -i https://localhost/test
# 正确认证
curl -u kevin:kevin -H "Content-Type: application/json" -d '{"message":"test"}' https://localhost/test
这个修正版本应该能正确处理基础认证并保存 JSON 数据到文件。


