【REST2SQL】01RDB关系型数据库REST初设计
【REST2SQL】02 GO连接Oracle数据库
【REST2SQL】03 GO读取JSON文件
【REST2SQL】04 REST2SQL第一版Oracle版实现
【REST2SQL】05 GO 操作 达梦 数据库
【REST2SQL】06 GO 跨包接口重构代码
【REST2SQL】07 GO 操作 Mysql 数据库
【REST2SQL】08 日志重构增加输出到文件log.txt
【REST2SQL】09 给Go的可执行文件exe加图标和版本信息等
【REST2SQL】10 REST2SQL操作指南
【REST2SQL】11 基于jwt-go生成token与验证
【REST2SQL】11 基于jwt-go生成token与验证的Token生成和验证合并到【REST2SQL】
1 Rest2sql目录下新建token子目录
- Rest2sql目录下新建token子目录
- 拷贝 【REST2SQL】11 基于jwt-go生成token与验证 的mytoken.go
- mytoken.go改名为 token.go
2 token.go的代码重构
- 包名改为token
- 屏蔽或删除 main()入口函数
- 重构全局变量,都改为外部不可见,即变量名改为首字母改为小写即可
- 只暴露生成token函数GenerateTokenHandler()和验证token函数ValidateTokenHandler()
- 暴露函数第二个参数重构
重构完成的代码如下:
package token
import (
"crypto/rand"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
jwt "github.com/dgrijalva/jwt-go"
)
// 初始化函数
func init() {
// 实例化计数计时器
counter = NewCounter()
// 初始化随机 Key
key, err := generateRandomString(16)
if err != nil {
log.Fatal(err)
}
// 打印时间戳
fmt.Println("Start At timeStamp :", timeStamp, time.Unix(timeStamp, 0), key)
}
// Counter 定义一个简单的计数器
type Counter struct {
value int
timeStampc int64 // 时间戳,用于定期更新密钥key
}
// 计时器变量实例
var counter *Counter
// NewCounter 创建一个新的计数器
func NewCounter() *Counter {
return &Counter{value: 0, timeStampc: time.Now().Unix()}
}
// Increment 增加计数器的值
func (c *Counter) Increment() {
c.value++
c.timeStampc = time.Now().Unix()
}
// // Decrement 减少计数器的值
// func (c *Counter) Decrement() {
// c.value--
// }
// // Reset 重置计数器的值为0
// func (c *Counter) Reset() {
// c.value = 0
// }
// GetValue 返回计数器的当前值
func (c *Counter) GetCounter() *Counter {
return c
}
//
// 定义Token的Claims
type customClaims struct {
Userid string `json:"userid"`
Passwd string `json:"passwd"`
jwt.StandardClaims
}
// 定义Token相关变量
var (
uid string = "BLMa" //用户名
pwd string = "5217" //密码
key string = "token" //默认密钥,服务启动时会修改
iss string = "guwuy" //签发者
timeStamp int64 = time.Now().Unix() // 时间戳,用于定期更新密钥key
timeSecond int64 = 20 //60 * 60 * 24 * 7 //一周时间的秒数,用于7天修改一次Key
timeExpires time.Duration = 60 * 60 * 8 // Token 过期时间 秒数,8小时
)
// generateRandomString 生成一个指定长度的随机字符串
func generateRandomString(length int) (string, error) {
const letters = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
bytes := make([]byte, length)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
for i, b := range bytes {
bytes[i] = letters[b%byte(len(letters))]
}
return string(bytes), nil
}
// 定期生成随机Key
func generateRandomKey() {
//当前时间戳
timestamp := counter.GetCounter().timeStampc
//fmt.Println("Now timeStamp,timestamp:", timeStamp, timestamp)
if timestamp-timeStamp > timeSecond {
counter.Increment()
// 修改Key
key, err := generateRandomString(16)
if err != nil {
log.Fatal(err)
}
timeStamp = timestamp // 更新Key修改的时间戳
// 打印时间戳
fmt.Println("Now timeStamp :", timeStamp, time.Unix(timeStamp, 0), key)
}
}
// 生成新的Token
func generateToken(userId string) (string, error) {
// 计数器,计时器
counter.Increment()
// 设置Claims
claims := customClaims{
Userid: userId,
Passwd: pwd,
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Second * timeExpires).Unix(), // 设置过期时间
Issuer: iss, // 设置签发者
},
}
// 创建Token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 定期生成随机Key
generateRandomKey()
// 签名Token,这里使用硬编码的密钥,实际生产环境中应使用更安全的密钥管理方式
signedToken, err := token.SignedString([]byte(key))
if err != nil {
return "", err
}
return signedToken, nil
}
// 验证Token
func validateToken(tokenString string) (*customClaims, error) {
// 解析Token
token, err := jwt.ParseWithClaims(tokenString, &customClaims{}, func(token *jwt.Token) (interface{}, error) {
// 验证Token的签名,这里使用硬编码的密钥
return []byte(key), nil
})
if claims, ok := token.Claims.(*customClaims); ok && token.Valid {
return claims, nil
}
return nil, err
}
// HTTP处理函数:生成Token
func GenerateTokenHandler(w http.ResponseWriter, uid_pwd map[string]string) {
//请求参数,实际情况下,这里可能从请求参数或身份验证过程中获取
uid = uid_pwd["Userid"]
pwd = uid_pwd["Passwd"]
// 这里加uid,pwd的数据库校验
//fmt.Println(uid, pwd)
token, err := generateToken(uid)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"token": token,
})
}
// HTTP处理函数:验证Token
func ValidateTokenHandler(w http.ResponseWriter, tokenString string) {
claims, err := validateToken(tokenString)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"userid": claims.Userid,
"expires": claims.ExpiresAt,
})
}
// // main入口
// func main() {
// // 检查并生成Key
// GenerateRandomKey()
// // Token 路由
// http.HandleFunc("/generate-token", generateTokenHandler)
// // Http://localhost:8080/generate-token?userid=blma&passwd=5217
// // curl Http://localhost:8080/generate-token?userid=blma%26passwd=5217
// http.HandleFunc("/validate-token", validateTokenHandler)
// //curl http://localhost:8080/validate-token -H "Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyaWQiOiI5OTk4IiwicGFzc3dkIjoiODk5OSIsImV4cCI6MTcwOTcyMDU0MSwiaXNzIjoiZ3V3dXkifQ.UXiW-cgnDZfGUmLtv_yme6gzFZ9XDiKaNATIdFzJ2fY"
// fmt.Println("Starting Token server ...")
// fmt.Println("Http://localhost:8080/generate-token?userid=&passwd=")
// fmt.Println("curl http://localhost:8080/validate-token -H \"Authorization:token\"")
// log.Fatal(http.ListenAndServe(":8080", nil))
// }
3 rest2sql.go的 handler() 增加token访问路由
重构的核心代码如下:
- 请求路径错误提示代码:
// 2请求路径Path
req["Path"] = r.URL.Path
path := strings.Split(r.URL.Path, "/")
if len(path) < 3 {
w.Write([]byte("400 Bad Request错误请求。请尝试/rest/xxx or /sql/xxx or /TOKEN/xxx"))
return
}
- 允许的请求路径代码:
// 3 请求类型REST or SQL or Token
rors := strings.ToUpper(fmt.Sprint(path[1]))
// 支持的请求类型
if !(rors == "REST" || rors == "SQL" || rors == "TOKEN") {
w.Write([]byte("400 Bad Request错误请求。请尝试/REST/xxx or /SQL/xxx or /TOKEN/xxx"))
return
}
- 请求头token结构代码:
// 8 请求头 Authorization
req["Authorization"] = r.Header.Get("Authorization") // 假设Token在Authorization头中
- 请求参数增加userid和passw代码:
// 9 请求参数
query := r.URL.Query()
req["Userid"] = query.Get("userid") // 登录用户
req["Passwd"] = query.Get("passwd") // 登录密码
3 dothing.go代码重构
分支代码:
case "TOKEN":
// Token 生成与校验
doTOKEN(w, req)
- doTiken函数代码:
// 根据请求参数执行不同的TOKEN操作 ///
func doTOKEN(w http.ResponseWriter, req map[string]interface{}) {
// token操作, generate or validate
resToken := strings.ToLower(req["ResName"].(string))
switch resToken {
case "generate-token":
// w.Write([]byte("generate-token"))
var uid_pwd map[string]string = make(map[string]string)
uid_pwd["Userid"] = req["Userid"].(string)
uid_pwd["Passwd"] = req["Passwd"].(string)
token.GenerateTokenHandler(w, uid_pwd)
// http://127.0.0.1:5217/TOKEN/generate-token?userid=9998&passwd=8999
case "validate-token":
//w.Write([]byte("validate-token"))
var tokenString string = req["Authorization"].(string)
token.ValidateTokenHandler(w, tokenString)
// curl http://localhost:5217/token/validate-token -H "Authorization:token"
}
}
4 实操演练
Setp 1 启动服务
Step 2 生成Token
http://127.0.0.1:5217/TOKEN/generate-token?userid=9998&passwd=8999
Step 3 验证Token
curl http://localhost:5217/token/validate-token -H "Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MDk4Njk0OTEsImlzcyI6Imd1d3V5In0.7CLaQKNXZOhnirLfOb_1meYYnc6KVDkXUhrxbfvYgKw"