go get -u github.com/beego/beego/v2
go get -u github.com/beego/bee/v2
beego 是一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及后端服务等各种应用,是一个 RESTful 的框架,主要设计灵感来源于 tornado、sinatra 和 flask 这三个框架,但是结合了 Go 本身的一些特性(interface、struct 嵌入等)而设计的一个框架。
官方文档 https://beego.me/docs/intro/
go get github.com/dgrijalva/jwt-go
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
Github Repo: https://github.com/dgrijalva/jwt-go
main.go
连接数据库 (这里我用的是mysql)+ 运行主程序
package main
import (
"firstAPI/models"
_ "firstAPI/routers"
"fmt"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/orm"
_ "github.com/go-sql-driver/mysql"
)
func init() {
orm.Debug = true
if err1 := orm.RegisterDriver("mysql", orm.DRMySQL); err1 != nil {
logs.Error(err1.Error())
}
orm.RegisterModel(new(models.User))
if err2 := orm.RegisterDataBase("default","mysql","root:12345678@tcp(127.0.0.1:3306)/testdb");err2 != nil {
logs.Error(err2.Error())
panic(err2.Error())
}
fmt.Println("Connected to the database")
orm.RunSyncdb("default", false, true)
}
func main() {
if beego.BConfig.RunMode == "dev" {
beego.BConfig.WebConfig.DirectoryIndex = true
beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
}
beego.Run()
models/utils.go
产生token,验证token,更新token
package models
import(
"crypto/rand"
"errors"
"fmt"
"github.com/astaxie/beego/logs"
"github.com/dgrijalva/jwt-go"
"golang.org/x/crypto/scrypt"
"io"
"log"
"time"
)
// JWT : HEADER PAYLOAD SIGNATURE
const (
SecretKEY string = "JWT-Secret-Key"
DEFAULT_EXPIRE_SECONDS int = 180 // default expired 1 minute
PasswordHashBytes = 16
)
// This struct is the payload
type MyCustomClaims struct {
UserID int `json:"UserID"`
jwt.StandardClaims
}
// This struct is the parsing of token payload
type JwtPayload struct {
Username string `json:"Username"`
UserID int `json:"UserID"`
IssuedAt int64 `json:"Iat"`
ExpiresAt int64 `json:"Exp"`
}
//generate token
func GenerateToken(loginInfo *LoginRequest, userID int, expiredSeconds int) (tokenString string, err error) {
if expiredSeconds == 0 {
expiredSeconds = DEFAULT_EXPIRE_SECONDS
}
// Create the Claims
mySigningKey := []byte(SecretKEY)
expireAt := time.Now().Add(time.Second * time.Duration(expiredSeconds)).Unix()
logs.Info("Token will be expired at ", time.Unix(expireAt, 0))
user := *loginInfo
claims := MyCustomClaims{
userID,
jwt.StandardClaims{
Issuer: user.Username,
IssuedAt: time.Now().Unix(),
ExpiresAt: expireAt,
},
}
// Create the token using your claims
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Signs the token with a secret
tokenStr, err := token.SignedString(mySigningKey)
if err != nil {
return "", errors.New("error: failed to generate token")
}
return tokenStr, nil
}
//validate token
func ValidateToken(tokenString string) (*JwtPayload, error) {
token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{
},
func(token *jwt.Token) (interface{
}, error) {
return []byte(SecretKEY), nil
})
claims, ok := token.Claims.(*MyCustomClaims)
if ok && token.Valid {
log.Println("ok && token valid")
logs.Info("%v %v", claims.UserID, claims.StandardClaims.ExpiresAt)
logs.Info("Token was issued at ", time.Now().Unix())
logs.Info("Token will be expired at ", time.Unix(claims.StandardClaims.ExpiresAt, 0))
return &JwtPayload{
Username: claims.StandardClaims.Issuer,
UserID: claims.UserID,
IssuedAt: claims.StandardClaims.IssuedAt,
ExpiresAt: claims.StandardClaims.ExpiresAt,
}, nil
} else {
fmt.Println(err)
return nil, errors.New("error: failed to validate token")
}
}
//update token
func RefreshToken(tokenString string) (newTokenString string, err error) {
// get previous token
token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{
},
func(token *jwt.Token) (interface{
}, error) {
return []byte(SecretKEY), nil
})
claims, ok := token.Claims.(*MyCustomClaims)
if !ok || !token.Valid {
return "", err
}
mySigningKey := []byte(SecretKEY)
expireAt := time.Now().Add(time.Second * time.Duration(DEFAULT_EXPIRE_SECONDS)).Unix() //new expired
newClaims := MyCustomClaims{
claims.UserID,
jwt.StandardClaims{
Issuer: claims.StandardClaims.Issuer, //name of token issue
IssuedAt: time.Now().Unix(), //time of token issue
ExpiresAt: expireAt,// new expired
},
}
// generate new token with new claims
newToken := jwt.NewWithClaims(jwt.SigningMethodHS256, newClaims)
// sign the token with a secret
tokenStr, err := newToken.SignedString(mySigningKey)
if err != nil {
return "", errors.New("error: failed to generate new fresh json web token")
}
return tokenStr, nil
}
Hash密码
// generate salt
func GenerateSalt() (salt string, err error) {
buf := make([]byte, PasswordHashBytes)
if _, err := io.ReadFull(rand.Reader, buf); err != nil {
return "", errors.New("error: failed to generate user's salt")
}
return fmt.Sprintf("%x", buf), nil
}
// generate password hash
func GeneratePassHash(password string, salt string) (hash string, err error) {
h, err := scrypt.Key([]byte(password), []byte(salt), 16384, 8, 1, PasswordHashBytes)
if err != nil {
return "", errors.New("error: failed to generate password hash")
}
return fmt.Sprintf("%x", h), nil
}
验证前端传回来的token是否过期
func CheckStatus(tokenString string) (string,int64) {
jp, err := ValidateToken(tokenString)
if err != nil {
// if token has already expired
fmt.Println("Your token has expired, Please log in again! ")
return "", -1
}
timeDiff := jp.ExpiresAt - time.Now().Unix()
fmt.Println("timeDiff = ", timeDiff)
if timeDiff <= 30 {
// if token is close to expiration, refresh the token
fmt.Println("Your token would soon be expired")
newToken, err := RefreshToken(tokenString)
if err == nil {
return newToken, timeDiff
}
}
// if token is valid, do nothing
fmt.Println("Your token is good ")
return tokenString,timeDiff
}
models/user.go
登录和注册 (request 根据自己需求定义)
func DoLogin(lr *LoginRequest) (*LoginResponse, int, error){
// get username and password
username := lr.Username
password := lr.Password
// validate user name and password is they are empty
if len(username) == 0 || len(password) == 0 {
return nil, http.StatusBadRequest,errors.New("error: username or password is empty")
}
o := orm.NewOrm()
// check if the username exists
user := &User{
Username: username}
err := o.Read(user,"Username")
if err != nil {
return nil, http.StatusBadRequest, errors.New("error: username doesn't exist")
}
// generate the password hash
hash, err := GeneratePassHash(password,user.Salt)
if err != nil {
return nil, http.StatusBadRequest, err
}
if hash != user.Password {
return nil, http.StatusBadRequest,errors.New("error: password is error")
}
// generate token
tokenString, err := GenerateToken(lr, user.Id, 0)
if err != nil {
return nil, http.StatusBadRequest, err
}
return &LoginResponse{
Username: user.Username,
UserID: user.Id,
Token: tokenString,
},http.StatusOK,nil
}
func DoCreateUser(cr *CreateRequest)(*CreateResponse,int,error){
o := orm.NewOrm()
// check if username exists
userNameCheck := User{
Username: cr.Username}
err := o.Read(&userNameCheck,"Username")
if err == nil {
return nil, http.StatusBadRequest, errors.New("username has already existed")
}
//generate salt
saltKey, err := GenerateSalt()
if err != nil {
logs.Info(err.Error())
return nil, http.StatusBadRequest, err
}
// generate password hash
hash, err := GeneratePassHash(cr.Password,saltKey)
if err != nil {
logs.Info(err.Error())
return nil, http.StatusBadRequest,err
}
// create user
user := User{
}
user.Username = cr.Username
user.Password = hash
user.Salt = saltKey
_, err = o.Insert(&user)
if err != nil {
logs.Info(err.Error())
return nil, http.StatusBadRequest,err
}
return &CreateResponse{
UserID:user.Id,
Username: user.Username,
}, http.StatusOK,nil
}
感谢阅读~~~