在 Go 中使用项目结构优化的 RESTful API 服务运行

444 阅读6分钟

Running with a project structure optimized RESTful API service in Go (#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中创建一个新的环境,并声明你要使用的变量,你就会使一切都变得清晰。

Running with a project structure optimized RESTful API service in Go

你还应该在一些请求的测试部分添加一组代码,用请求头的承载令牌自动安排。(signup,login,getme,update,logout )在代码之前,你应该在编辑集合的授权部分定义一个名为authToken 的环境令牌。

Running with a project structure optimized RESTful API service in Go

然后将代码粘贴到signup,login,getme,update,logout 请求中。

if (pm.response.code == 200){
    pm.environment.set('authToken',pm.response.json()["Token"])
}

Running with a project structure optimized RESTful API service in Go

之后,你的请求头将自动更新为有效的令牌以进行认证。

你必须确保你选择的环境

Running with a project structure optimized RESTful API service in Go

(#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)数据库方案

Running with a project structure optimized RESTful API service in Go

GitHub

github.com/Kivanc10/go…