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

10 回复
嗨拉斯,

感谢您的帮助。我会仔细研究一下!

谢谢,

凯文

更多关于Golang中API和基础HTTP认证的实现问题求助的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


约翰,

谢谢,我本想确保更好地理解这些函数,但我觉得还需要再多研究一下。

谢谢,

凯文

// 代码示例保留原样

你好Johan,

这个API是用于与另一家公司对接,他们要求使用HTTP基本认证。

谢谢,

Kevin

Johan,

感谢您的纠正。我想确认自己是否理解正确。将basicauth变量改为处理程序,是因为它在main函数中作为HandleFunc的一部分被调用吗?然后apitest函数是因为它被传递给匿名函数,对吗?

谢谢,

Kevin

我不太确定是否理解您的意思。您可以使用 http.Handlerhttp.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基本认证来控制应用页面的访问权限。

我发现它能满足我的需求,但需要注意以下三点:

  1. 请记住HTTP基本认证并不安全!请将服务部署在HTTP反向代理后面,这样TLS可以保护认证信息(这些信息通过HTTP头发送)
  2. 在现代浏览器中很难退出HTTP基本认证会话。浏览器会积极缓存HTTP头,需要使用JavaScript技巧来清除状态。别问我更多细节,我自己也不太懂 😊 但下面有相关讨论:
  3. 这不支持多个"用户"(不同角色等)。如果需要此功能,请使用更高级的账户机制。

参考:https://stackoverflow.com/a/14329930/1012159

我的完整示例:

https://play.golang.org/p/2rRaQY3Iu3f

注意在点击"退出"后应显式刷新页面(按CTRL-R)来验证会话确实已取消认证。

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)
    }
}

再次遇到问题

由于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)

你的代码中存在几个关键问题,我来逐一解释并提供修正后的代码。

主要问题分析:

  1. 函数签名错误apitest 函数被声明为返回 http.Handler,但实际应该是一个标准的 HTTP 处理函数
  2. 中间件使用不当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)
    }
}

关键修正点:

  1. 修正 apitest 函数签名:移除了返回类型,使其成为标准的 HTTP 处理函数
  2. 正确包装处理函数:使用 http.HandlerFunc(apitest) 将函数转换为 http.Handler
  3. 使用 http.Handle 而不是 http.HandleFunc:因为中间件返回的是 http.Handler 接口
  4. 添加了 JSON 数据处理逻辑:演示如何接收和保存 JSON 数据

测试示例:

使用 curl 测试认证:

# 错误认证
curl -i https://localhost/test

# 正确认证
curl -u kevin:kevin -H "Content-Type: application/json" -d '{"message":"test"}' https://localhost/test

这个修正版本应该能正确处理基础认证并保存 JSON 数据到文件。

回到顶部