Cấp quyền cookie Golang

Aug 24 2020

Tôi đang viết hệ thống đăng nhập trong Go (Golang) bằng cách sử dụng cookie. Tôi nghĩ rằng nó không đủ an toàn. Bạn có thể cung cấp một số gợi ý về cách cải thiện bảo mật.
Tập tin chính:

package main

import (
    "fmt"
    "golang.org/x/crypto/bcrypt"
    "html/template"
    "math/rand"
    "net/http"
    "strings"
    "time"
)

var (
    runes  = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890")
    t *template.Template
)

func init()  {
    rand.Seed(time.Now().Unix())
    t, _ = template.ParseFiles("main.html","signup.html","signin.html")
}

type User struct {
    Login, Email string
}



func genToken() string {
    s := make([]rune, 15)
    for i := range s {
        s[i] = runes[rand.Intn(len(runes))]
    }
    return string(s)
}


func setCookie(w http.ResponseWriter, name, value string,d int) {
    cookie := http.Cookie{
        Name:    name,
        Value:   value,
    }
    if d != 0{
        expires := time.Now().AddDate(0,0,d)
        cookie.Expires = expires
    }
    http.SetCookie(w, &cookie)
}

func getCookie(r *http.Request, name string) string {
    c, err := r.Cookie(name)
    if err != nil {
        return ""
    }
    return c.Value
}

func deleteCookie(w http.ResponseWriter,name string){
    cookie := http.Cookie{
        Name: name,
        MaxAge: -1,
    }
    http.SetCookie(w, &cookie)
}

func signup(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
      case "GET":
        t.ExecuteTemplate(w,"signup.html",nil)
      case "POST":
        r.ParseForm()
        data := r.Form
        var error string
        if data["login"][0] == ""{
            error = "Login can't be empty"
        } else if data["email"][0] == ""{
            error = "Email can't be empty"
        } else if data["password"][0] == ""{
            error = "Password cant't be empty"
        } else if len(data["login"][0]) < 4{
            error = "Login must be at least 4 characters"
        } else if DB.checkLogin(data["login"][0]){
            error = "User with such login already exists"
        } else if !strings.ContainsRune(data["email"][0],'@'){
            error = "Email must contain @"
        } else if DB.checkEmail(data["email"][0]) {
            error = "User with such email already exists"
        } else if len(data["password"][0]) < 8{
            error = "Password must be at least 8 characters"
        } else if data["password2"][0] != data["password"][0]{
            error = "Passwords don't match"
        }
        if error != ""{
            values :=&User{}
            values.Login = data["login"][0]
            values.Email = data["email"][0]
            t, err := template.ParseFiles("signup.html")
            if err != nil{
                http.Error(w,"Internal server error",500)
            }
            t.Execute(w,values)
            fmt.Fprintln(w,"<hr><span style='color:red;'>" + error + "</span>")
        } else {
            hashedPassword, err := bcrypt.GenerateFromPassword([]byte(data["password"][0]),10)
            if err != nil{
                http.Error(w,"Internal server error",500)
            }
            DB.newUser(data["login"][0],data["email"][0],string(hashedPassword))
            http.Redirect(w,r,"/login",http.StatusSeeOther)
        }
    }
}

func signin(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
      case "GET":
        t.ExecuteTemplate(w,"signin.html",nil)
      case "POST":
          r.ParseForm()
          data := r.Form
          var error string
          if !DB.checkLogin(data["login"][0]){
              error = "User with such login doesn't exists"
          } else {
              if !DB.checkPassword(data["login"][0],data["password"][0]){
                  error = "Wrong password"
              }
          }
          if error != ""{
              values :=&User{}
              values.Login = data["login"][0]
              t, err := template.ParseFiles("signin.html")
              if err != nil{
                  http.Error(w,"Internal server error",http.StatusInternalServerError)
              }
              t.Execute(w,values)
              fmt.Fprintln(w,"<hr><span style='color:red;'>" + error + "</span>")
          } else {
              expiresAfter := 0
              if r.FormValue("remember") == "1"{
                  expiresAfter = 30
              }
              token := genToken()
              setCookie(w,"login",data["login"][0],expiresAfter)
              setCookie(w,"session_token",token,expiresAfter)
              DB.newSession(data["login"][0],token)
              http.Redirect(w,r,"/",http.StatusSeeOther)
          }
    }
}

func mainPage(w http.ResponseWriter, r *http.Request) {
    login := getCookie(r,"login")
    token := getCookie(r,"session_token")
    if !DB.checkToken(login,token){
        http.Redirect(w,r,"/login",http.StatusSeeOther)
    }
    user := DB.getUser(login)
    t.ExecuteTemplate(w,"main.html",user)
}

func logout(w http.ResponseWriter,r *http.Request){
    login := getCookie(r,"login")
    token := getCookie(r,"session_token")
    deleteCookie(w,"login")
    deleteCookie(w,"session_token")
    DB.deleteSession(login,token)
    http.Redirect(w,r,"/login",http.StatusSeeOther)
}

func main() {
    http.HandleFunc("/register", signup)
    http.HandleFunc("/login", signin)
    http.HandleFunc("/", mainPage)
    http.HandleFunc("/logout",logout)
    http.ListenAndServe(":8080", nil)
}

Tệp cơ sở dữ liệu:

package main

import (
    "database/sql"
    "golang.org/x/crypto/bcrypt"
    "log"

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

var DB = newDB("root:root574@/signin")

type db struct {
    DB *sql.DB
}

func newDB(name string) *db {
    conn, err := sql.Open("mysql", name)
    if err != nil {
        log.Fatal(err)
    }
    if err = conn.Ping(); err != nil {
        log.Fatal(err)
    }
    return &db{DB: conn}
}

func (db db) newUser(login, email, password string) {
    db.DB.Exec("INSERT INTO users(login,email,password) VALUES (?,?,?)", login, email, password)
}

func (db db) newSession(login, token string) {
    db.DB.Exec("INSERT INTO sessions(login,token) VALUES (?,?)",login,token)
}

func (db db) deleteSession(login, token string) {
    db.DB.Exec("DELETE FROM sessions WHERE login = ? and session_token = ?",login,token)
}

func (db db) checkLogin(login string) bool {
    var rows, _ = db.DB.Query("SELECT id FROM users WHERE login = ?", login)
    if rows.Next() {
        return true
    }
    rows.Close()
    return false
}

func (db db) checkEmail(email string) bool {
    var rows, _ = db.DB.Query("SELECT id FROM users WHERE email = ?", email)
    if rows.Next() {
        return true
    }
    rows.Close()
    return false
}

func (db db) checkPassword(login, password string) bool{
    var rows, _ = db.DB.Query("SELECT password FROM users WHERE login = ?", login)
    var dbpassword string
    rows.Next()
    rows.Scan(&dbpassword)
    rows.Close()
    if bcrypt.CompareHashAndPassword([]byte(dbpassword),[]byte(password)) != nil{
        return false
    }
    return true
}

func (db db) checkToken(login, token string) bool {
    var rows, _ = db.DB.Query("SELECT token FROM sessions WHERE login = ? and token = ?",login,token)
    if rows.Next(){
        return true
    }
    rows.Close()
    return false
}

func (db db) getUser(login string) *User {
    var rows, _ = db.DB.Query("select email FROM users WHERE login = ?",login)
    user := &User{}
    rows.Next()
    rows.Scan(&user.Email)
    rows.Close()
    user.Login = login
    return user
}

Bảng người dùng cơ sở dữ liệu:

+----------+--------------+------+-----+---------+----------------+
| Field    | Type         | Null | Key | Default | Extra          |
+----------+--------------+------+-----+---------+----------------+
| id       | int          | NO   | PRI | NULL    | auto_increment |
| login    | varchar(255) | YES  |     | NULL    |                |
| email    | varchar(255) | YES  |     | NULL    |                |
| password | varchar(255) | YES  |     | NULL    |                |
+----------+--------------+------+-----+---------+----------------+

Bảng phiên cơ sở dữ liệu:

+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| login | varchar(255) | YES  |     | NULL    |       |
| token | varchar(15)  | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+

Trả lời

2 Ludisposed Aug 27 2020 at 13:17

Không bao giờ người dùng PSEUDO giá trị ngẫu nhiên cho bí mật!

Điều này có thể bị kẻ tấn công khai thác bằng cách đoán giá trị hạt giống, trong trường hợp này:

rand.Seed(time.Now().Unix())

Và sau đó tạo ra một giá trị Cookie có thể để đăng nhập.

Thay vào đó, hãy sử dụng các triển khai an toàn bằng mật mã, trong golang, bạn nên sử dụng crypto/rand