Golang REST API草案反馈与讨论

Golang REST API草案反馈与讨论 我的目标是使用Go创建一个尽可能通用且遵循DRY原则的API。为了实现这一点,我做出了一些不太常见的决定:

  1. 使用AJAX调用来避免在更新网页时重新加载整个页面(导致闪烁)。而不是使用Go。
  2. 从API中排除硬编码的查询以减少端点(路由)。这样做的好处是,在更新查询时可以修改和添加查询,而无需重新编译API。
  3. 使用JSON来创建和更新数据,使其更加通用。
  4. 使用sqlx驱动来进一步减少代码并避免重复。

enter image description here

package main

import (
  //"fmt"
  "github.com/jmoiron/sqlx"
  _ "github.com/lib/pq"
  "net/http"
  "os"
  "strings"
)

var db *sqlx.DB

func main() {
  Connect()
  http.HandleFunc("/", handler)
  http.Handle("/favicon.ico", http.NotFoundHandler())
  http.ListenAndServe(":9998", nil)
}

func handler(w http.ResponseWriter, r *http.Request) {

  w.Header().Set("Access-Control-Allow-Origin", "*")
  w.Header().Set("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT,CREATE,DELETE")
  w.Header().Set("Access-Control-Allow-Headers", "*")
  w.Header().Set("Content-Type", "application/json")

  switch r.Method {
  case "DELETE":
    Delete(w, r)
  case "POST":
    Create(w, r)
  case "PUT":
    Update(w, r)
  default: //GET
    Get(w, r)
  }
}

func Getquery(path string) string {
  // get query from lookup db
  var query string
  err := db.QueryRow("SELECT sql_query FROM sqls WHERE sql_id=$1", path).Scan(&query)
  if err != nil {
    path = ""
  }
  return query
}

func getpath(r *http.Request) (string, string, string) {
  path := strings.Split(r.URL.String(), "/")
  switch len(path) {
  case 4:
    return path[1], path[2], path[3]
  case 3:
    return path[1], path[2], ""
  case 2:
    return path[1], "", ""
  default:
    return "", "", ""
  }
}

我的问题是:

  1. 您能看到任何安全问题吗?(CORS除外)
  2. 有什么地方您会采取不同的做法吗?
  3. 对这种通用方法有什么看法?

更详细的描述在这里:Go REST API


更多关于Golang REST API草案反馈与讨论的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang REST API草案反馈与讨论的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


从安全性和架构角度来看,这个通用API设计存在几个关键问题:

1. 安全问题

SQL注入风险

Getquery函数直接从数据库读取SQL语句并执行,这是严重的安全漏洞:

// 当前代码存在SQL注入风险
func Get(w http.ResponseWriter, r *http.Request) {
    table, id, _ := getpath(r)
    query := Getquery(table)
    
    // 直接拼接用户输入的ID到查询中
    finalQuery := strings.Replace(query, "$1", id, -1)
    rows, err := db.Query(finalQuery) // 高危!
}

缺少输入验证

没有对路径参数进行验证:

func getpath(r *http.Request) (string, string, string) {
    path := strings.Split(r.URL.String(), "/")
    // 缺少对path[1], path[2], path[3]的验证
    // 恶意输入如:/users;DROP TABLE users;/1 会导致问题
    return path[1], path[2], path[3]
}

权限控制缺失

所有查询都使用同一数据库连接,没有基于角色的访问控制。

2. 架构改进建议

使用参数化查询

func Get(w http.ResponseWriter, r *http.Request) {
    table, id, _ := getpath(r)
    query := Getquery(table)
    
    // 使用参数化查询防止SQL注入
    rows, err := db.Query(query, id)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer rows.Close()
    
    // 处理结果...
}

添加输入验证

func validateTableName(table string) bool {
    // 只允许字母数字和下划线
    matched, _ := regexp.MatchString("^[a-zA-Z0-9_]+$", table)
    return matched
}

func validateID(id string) bool {
    // 验证ID是否为数字
    _, err := strconv.Atoi(id)
    return err == nil
}

实现查询缓存

var queryCache = sync.Map{}

func Getquery(path string) (string, error) {
    // 检查缓存
    if cached, ok := queryCache.Load(path); ok {
        return cached.(string), nil
    }
    
    // 数据库查询
    var query string
    err := db.QueryRow("SELECT sql_query FROM sqls WHERE sql_id=$1", path).Scan(&query)
    if err != nil {
        return "", err
    }
    
    // 存储到缓存
    queryCache.Store(path, query)
    return query, nil
}

3. 通用方法的优缺点

优点:

  • 减少重复代码
  • 动态查询配置
  • 快速原型开发

缺点:

  • 类型安全丢失
  • 调试困难
  • 性能优化受限
  • 缺乏编译时检查

改进的通用处理器示例:

type APIHandler struct {
    db *sqlx.DB
    queryCache *sync.Map
    validator *Validator
}

func (h *APIHandler) HandleRequest(w http.ResponseWriter, r *http.Request) {
    // 验证输入
    table, id, action := getpath(r)
    if !h.validator.ValidateTable(table) {
        http.Error(w, "Invalid table name", http.StatusBadRequest)
        return
    }
    
    // 获取查询模板
    query, err := h.GetQuery(table, action)
    if err != nil {
        http.Error(w, "Query not found", http.StatusNotFound)
        return
    }
    
    // 根据方法处理
    switch r.Method {
    case http.MethodGet:
        h.handleGet(w, r, query, id)
    case http.MethodPost:
        h.handlePost(w, r, query)
    case http.MethodPut:
        h.handlePut(w, r, query, id)
    case http.MethodDelete:
        h.handleDelete(w, r, query, id)
    }
}

func (h *APIHandler) handleGet(w http.ResponseWriter, r *http.Request, query, id string) {
    var result interface{}
    
    if id == "" {
        // 列表查询
        err := h.db.Select(&result, query)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    } else {
        // 单条查询
        err := h.db.Get(&result, query, id)
        if err != nil {
            http.Error(w, err.Error(), http.StatusNotFound)
            return
        }
    }
    
    json.NewEncoder(w).Encode(result)
}

主要安全建议:

  1. 始终使用参数化查询
  2. 验证所有用户输入
  3. 实现查询白名单机制
  4. 添加请求限流和监控
  5. 使用HTTPS传输数据

这种通用方法适合内部工具或原型开发,但在生产环境中需要更严格的安全控制和类型安全保证。

回到顶部