在当今的互联网时代,我们的服务不仅要面向浏览器,还要面向各种客户端,如移动设备、桌面应用、物联网设备等。为了让不同的客户端能够方便地访问我们的服务,我们需要提供一种统一的接口,让客户端能够通过网络请求获取或修改我们的数据。这种接口就是API,即应用程序编程接口。
Restful API
API有很多种风格,如SOAP、RPC、GraphQL等,但最流行的一种是Restful API。Restful API是一种基于HTTP协议的API风格,它遵循REST原则,即表述性状态转移。REST原则的核心思想是,每个资源都有一个唯一的标识符(URI),客户端可以通过HTTP方法(GET、POST、PUT、DELETE等)对资源进行增删改查操作,服务器返回资源的表述(Representation),通常是JSON或XML格式。这样,客户端和服务器之间就形成了一种统一、无状态、可缓存的接口。
API接口设计准则
为了让我们的API更加易用、可维护和可扩展,我们需要遵循一些设计准则,例如:
- 使用名词而不是动词来表示资源,如
/users而不是/getUsers - 使用复数而不是单数来表示资源集合,如
/users而不是/user - 使用HTTP方法来表示对资源的操作,如
GET /users表示获取所有用户,POST /users表示创建一个新用户,PUT /users/:id表示更新一个指定用户,DELETE /users/:id表示删除一个指定用户 - 使用HTTP状态码来表示请求的结果,如200表示成功,400表示客户端错误,500表示服务器错误
- 使用查询参数来过滤、排序、分页等资源,如
GET /users?name=alice&age=20&sort=asc&page=1&size=10 - 使用路径参数来表示资源之间的关系,如
GET /users/:id/posts表示获取一个指定用户的所有文章 - 使用请求头来传递元数据,如
Content-Type表示请求体的格式,Authorization表示用户认证信息 - 使用响应头来传递元数据,如
Content-Type表示响应体的格式,Location表示创建或更新资源后的URI - 使用响应体来传递资源的表述,如JSON或XML格式
用户认证简介
当我们提供API给客户端时,我们可能需要限制某些资源或操作只能由特定的用户访问。例如,我们可能只允许用户查看或修改自己的个人信息,或者只允许管理员执行某些敏感操作。为了实现这种功能,我们需要对用户进行认证(Authentication)和授权(Authorization)。认证是指验证用户的身份,授权是指验证用户是否有权限访问某个资源或执行某个操作。
用户认证有很多种策略,如基本认证(Basic Authentication)、摘要认证(Digest Authentication)、令牌认证(Token Authentication)、OAuth、OpenID Connect等。这里我们只介绍一种比较简单而又常用的策略,即令牌认证。
令牌认证的原理是,用户在登录时,服务器验证用户的用户名和密码,如果正确,就生成一个令牌(Token),并返回给客户端。客户端在后续的请求中,将令牌放在请求头的Authorization字段中,服务器根据令牌来验证用户的身份和权限。令牌通常有一定的有效期,过期后需要重新登录或刷新。令牌可以是任意的字符串,但通常使用一种叫做JWT(JSON Web Token)的格式,它包含了用户的信息和签名,可以防止被篡改。
代码示例
然后,我们需要定义一个User结构体来表示用户:
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Password string `json:"password"`
}
接着,我们需要定义一个全局的用户列表来模拟数据库:
var users = []User{
{ID: 1, Username: "alice", Password: "123456"},
{ID: 2, Username: "bob", Password: "654321"},
}
然后,我们需要定义一个函数来生成JWT令牌:
import (
"github.com/dgrijalva/jwt-go"
)
var secretKey = []byte("secret")
func generateToken(user User) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"id": user.ID,
"username": user.Username,
})
return token.SignedString(secretKey)
}
接着,我们需要定义一个函数来解析JWT令牌:
func parseToken(tokenString string) (*User, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
id := int(claims["id"].(float64))
username := claims["username"].(string)
return &User{ID: id, Username: username}, nil
}
return nil, err
}
然后,我们需要定义一个中间件来验证用户的令牌:
func authMiddleware(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "no authorization header"})
return
}
user, err := parseToken(authHeader)
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
return
}
c.Set("user", user)
c.Next()
}
处理用户登录:
func loginHandler(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
for _, u := range users {
if u.Username == user.Username && u.Password == user.Password {
token, err := generateToken(u)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"token": token})
return
}
}
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid username or password"})
}