From 38953c0db6c922fb71f85be3f6cd297a323f443d Mon Sep 17 00:00:00 2001 From: tangmingyou <234767776@qq.com> Date: Tue, 10 Jan 2023 17:55:08 +0800 Subject: [PATCH] dao login --- .gitignore | 2 + cmd/main.go | 28 ++-- etc/config.toml | 21 ++- etc/config.yaml | 0 go.mod | 5 + go.sum | 14 ++ internal/conf/conf.go | 50 +++++++ internal/dao/dao.go | 29 ++++ internal/model/entity/entity.go | 11 ++ internal/model/entity/user.go | 14 ++ internal/model/message.go | 12 -- internal/model/message/alert.go | 5 + internal/model/message/auth.go | 9 ++ internal/model/message/message.go | 25 ++++ internal/server/auth.go | 96 ------------ internal/server/server.go | 14 +- internal/server/ws.go | 42 ++++-- internal/service/auth.go | 147 ++++++++++++++++++- internal/service/user.go | 48 ++++++ internal/{subject => session}/net_account.go | 2 +- internal/{subject => session}/net_client.go | 2 +- internal/{subject => session}/player.go | 2 +- internal/session/subject.go | 8 + tool/collect/validator.go | 10 ++ tool/security/aes.go | 8 +- ws.html | 43 ++++++ 26 files changed, 496 insertions(+), 151 deletions(-) delete mode 100644 etc/config.yaml create mode 100644 internal/conf/conf.go create mode 100644 internal/dao/dao.go create mode 100644 internal/model/entity/entity.go create mode 100644 internal/model/entity/user.go delete mode 100644 internal/model/message.go create mode 100644 internal/model/message/alert.go create mode 100644 internal/model/message/auth.go create mode 100644 internal/model/message/message.go delete mode 100644 internal/server/auth.go create mode 100644 internal/service/user.go rename internal/{subject => session}/net_account.go (90%) rename internal/{subject => session}/net_client.go (95%) rename internal/{subject => session}/player.go (84%) create mode 100644 internal/session/subject.go create mode 100644 tool/collect/validator.go create mode 100644 ws.html diff --git a/.gitignore b/.gitignore index 5140799..b32653d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ *.patch *.tmp *.swp +*.db # system ignore .DS_Store @@ -26,3 +27,4 @@ Thumbs.db /target /configs /dist +/etc/texas-poker.db \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index 76c6927..736fdd4 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -5,25 +5,25 @@ import ( "crypto/rsa" "crypto/sha256" "encoding/json" - "flag" "fmt" - "texas-poker-bk/internal/model" + "texas-poker-bk/internal/conf" + "texas-poker-bk/internal/model/message" + "texas-poker-bk/internal/server" "time" ) -var confPath string - -func init() { - flag.StringVar(&confPath, "conf", "comet-example.toml", "default config path.") +func startServer() { + httpServer := server.NewServer() + err := httpServer.Run(conf.Conf.Http.Addr) + if err != nil { + panic(err) + } } func main() { - // testRsa() - -} - -func testToml() { + startServer() + // testRsa() } func testRsa() { @@ -44,11 +44,11 @@ func testRsa() { } func testMsgCodec() { - identity := &model.Identity{Token: "12312312asahjasd"} + identity := &message.ReqIdentity{Token: "12312312asahjasd"} bytes, _ := json.Marshal(identity) - // msg: int32:route:msg - msg := &model.Message{T: "identity", Ms: time.Now().UnixMilli(), D: bytes} + // message: int32:route:message + msg := &message.Message{T: "identity", Ms: time.Now().UnixMilli(), D: bytes} b, _ := json.Marshal(msg) fmt.Println(msg) diff --git a/etc/config.toml b/etc/config.toml index dd235bc..c482214 100644 --- a/etc/config.toml +++ b/etc/config.toml @@ -1,7 +1,18 @@ -[auth] - ignoreUrl = [ - "/ws", - "/auth/captcha" - ] +[http] +addr = ":9999" +[auth] +aesTokenKey = "9Nz3Y6DES3msAFndz4QsJAECUIFkf+KKaRa+jRnNALk=" +ignoreUrl = [ + "/ws", + "/auth/captcha", + "/auth/authorize", + "/user/findByName", +] +# 需要输入图形验证码的url +captchaUrl = [ + "/auth/authorize" +] +[sqlite] +dbPath = "etc/texas-poker.db" diff --git a/etc/config.yaml b/etc/config.yaml deleted file mode 100644 index e69de29..0000000 diff --git a/go.mod b/go.mod index 0d80bb7..948157a 100644 --- a/go.mod +++ b/go.mod @@ -16,9 +16,12 @@ require ( github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect @@ -29,4 +32,6 @@ require ( golang.org/x/text v0.5.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gorm.io/driver/sqlite v1.4.4 // indirect + gorm.io/gorm v1.24.3 // indirect ) diff --git a/go.sum b/go.sum index da773b3..ae89a31 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,12 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -43,6 +49,9 @@ github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= @@ -96,3 +105,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc= +gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= +gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/gorm v1.24.3 h1:WL2ifUmzR/SLp85CSURAfybcHnGZ+yLSGSxgYXlFBHg= +gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= diff --git a/internal/conf/conf.go b/internal/conf/conf.go new file mode 100644 index 0000000..65c3a4e --- /dev/null +++ b/internal/conf/conf.go @@ -0,0 +1,50 @@ +package conf + +import ( + "flag" + "github.com/BurntSushi/toml" +) + +var ( + confPath string + Conf *Config +) + +func init() { + // go run xx -conf=xx.toml + flag.StringVar(&confPath, "conf", "etc/config.toml", "default config path.") + + Conf = Default() + _, err := toml.DecodeFile("etc/config.toml", Conf) + if err != nil { + panic(err) + } + // fmt.Printf("%v", Conf) +} + +func Default() *Config { + return &Config{ + Auth: &Auth{ + IgnoreUrl: []string{}, + }, + } +} + +type Config struct { + Auth *Auth + Http *Http + Sqlite *Sqlite +} + +type Auth struct { + AesTokenKey string + IgnoreUrl []string +} + +type Http struct { + Addr string +} + +type Sqlite struct { + DbPath string +} diff --git a/internal/dao/dao.go b/internal/dao/dao.go new file mode 100644 index 0000000..2813026 --- /dev/null +++ b/internal/dao/dao.go @@ -0,0 +1,29 @@ +package dao + +import ( + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "texas-poker-bk/internal/conf" +) + +var Dao *Persistent + +type Persistent struct { + Sqlite *gorm.DB +} + +func init() { + Dao = &Persistent{ + Sqlite: newSqlite(), + } +} + +func newSqlite() (db *gorm.DB) { + db, err := gorm.Open(sqlite.Open(conf.Conf.Sqlite.DbPath), &gorm.Config{ + QueryFields: true, + }) + if err != nil { + panic(err) + } + return db +} diff --git a/internal/model/entity/entity.go b/internal/model/entity/entity.go new file mode 100644 index 0000000..9b3135b --- /dev/null +++ b/internal/model/entity/entity.go @@ -0,0 +1,11 @@ +package entity + +import ( + "time" +) + +// Id int64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"` +type Model struct { + CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` +} diff --git a/internal/model/entity/user.go b/internal/model/entity/user.go new file mode 100644 index 0000000..e118c55 --- /dev/null +++ b/internal/model/entity/user.go @@ -0,0 +1,14 @@ +package entity + +type User struct { + Model + Id int64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"` + Username string `gorm:"column:username" json:"username"` // comment:用户名 + Password string `gorm:"column:password" json:"password"` // comment:密码 + Nickname string `gorm:"column:nickname" json:"nickname"` // comment:昵称 + Version int64 `gorm:"column:version" json:"version"` // comment:更新版本锁 +} + +func (u *User) TableName() string { + return "t_user" +} diff --git a/internal/model/message.go b/internal/model/message.go deleted file mode 100644 index d4a2a25..0000000 --- a/internal/model/message.go +++ /dev/null @@ -1,12 +0,0 @@ -package model - -type Message struct { - T string `json:"t"` // action 时间主题 - Ms int64 `json:"ms"` // 毫秒时间戳 - D []byte `json:"d"` // 消息数据 -} - -// Identity 身份认证消息 -type Identity struct { - Token string `json:"t"` -} diff --git a/internal/model/message/alert.go b/internal/model/message/alert.go new file mode 100644 index 0000000..801ab42 --- /dev/null +++ b/internal/model/message/alert.go @@ -0,0 +1,5 @@ +package message + +// Alert 弹窗消息 +type Alert struct { +} diff --git a/internal/model/message/auth.go b/internal/model/message/auth.go new file mode 100644 index 0000000..5400076 --- /dev/null +++ b/internal/model/message/auth.go @@ -0,0 +1,9 @@ +package message + +// ReqIdentity 身份认证消息 +type ReqIdentity struct { + Token string `json:"t"` +} + +type ResIdentity struct { +} diff --git a/internal/model/message/message.go b/internal/model/message/message.go new file mode 100644 index 0000000..4d332ad --- /dev/null +++ b/internal/model/message/message.go @@ -0,0 +1,25 @@ +package message + +type Message struct { + T string `json:"t"` // action 时间主题 + Ms int64 `json:"ms"` // 毫秒时间戳 + D []byte `json:"d"` // 消息数据 +} + +type Res struct { + Code int32 `json:"code"` + Msg string `json:"msg"` + Data any `json:"data"` +} + +func NewRes(code int32, msg string, data any) *Res { + return &Res{Code: code, Msg: msg, Data: data} +} + +func NewResSuccess(data any) *Res { + return &Res{Code: 0, Data: data} +} + +func NewResFail(msg string) *Res { + return &Res{Code: 0, Msg: msg} +} diff --git a/internal/server/auth.go b/internal/server/auth.go deleted file mode 100644 index 50a7a74..0000000 --- a/internal/server/auth.go +++ /dev/null @@ -1,96 +0,0 @@ -package server - -import ( - "encoding/base64" - "encoding/json" - "github.com/gin-gonic/gin" - "net/http" - "texas-poker-bk/tool/security" -) - -var ( - SubjectKey = "auth:subject" - sc = &scEtc{} // 加密配置 -) - -// Subject 认证对象 -type Subject struct { - Id int64 `json:"id,omitempty"` // 用户id - Name string `json:"name,omitempty"` // 用户名 - Time int64 `json:"time,omitempty"` // 生成时间戳 -} - -type scEtc struct { - securityKey string - keyBytes []byte -} - -func init() { - sc.securityKey = "9Nz3Y6DES3msAFndz4QsJAECUIFkf+KKaRa+jRnNALk=" - keyBytes, err := base64.URLEncoding.DecodeString(sc.securityKey) - if err != nil { - panic(err) - } - sc.keyBytes = keyBytes -} - -// SubjectAuthFilter 认证过滤器 -func SubjectAuthFilter(ctx *gin.Context) { - auth := ctx.GetHeader("Authorization") - if auth == "" { - // TODO config ignore urls - //if isIgnoreUrl(ctx.Request.URL.Path) { - // return - //} - ctx.JSON(http.StatusUnauthorized, gin.H{"msg": "请登录后进行操作"}) - ctx.Abort() - return - } - - defer func() { - // 捕获aes解析错误 - if r := recover(); r != nil { - ctx.JSON(http.StatusUnauthorized, gin.H{"msg": "认证失败,请重新登录"}) - ctx.Abort() - } - }() - // TODO 这里返回 error,不然后捕获后续执行 handler 的 panic - subject := DecodeSubject(auth) - ctx.Set(SubjectKey, subject) -} - -// EncodeSubject 对subject对象aesCBC加密并返回base64Std编码的 token -// subject 客户端对象 -func EncodeSubject(subject *Subject) string { - bytes, err := json.Marshal(subject) - if err != nil { - panic(err) - } - encode := security.EncryptAesCBC(bytes, sc.keyBytes) - return base64.URLEncoding.EncodeToString(encode) -} - -// DecodeSubject 解码并解密token返回subject对象 -// auth base64Std 编码的 token -func DecodeSubject(auth string) *Subject { - bytes, err := base64.URLEncoding.DecodeString(auth) - if err != nil { - panic(err) - } - decode := security.DecryptAesCBC(bytes, sc.keyBytes) - subject := &Subject{} - err = json.Unmarshal(decode, subject) - if err != nil { - panic(err) - } - return subject -} - -// GetSubject 从请求上下文中获取客户端对象, 请求经过了认证过滤器 -func GetSubject(ctx *gin.Context) *Subject { - subject, exists := ctx.Get(SubjectKey) - if !exists { - return nil - } - return subject.(*Subject) -} diff --git a/internal/server/server.go b/internal/server/server.go index 160024a..f9e6869 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -5,17 +5,23 @@ import ( "texas-poker-bk/internal/service" ) -func Start() *gin.Engine { +func NewServer() *gin.Engine { server := gin.Default() + // token认证过滤器 + server.Use(service.SubjectAuthFilter) + + // session storage + server.Use(SessionStore("golang-tech-stack")) // websocket server.GET("/ws", Upgrade) auth := server.Group("/auth") - auth.GET("/captcha", service.Captcha) // 验证码 + auth.GET("/captcha", service.Captcha) // 验证码 + auth.POST("/authorize", service.Authorize) // 登录或注册认证 - // session storage - server.Use(SessionStore("golang-tech-stack")) + user := server.Group("/user") + user.GET("/findByName", service.FindUserByName) return server } diff --git a/internal/server/ws.go b/internal/server/ws.go index 21123e9..8860d2a 100644 --- a/internal/server/ws.go +++ b/internal/server/ws.go @@ -7,21 +7,33 @@ import ( "github.com/gorilla/websocket" "log" "net/http" - "texas-poker-bk/internal/model" - "texas-poker-bk/internal/subject" + "texas-poker-bk/internal/model/message" + "texas-poker-bk/internal/service" + "texas-poker-bk/internal/session" "time" ) func Upgrade(ctx *gin.Context) { + token, exists := ctx.GetQuery("t") + if !exists { + ctx.JSON(http.StatusUnauthorized, gin.H{"message": "unauth"}) + return + } + subject, err := service.DecodeSubject(token) + if err != nil { + ctx.JSON(http.StatusUnauthorized, gin.H{"message": "token failed"}) + return + } + conn, err := upgradeWs(ctx.Writer, ctx.Request) if err != nil { fmt.Println(err) http.Error(ctx.Writer, "Not a websocket handshake", 400) return } - client := subject.NewNetClient(conn) + client := session.NewNetClient(conn) - go handleNetClient(client) + go handleNetClient(client, subject) } func upgradeWs(resWriter http.ResponseWriter, req *http.Request) (*websocket.Conn, error) { @@ -37,7 +49,7 @@ func upgradeWs(resWriter http.ResponseWriter, req *http.Request) (*websocket.Con } // 处理新建的websocket -func handleNetClient(client *subject.NetClient) { +func handleNetClient(client *session.NetClient, subject *session.Subject) { //defer (func() { // client.Close("conn finish") //})() @@ -52,7 +64,7 @@ func handleNetClient(client *subject.NetClient) { client.Close("read conn err:" + err.Error()) return } - msg := &model.Message{} + msg := &message.Message{} err = json.Unmarshal(bytes, msg) if err != nil { @@ -60,11 +72,23 @@ func handleNetClient(client *subject.NetClient) { continue } - // TODO 认证消息(return -> account msg route) + // 认证消息(return -> account message route) switch msg.T { case "identify": - param := &model.Identity{} + param := &message.ReqIdentity{} _ = json.Unmarshal(msg.D, param) + subject, err := service.DecodeSubject(param.Token) + if err != nil { + res := message.NewResFail(err.Error()) + resBytes, _ := json.Marshal(res) + _ = client.Conn.WriteMessage(websocket.TextMessage, resBytes) + // 认证失败关闭连接 + client.Close("authorization fail!") + return + } + + fmt.Printf("subject: %v", subject) + default: client.Close("wrong message") } @@ -75,6 +99,6 @@ func handleNetClient(client *subject.NetClient) { } // handleIdentity TODO 通过 token 认证连接的身份 -func handleIdentity(identity *model.Identity, client *subject.NetClient) { +func handleIdentity(identity *message.ReqIdentity, client *session.NetClient) { } diff --git a/internal/service/auth.go b/internal/service/auth.go index eb8ae01..e4e21ac 100644 --- a/internal/service/auth.go +++ b/internal/service/auth.go @@ -1,12 +1,151 @@ package service -import "github.com/gin-gonic/gin" +import ( + "encoding/base64" + "encoding/json" + "github.com/gin-gonic/gin" + "net/http" + "texas-poker-bk/internal/conf" + "texas-poker-bk/internal/model/entity" + "texas-poker-bk/internal/session" + "texas-poker-bk/tool/collect" + "texas-poker-bk/tool/security" + "time" +) -// Login 登录或注册 -func Login(ctx *gin.Context) { +// http: 登录注册、创建房间 +// ws: 大厅、房间、房间状态 +var ( + SubjectKey = "auth:session" + aesTokenKeyBytes []byte // token aes 加密 key +) + +// Authorize 登录或注册 +func Authorize(ctx *gin.Context) { + username := ctx.PostForm("username") + password := ctx.PostForm("password") + + //username := ctx.Param("username") + //password := ctx.Param("password") + if username == "" || password == "" { + ctx.JSON(http.StatusBadRequest, gin.H{"message": "用户名或密码不能为空"}) + return + } + user := userDao.FindUserByName(username) + if user == nil { + // 注册 + user = registerUser(username, password) + } else { + // 校验密码 + if user.Password != password { + ctx.JSON(http.StatusUnauthorized, gin.H{"message": "用户名或密码有误"}) + return + } + } + + sub := &session.Subject{Id: user.Id, Name: user.Username, Time: time.Now().UnixMilli()} + token, err := EncodeSubject(sub) + if err != nil { + ctx.JSON(http.StatusUnauthorized, gin.H{"message": err.Error()}) + return + } + ctx.JSON(http.StatusOK, gin.H{"data": gin.H{"token": token}}) +} + +// 注册用户到DB +func registerUser(username string, password string) *entity.User { + user := &entity.User{ + Username: username, + Password: password, + Nickname: username, + } + // TODO error + userDao.dao.Save(user) + return user +} + +func init() { + keyBytes, err := base64.StdEncoding.DecodeString(conf.Conf.Auth.AesTokenKey) + if err != nil { + panic(err) + } + aesTokenKeyBytes = keyBytes +} + +// SubjectAuthFilter 认证过滤器 +func SubjectAuthFilter(ctx *gin.Context) { + + // 检查是不需要登录的 url + // ignore auth urls + if collect.IsNotEmptySlice(conf.Conf.Auth.IgnoreUrl) { + for _, ignoreUrl := range conf.Conf.Auth.IgnoreUrl { + if ignoreUrl == ctx.Request.URL.Path { + return + } + } + } + + auth := ctx.GetHeader("Authorization") + if auth == "" { + ctx.JSON(http.StatusUnauthorized, gin.H{"message": "请登录后进行操作"}) + ctx.Abort() + } + + defer func() { + // 捕获aes解析错误 + if r := recover(); r != nil { + ctx.JSON(http.StatusUnauthorized, gin.H{"message": "认证失败,请重新登录"}) + ctx.Abort() + } + }() + + // 这里返回 error,不然后捕获后续执行 handler 的 panic + subject, err := DecodeSubject(auth) + if err != nil { + ctx.JSON(http.StatusUnauthorized, gin.H{"message": "认证失败,请重新登录"}) + ctx.Abort() + } else { + // 设置token用户到请求上下文 + ctx.Set(SubjectKey, subject) + } +} + +// EncodeSubject 对subject对象aesCBC加密并返回base64Std编码的 token +// session 客户端对象 +func EncodeSubject(subject *session.Subject) (string, error) { + bytes, err := json.Marshal(subject) + if err != nil { + return "", err + } + encode, err := security.EncryptAesCBC(bytes, aesTokenKeyBytes) + if err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(encode), nil } -func Register() { +// DecodeSubject 解码并解密token返回subject对象 +// auth base64Std 编码的 token +func DecodeSubject(auth string) (*session.Subject, error) { + bytes, err := base64.URLEncoding.DecodeString(auth) + if err != nil { + return nil, err + } + decode := security.DecryptAesCBC(bytes, aesTokenKeyBytes) + subject := &session.Subject{} + err = json.Unmarshal(decode, subject) + if err != nil { + return nil, err + } + return subject, nil +} +// GetSubject 从请求上下文中获取客户端对象, 请求经过了认证过滤器 +func GetSubject(ctx *gin.Context) *session.Subject { + subject, exists := ctx.Get(SubjectKey) + if !exists { + panic("request subject not exists!") + } + return subject.(*session.Subject) } diff --git a/internal/service/user.go b/internal/service/user.go new file mode 100644 index 0000000..8ec4f8c --- /dev/null +++ b/internal/service/user.go @@ -0,0 +1,48 @@ +package service + +import ( + "fmt" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "net/http" + "texas-poker-bk/internal/dao" + "texas-poker-bk/internal/model/entity" +) + +var userDao *User = &User{dao: dao.Dao.Sqlite} + +func init() { + err := dao.Dao.Sqlite.AutoMigrate(&entity.User{}) + if err != nil { + fmt.Println("user table", err) + } +} + +type User struct { + dao *gorm.DB +} + +func FindUserByName(ctx *gin.Context) { + username := ctx.Param("username") + user := userDao.FindUserByName(username) + if user == nil { + ctx.JSON(http.StatusOK, gin.H{ + "message": "not found", + }) + return + } + ctx.JSON(http.StatusOK, gin.H{ + "message": "ok", + "data": user, + }) +} + +func (u *User) FindUserByName(username string) *entity.User { + user := &entity.User{} + tx := u.dao.Model(user).Where("username=?", username).Limit(1).Scan(user) + // 查询出结果时 tx.RowsAffected 固定=1 + if tx.RowsAffected == 0 { + return nil + } + return user +} diff --git a/internal/subject/net_account.go b/internal/session/net_account.go similarity index 90% rename from internal/subject/net_account.go rename to internal/session/net_account.go index 6906915..f1ba72b 100644 --- a/internal/subject/net_account.go +++ b/internal/session/net_account.go @@ -1,4 +1,4 @@ -package subject +package session // NetAccount 已认证的长连接用户 type NetAccount struct { diff --git a/internal/subject/net_client.go b/internal/session/net_client.go similarity index 95% rename from internal/subject/net_client.go rename to internal/session/net_client.go index 8ce5d26..3744488 100644 --- a/internal/subject/net_client.go +++ b/internal/session/net_client.go @@ -1,4 +1,4 @@ -package subject +package session import ( "github.com/gorilla/websocket" diff --git a/internal/subject/player.go b/internal/session/player.go similarity index 84% rename from internal/subject/player.go rename to internal/session/player.go index bb7fe19..e1bc452 100644 --- a/internal/subject/player.go +++ b/internal/session/player.go @@ -1,4 +1,4 @@ -package subject +package session // Player 玩家: table,balance,(hand card) type Player struct { diff --git a/internal/session/subject.go b/internal/session/subject.go new file mode 100644 index 0000000..36dcd52 --- /dev/null +++ b/internal/session/subject.go @@ -0,0 +1,8 @@ +package session + +// Subject 认证对象 +type Subject struct { + Id int64 `json:"id,omitempty"` // 用户id + Name string `json:"name,omitempty"` // 用户名 + Time int64 `json:"time,omitempty"` // 生成时间戳 +} diff --git a/tool/collect/validator.go b/tool/collect/validator.go new file mode 100644 index 0000000..53b39bf --- /dev/null +++ b/tool/collect/validator.go @@ -0,0 +1,10 @@ +package collect + +// IsEmptySlice 切片是否为空 +func IsEmptySlice[T any](slice []T) bool { + return slice == nil || len(slice) == 0 +} + +func IsNotEmptySlice[T any](slice []T) bool { + return !IsEmptySlice(slice) +} diff --git a/tool/security/aes.go b/tool/security/aes.go index 42e66a7..c1eb4d8 100644 --- a/tool/security/aes.go +++ b/tool/security/aes.go @@ -27,7 +27,7 @@ func TestAes() { fmt.Println(key) text := []byte("you are my sunshine!") - encrypt := EncryptAesCBC(text, key) + encrypt, _ := EncryptAesCBC(text, key) fmt.Println(encrypt) fmt.Println(base64.URLEncoding.EncodeToString(text)) @@ -52,10 +52,10 @@ func getAesKey(keySize int) []byte { // EncryptAesCBC // src -> 要加密的原文 // key -> 秘钥, 和加密秘钥相同, 大小为: 8byte -func EncryptAesCBC(src, key []byte) []byte { +func EncryptAesCBC(src, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { - panic(err) + return nil, err } blockSize := block.BlockSize() // 对最后一个明文分组进行数据填充 @@ -68,7 +68,7 @@ func EncryptAesCBC(src, key []byte) []byte { // 5.加密连续的数据块 dst := make([]byte, len(src)) blackMode.CryptBlocks(dst, src) - return dst + return dst, nil } // DecryptAesCBC diff --git a/ws.html b/ws.html new file mode 100644 index 0000000..c57723c --- /dev/null +++ b/ws.html @@ -0,0 +1,43 @@ + + +
+ +