先來寫登錄,我們需要驗證帳號與密碼是否正確,然後回傳user需要的資料(uid與其下的owner)
進入database後寫入
DELIMITER ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `login`(
user_name VARCHAR(50),
password CHAR(44)
)
BEGIN
SELECT `user`.`uid`, `user`.`username`, GROUP_CONCAT(`owner`.`oid` SEPARATOR " ") AS subOid, GROUP_CONCAT(`owner`.`uniquename` SEPARATOR " ") AS subOuniquename, GROUP_CONCAT(`owner`.`nickname` SEPARATOR " ") AS subOnickname, GROUP_CONCAT(`owner`.`description` SEPARATOR " ") AS subOdescription
FROM `user`
Left JOIN `owner`
ON `owner`.`uid` = `user`.`uid`
WHERE `user`.`username` = user_name AND `user`.`password` = password
GROUP BY `user`.`uid`;
END ;;
DELIMITER ;
登錄後就要給予權限了,現在登錄驗證都會採用oauth2的流程,不過我們先別寫的太複雜,我們先簡單設個登錄後生成token紀錄在資料庫並回傳前端,之後前端再用token來驗證就好。
先來寫token的table
CREATE TABLE `token` (
`uid` int unsigned NOT NULL,
`accesscode` char(44) NOT NULL COMMENT 'account number',
`createtime` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`accesscode`)
);
其實把token存在redis等暫存資料庫會更好,不過暫時就先這樣。
現在把token存起來
DELIMITER ;;
CREATE PROCEDURE `new_token`(
userid INT UNSIGNED,
code CHAR(44)
)
BEGIN
INSERT INTO `token` (`uid`, `accesscode`) VALUES (userid, code);
END ;;
DELIMITER ;
別忘了我們的密碼是與salt一起hash的,所以要驗密碼還要先拿出salt
DELIMITER ;;
CREATE PROCEDURE `get_user_salt`(
username varchar(40)
)
BEGIN
SELECT `salt`
FROM `user`
WHERE `user`.`username` = username;
END ;;
DELIMITER ;
現在來寫go,在database/scheme.go補上
type UserOut struct {
Uid int `json:"uid" xorm:"not null pk autoincr INT(11) 'uid'"`
Username string `json:"username" xorm:"not null comment('account number') VARCHAR(40) 'username'"`
OwnerSub `xorm:"extends"`
}
type OwnerSub struct {
Su string `json:"subOid" xorm:"VARCHAR(512) 'subOid'"`
SubUniquename string `json:"subOuniquename" xorm:"VARCHAR(512) 'subOuniquename'"`
SubNickname string `json:"subOnickname" xorm:"VARCHAR(512) 'subOnickname'"`
SubDescription string `json:"subOdescription" xorm:"VARCHAR(512) 'subOdescription'"`
}
在database/account.go寫入
func Login(userName string, password string) (*UserOut, error) {
userData := &UserOut{}
has, err := db.SQL("call login(?, ?)", userName, password).Get(userData)
if err != nil {
return nil, err
} else if !has {
return nil, nil
}
return userData, nil
}
func GetSalt(userName string) (string, error) {
var salt string
has, err := db.SQL("call get_user_salt(?)", userName).Get(&salt)
if err != nil {
return "", err
} else if !has {
return "", nil
}
return salt, nil
}
在database下創建auth.go寫入
package database
// generate a new access token
func NewAccessToken(uid, code string) error {
return checkAffect(db.Exec("call new_token(?, ?)", uid, code))
}
在serve串起來吧!在serve/account.go補上
import (
"net/url"
"strconv"
)
func Login(c *gin.Context) {
// get salt
userName := c.PostForm("username")
salt, err := database.GetSalt(userName)
if err != nil {
log.Warn(c, apperr.ErrPermissionDenied, err, "Sorry, something error", "database error of login")
return
} else if salt == "" {
log.Warn(c, apperr.ErrWrongArgument, err, "wrong username or password")
return
}
// get hash password
pw, err := hash.GetPWHashString(c.PostForm("password"), salt, Params.iterations, Params.memory, Params.parallelism, Params.keyLength)
if err != nil {
log.Warn(c, apperr.ErrPermissionDenied, err, "Sorry, something error", "base64 decode error")
return
}
// login
userData, err := database.Login(userName, pw)
if err != nil {
log.Warn(c, apperr.ErrPermissionDenied, err, "Sorry, something error", "database error of login")
return
} else if userData == nil {
log.Warn(c, apperr.ErrWrongArgument, err, "wrong username or password")
return
}
// generate new token
uid := strconv.Itoa(userData.Uid)
code, err := newAccessToken(uid)
if err != nil {
log.Warn(c, apperr.ErrPermissionDenied, err, "Sorry, something error", "database error of create token")
}
// delete uid
userData.Uid = 0
// create new cookie
c.Writer.Header().Add("Set-Cookie", cookie.CreateCookie("AccessToken", []string{"AccessCode", "uid"},
[]string{code, uid}, 2592000, "/", "."+setting.Servers["main"].Host, http.SameSiteLaxMode,
true, true))
c.JSON(http.StatusOK, gin.H{
"user": userData,
})
}
// generate a new token and save it
func newAccessToken(uid string) (string, error) {
code, err := random.GetRandomString(32)
if err != nil {
return "", err
}
return code, database.NewAccessToken(uid, code)
}
解釋:
註冊到router,在router/account.go補上
r.POST("/login", serve.Login)
我們已經完成登錄了,接下來寫middleware來控管權限。
目前的工作環境
.
├── app
│ ├── apperr
│ │ ├── error.go
│ │ └── handle.go
│ ├── common
│ │ └── cookie.go
│ ├── config
│ │ └── app
│ │ ├── app.yaml
│ │ └── error.yaml
│ ├── database
│ │ ├── auth.go
│ │ ├── connect.go
│ │ ├── error.go
│ │ ├── main.go
│ │ └── scheme.go
│ ├── go.mod
│ ├── go.sum
│ ├── log
│ │ ├── logger.go
│ │ └── logging.go
│ ├── main.go
│ ├── middleware
│ │ ├── error.go
│ │ └── log.go
│ ├── router
│ │ ├── account.go
│ │ ├── host_switch.go
│ │ └── main.go
│ ├── serve
│ │ ├── account.go
│ │ ├── auth.go
│ │ ├── main.go
│ │ └── main_test.go
│ ├── setting
│ │ └── setting.go
│ ├── util
│ │ ├── debug
│ │ │ ├── stack.go
│ │ │ └── stack_test.go
│ │ ├── file
│ │ │ └── file.go
│ │ ├── hash
│ │ │ ├── hash.go
│ │ │ └── hash_test.go
│ │ └── random
│ │ └── random.go
│ └── view
│ ├── css
│ ├── html
│ │ ├── component
│ │ │ ├── blogContainer.html
│ │ │ └── blogList.html
│ │ └── meta
│ │ ├── head.html
│ │ └── index.html
│ └── js
├── config
│ └── app
│ ├── app.yaml
│ └── error.yaml
└── database
└── maindata