将服务开放给用户——构建API与认证用户 | 青训营

60 阅读4分钟

本文使用字节跳动开发的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需要携带一定的用户信息。

目前有几种方法可以达到该效果:

  1. 将用户信息明文放入Token中。这种方式简单,但是Token可能会泄露,造成用户信息暴露。
  2. 对用户信息加密后放入Token。这种方式更安全一些,但是增加了解密的计算负载,同时密钥可能需要更新。
  3. 在服务端保存UserID与Token的映射。这种方式最安全,不会暴露用户真实信息。每次请求只需要带上token,服务端用token获取对应的userid即可。缺点是需要服务端保存映射表,增大存储和查找成本。
  4. 将用户信息加密作为claims,放入JWT token。JWT可以对信息进行签名,一定程度上防止篡改。但仍然面临加密密钥的保护问题。
  5. 使用会话机制,在服务端存储会话和用户映射,发放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
}

完事。