(#restful-api-in-go)GO中的RESTful API
这是一个中级的Go项目,在Go中运行的项目结构优化了RESTful API服务。该项目中的API是基于坚实和通用的原则设计的,并连接到本地MySQL数据库。
这个项目的亮点如下
- RESTful API展示了数据库表的标准CRUD操作。
- 该项目具有干净的架构,并且已经覆盖了测试。
- 数据验证
- 它使用基于JWT的认证和认证中间件。
- 每个令牌在15分钟内过期,以防止系统错误。
- 错误处理是通过清晰的响应完成的。
- 在JWT创建的令牌的帮助下,它提出了用户名和密码的结构化日志,持续15分钟。之后,用户可以创建TODO。
- 该项目在开发过程中使用了以下软件包
(#getting-started)开始使用
如果你以前没有接触过Go,你应该访问这个网站。
安装完Go后,你应该运行以下命令来体验这个项目
# download the starter code
git clone https://github.com/Kivanc10/golang-rest-api-with-mysql.git
# open the code
cd golang-rest-api-with-mysql
# start the database server and run the code
go run ./operate/operate.go
之后,你就有了一个运行在http://127.0.0.1:8080 的RESTful API。它为我们提供了以下端点
-
GET /users:它为我们提供了所有登录用户的列表。 -
POST /signUp:它允许用户注册。它将用户信息保存在数据库中,并使用JWT创建令牌。它接受这样的附加数据。PersonID可以是任何东西,因为数据库安排了自动增量。-
{ "PersonID":0, "UserName":"sample user name", "Password":"12312321" }
``` -
POST /signIn:验证和登录。它用JWT再次创建令牌。它接受附加数据,就像上面所说的。 -
GET /users/me:它允许用户访问他的信息。要做到这一点,用户必须经过认证,否则系统将不会让它发生。 -
PUT /users/update/me: 它用接受的数据更新当前的认证用户。要做到这一点,用户必须经过认证。它接受的附件数据如下。{ "PersonID":0, "UserName":"new user name", "Password":"new password" }
``` -
-
DELETE /user/me:它删除了当前的认证用户。要做到这一点,用户必须经过认证。 -
GET /users/logout/me:它允许用户注销所有令牌。要做到这一点,用户必须经过认证。该用户将不会从数据库中删除。 -
POST /todo: 它允许用户创建todos。要做到这一点,用户必须经过认证。它接受这样的附件数据。-
{ "Context" : "sample todo"
}
``` -
-
GET /todos: 它列出了所有由认证用户创建的todos。 -
GET /todos/me:它只列出属于当前认证用户的todos。
(#if-you-have-api-client-tools-like-postman-you-can-handle-complicated-operations-easily)如果你有API客户端工具,如Postman ,你可以轻松处理复杂的操作。
如果你在Postman中创建一个新的环境,并声明你要使用的变量,你就会使一切都变得清晰。
你还应该在一些请求的测试部分添加一组代码,用请求头的承载令牌自动安排。(signup,login,getme,update,logout )在代码之前,你应该在编辑集合的授权部分定义一个名为authToken 的环境令牌。
然后将代码粘贴到signup,login,getme,update,logout 请求中。
if (pm.response.code == 200){
pm.environment.set('authToken',pm.response.json()["Token"])
}
之后,你的请求头将自动更新为有效的令牌以进行认证。
你必须确保你选择的环境
(#how-to-use-endpoints)如何使用端点
# sign up the user via POST /signUp
curl -X POST -H "Content-Type: application/json" -d `{ "PersonID":0,"UserName":"sample user name","Password":"12312321"}` http://localhost:8080/signUp
# it should return response.header with jwt and token
# sign in with user via POST /signIn
curl -X POST -H "Content-Type: application/json" -d `{ "PersonID":0,"UserName":"sample user name","Password":"12312321"}` http://localhost:8080/signIn
# it should return response.Header with jwt and token
# save token during the loggedin and inherit auth from postman environment,it handles itself
curl -X GET -H "Authorization: Bearer ...JWT token here..." http://localhost:8080/users/me
# to create todos for the user authenticated
curl -X POST -H "Authorization: Bearer ...JWT token here..." -d `{"Context" : "sample todo"}` http://localhost:8080/todo
# it returns the saved todo belong to the user authenticated
(#project-layout)项目布局
.
├── dbOp main database operations of the project
├── middleware auth middleware and tokens will create
├── operate main application of the project
├── route routes operations of the project related to RESTful api functions
└── static static files belong to the project
(#working-with-jwt-based-authenticaton-and-auth-middleware)与基于JWT的认证和认证中间件一起工作
(#jwt)JWT
- JSON Web Token (JWT)是一种独立的方式,以JSON对象的形式在各方之间安全地传输信息。如果你将处理授权和信息交换,你最合理的做法就是使用JWT。
(#auth-middleware)认证中间件
- 我们已经有了认证服务和它的适配器以及登录中间件,我们可以创建中间件来检查已认证的用户,如果用户没有被认证,它可以重定向到/登录页面。
在该项目中,如果有必要,我们创建令牌,如signUp,signIn,... ,以进行和保存更改。然后我们使用Auth中间件来访问当前用户,如果其令牌是有效的。auth中间件使我们能够设计一个真正的安全系统。
(#to-create-tokens)创建令牌
", atClaims)
return token, nil // return token created and no error if succeed
}
">
func CreateToken(userId uint64, name string) (string, error) { // it accepts userId and username
/* This function creates a token belong to the user with a set of information. The created token will be expired in 15 minutes */
var err error
//Creating Access Token
os.Setenv("ACCESS_SECRET", mySignInKey) // define a global access key
atClaims := jwt.MapClaims{} // create empty map to store keys-values belong to the user
# store infos into the map
atClaims["authorized"] = true
atClaims["user_id"] = userId
atClaims["user_name"] = name
atClaims["exp"] = time.Now().Add(time.Minute * 15).Unix() // token is valid for 15 minutes
at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) // use HS256 algorithm
token, err := at.SignedString([]byte(os.Getenv("ACCESS_SECRET")))
if err != nil { // error handling
return "", errors.New("an error occured during the create token")
}
fmt.Println("jwt map --> ", atClaims)
return token, nil // return token created and no error if succeed
(#to-integrate-auth-middleware)整合认证中间件
%s and len -> %d/n", authHeader, len(authHeader))
if len(authHeader) != 2 || authHeader[0] == "null" { // ["Bearer", "Token..."],如果不是这样,就有一个错误
//fmt.Println("Malformed token")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Malformed Token"))
log.Fatal("Malformed token")
}
jwtToken := authHeader[1] //获得
令牌
token, err := jwt.
Parse(jwtToken)
Parse(jwtToken, func(token *jwt.Token) (interface{}, error) { // parse the token
if _, ok := token.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return []byte(mySignInKey), nil
})
// 用jwt.MapClaims进行类型转换,检查令牌是否有效
if claims, ok := token.(jwt.MapClaims); ok && token.Valid { // 如果该令牌有效
ctx := context.WithValue(r.Context(), "props", claims) // props是上下文关键字
// 在处理程序中访问上下文值
next.ServeHTTP(w, r.WithContext(ctx
))
// 如果成功,就用上下文服务http
} else { // 如果有错误
fmt.Println("token err -> ", err)
//r.Header.Set("ExpiredToken", jwtToken)
//DelTokenIfExpired(jwtToken)
// usernameInter := claims["user_name"]
// if username, ok := usernameInter.Stringer); ok {
// person := dbop.GetPersonToDelToken(username.String())
// dbop.DeleteTokenIfExpired(person
) // }
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("you are Unauthorized or your token is expired") ) }
}
">
func MiddleWare(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := strings.Split(r.Header.Get("Authorization"), "Bearer ") // split request header acc. to Bearer
fmt.Printf("authheader -> %s and len -> %d\n", authHeader, len(authHeader))
if len(authHeader) != 2 || authHeader[0] == "null" { // ["Bearer ","Token..."],if it is not like that,there is an error there
//fmt.Println("Malformed token")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Malformed Token"))
log.Fatal("Malformed token")
}
jwtToken := authHeader[1] // get the token
token, err := jwt.Parse(jwtToken, func(token *jwt.Token) (interface{}, error) { // parse the token
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return []byte(mySignInKey), nil
})
// type conversion with jwt.MapClaims and to check the token is valid
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { // if that token is valid
ctx := context.WithValue(r.Context(), "props", claims) // props is context key
// Access context values in handlers like this
next.ServeHTTP(w, r.WithContext(ctx)) // if succeed serve http with context
} else { // if there is an error
fmt.Println("token err -> ", err)
//r.Header.Set("ExpiredToken", jwtToken)
//DelTokenIfExpired(jwtToken)
// usernameInter := claims["user_name"]
// if username, ok := usernameInter.(fmt.Stringer); ok {
// person := dbop.GetPersonToDelToken(username.String())
// dbop.DeleteTokenIfExpired(person)
// }
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("you are Unauthorized or your token is expired"))
}
}
})
}
(#database-scheme)数据库方案




