本文使用字节跳动开发的Hertz框架构建API,以及jwt进行用户认证。
API是什么?对于我们而言,API就是http链接后面那一串(就是域名后面)。对于一个框架而言,它是拼接了参数的路由。在这里我将用框架去构建API。
构建API
有两种接口描述语言,一种叫protobuf,一种叫thrift(有没有第三种我就不知道了)。这两种我都用过,感觉使用起来后者更好一些,而且事实证明,对后者对Hertz和Kitex的支持更好(因为我使用protobuf去编译的时候,程序无法编译,原因是将指针赋值给整数。哪位大佬有时间的话可以去修复一下)。
下面是一段thrift的示例(它在idl/http/这个文件夹里),我也把它用在了本次大项目中(也亏得我一番protobuf转thrift)。
// 抖音视频流服务
namespace go feed
include "../base/http.thrift"
// 视频流请求
struct douyin_feed_request {
1: i64 latest_time // 可选参数,限制返回视频的最新投稿时间戳,精确到秒,不填表示当前时间
2: string token // 可选参数,登录用户设置
}
// 视频流响应
struct douyin_feed_response {
1: i32 status_code // 状态码,0-成功,其他值-失败
2: string status_msg // 返回状态描述
3: list<http.Video> video_list // 视频列表
4: i64 next_time // 本次返回的视频中,发布最早的时间,作为下次请求时的latest_time
}
service FeedService {
// 获取视频流
douyin_feed_response Feed(1: douyin_feed_request request) (api.get = "/douyin/feed")
}
代码中,namespace规定了编译后该接口的包名。实际上上面这份代码编译后到了douyin/cmd/api/biz/handler/feed/feed_service.go
如果你直接拿着去编译的话,大概率是不能够成功的。里面有<http.Video>,这表示了另一个对象,它在相对于这个文件的"../base/http.thrift"文件(include那一行)
使用下面这段shell进行编译:
kitex -I idl/http idl/http/feed.thrift
编译后,你的目录是这样子的:
.
├╴router_gen.go
├╴output
│ ├╴bootstrap.sh
│ ╰╴bin
│ ╰╴hertz_service
├╴.gitignore
├╴build.sh
├╴main.go
├╴.hz
├╴script
│ ╰╴bootstrap.sh
├╴router.go
├╴biz
│ ├╴handler
│ │ ╰╴feed
│ │ ╰╴ feed_service.go
│ ├╴router
│ │ ╰╴feed
│ │ ├╴feed.go
│ │ ╰╴middleware.go
│ ╰╴model
│ ├╴base
│ │ ╰╴http.go
│ ╰╴feed
│ ╰╴feed.go
╰╴idl
├╴base
│ ╰╴http.thrift
╰╴http
╰╴feed.thrift
你要写的东西,是biz/handler/feed/feed_service.go。如果你要使用中间件,那么你还需要写biz/router/feed/middleware.go。后面我会用上它。简单写下handler,接下来就是进行认证了。
使用JWT认证用户
JWT(JSON Web Token)是一种开放的行业标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于在各方之间安全地传输信息。使用密码、过期日期,就可以用它转发加密后的信息了,它还能够解密加密后的东西。
整个青训营大项目,有些地方只有一个token而没有user_id,所以token需要携带一定的用户信息。
目前有几种方法可以达到该效果:
- 将用户信息明文放入Token中。这种方式简单,但是Token可能会泄露,造成用户信息暴露。
- 对用户信息加密后放入Token。这种方式更安全一些,但是增加了解密的计算负载,同时密钥可能需要更新。
- 在服务端保存UserID与Token的映射。这种方式最安全,不会暴露用户真实信息。每次请求只需要带上token,服务端用token获取对应的userid即可。缺点是需要服务端保存映射表,增大存储和查找成本。
- 将用户信息加密作为claims,放入JWT token。JWT可以对信息进行签名,一定程度上防止篡改。但仍然面临加密密钥的保护问题。
- 使用会话机制,在服务端存储会话和用户映射,发放sessionId。这样请求只需要sessionId,不需要直接携带用户信息。实现简单,但需要服务端存储会话状态。这个我们的客户端没有,所以绝对不行。
所以,整体看下来,还是JWT合适一些。但,怎么使用?
使用go get安装好"github.com/golang-jwt/jwt” 接下来就给定密码
jwtSecret = []byte("cxk"),指定结构体:
type Claims struct {
Id int64 `json:"id"`
jwt.StandardClaims
}
然后就可以生成token了
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, Claims{
Id: 用户id,
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Hour).Unix(),
Issuer: "1122233",
},
})
token, err := tokenClaims.SignedString(jwtSecret)
然后是解密
token, err := jwt.ParseWithClaims(
tokenString, &Claims{},
func(token *jwt.Token) (i interface{}, e error) {
return jwtSecret, nil
})
检查一下err的值是否是nil,再看看这个token还能不能用
claims, ok := token.Claims.(*Claims);
if !ok || !token.Valid {
return nil, err
}
完事。