概述

JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑而独立的方法,用于在各方之间安全地将信息作为JSON对象传输。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用HMAC算法)或使用RSAECDSA的公钥/私钥对对JWT进行签名。

什么时候应该使用JWT

  • 授权:这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。单一登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。

  • 信息交换:JSON Web令牌是在各方之间安全地传输信息的一种好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否未被篡改。

为什么要使用JSON Web令牌

由于JSON不如XML冗长,因此在编码时JSON的大小也较小,从而使JWTSAML更为紧凑。这使得JWT是在HTMLHTTP环境中传递的不错的选择。

在安全方面,只能使用HMAC算法由共享机密对SWT进行对称签名。但是,JWTSAML令牌可以使用X.509证书形式的公用/专用密钥对进行签名。与签署JSON的简单性相比,使用XML Digital Signature签署XML而不引入模糊的安全漏洞是非常困难的。

JSON解析器在大多数编程语言中都很常见,因为它们直接映射到对象。相反,XML没有自然的文档到对象的映射。与SAML断言相比,这使使用JWT更加容易。

关于用法,JWT是在Internet规模上使用的。这突显了在多个平台(尤其是移动平台)上对JSON Web令牌进行客户端处理的简便性。

Tip 以上内容全部来自jwt官网介绍

go-zero中怎么使用jwt

jwt鉴权一般在api层使用,我们这次演示工程中分别在user api登录时生成jwt token,在search api查询图书时验证用户jwt token两步来实现。

user api生成jwt token

接着业务编码章节的内容,我们完善上一节遗留的getJwtToken方法,即生成jwt token逻辑

添加配置定义和yaml配置项

  1. $ vim service/user/cmd/api/internal/config/config.go
  1. type Config struct {
  2. rest.RestConf
  3. Mysql struct{
  4. DataSource string
  5. }
  6. CacheRedis cache.CacheConf
  7. Auth struct {
  8. AccessSecret string
  9. AccessExpire int64
  10. }
  11. }
  1. Name: user-api
  2. Host: 0.0.0.0
  3. Port: 8888
  4. Mysql:
  5. DataSource: $user:$password@tcp($url)/$db?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
  6. CacheRedis:
  7. - Host: $host
  8. Pass: $pass
  9. Type: node
  10. Auth:
  11. AccessSecret: $AccessSecret
  12. AccessExpire: $AccessExpire

Tip $AccessSecret:生成jwt token的密钥,最简单的方式可以使用一个uuid值。 $AccessExpire:jwt token有效期,单位:秒 更多配置信息,请参考api配置介绍

  1. $ vim service/user/cmd/api/internal/logic/loginlogic.go
  1. func (l *LoginLogic) getJwtToken(secretKey string, iat, seconds, userId int64) (string, error) {
  2. claims := make(jwt.MapClaims)
  3. claims["exp"] = iat + seconds
  4. claims["iat"] = iat
  5. claims["userId"] = userId
  6. token := jwt.New(jwt.SigningMethodHS256)
  7. token.Claims = claims
  8. return token.SignedString([]byte(secretKey))
  9. }

search api使用jwt token鉴权

编写search.api文件

  1. $ vim service/search/cmd/api/search.api
  1. type (
  2. SearchReq {
  3. // 图书名称
  4. Name string `form:"name"`
  5. }
  6. SearchReply {
  7. Name string `json:"name"`
  8. Count int `json:"count"`
  9. }
  10. )
  11. @server(
  12. jwt: Auth
  13. )
  14. service search-api {
  15. @handler search
  16. get /search/do (SearchReq) returns (SearchReply)
  17. }
  18. service search-api {
  19. @handler ping
  20. get /search/ping
  21. }

Tip jwt: Auth:开启jwt鉴权 如果路由需要jwt鉴权,则需要在service上方声明此语法标志,如上文中的/search/do 不需要jwt鉴权的路由就无需声明,如上文中/search/ping 更多语法请阅读api语法介绍

生成代码

前面已经描述过有三种方式去生成代码,这里就不赘述了。

添加yaml配置项

  1. $ vim service/search/cmd/api/etc/search-api.yaml
  1. Name: search-api
  2. Host: 0.0.0.0
  3. Port: 8889
  4. Auth:
  5. AccessSecret: $AccessSecret
  6. AccessExpire: $AccessExpire

Tip $AccessSecret:这个值必须要和user api中声明的一致。 $AccessExpire: 有效期 这里修改一下端口,避免和user api端口8888冲突

验证 jwt token

  • 启动user api服务,登录
    1. $ cd service/user/cmd/api
    2. $ go run user.go -f etc/user-api.yaml
    1. Starting server at 0.0.0.0:8888...
    image.png
    1. $ curl -i -X POST \
    2. http://127.0.0.1:8888/user/login \
    3. -H 'content-type: application/json' \
    4. -d '{
    5. "username":"666",
    6. "password":"123456"
    7. }'
    ```http HTTP/1.1 200 OK Content-Type: application/json Date: Mon, 08 Feb 2021 10:37:54 GMT Content-Length: 251

{ “id”: 1, “name”: “小明”, “gender”: “男”, “accessToken”: “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTI4NjcwNzQsImlhdCI6MTYxMjc4MDY3NCwidXNlcklkIjoxfQ.JKa83g9BlEW84IiCXFGwP2aSd0xF3tMnxrOzVebbt80”, “accessExpire”: 1612867074, “refreshAfter”: 1612823874 }

  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/161237/1625898991665-18096122-f651-4ecb-bddc-862b6b7f9acb.png#align=left&display=inline&height=690&id=u0a85471f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=690&originWidth=1069&size=71650&status=done&style=none&width=1069)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/161237/1625899088262-d700f724-2fa6-4b7d-8f44-bcbb18e8f281.png#align=left&display=inline&height=403&id=ucf32322c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=403&originWidth=1436&size=59655&status=done&style=none&width=1436)
  2. - 启动**user rpc**服务
  3. ```go
  4. PS D:\Projects\Github\NoobWu\go-zero-demo\book\service\user\cmd\rpc> go run .\user.go -f .\etc\user.yaml

image.png

  • 启动search api服务,调用/search/do验证jwt鉴权是否通过

    1. cd service/search/cmd/api
    2. PS D:\Projects\Github\NoobWu\go-zero-demo\book\service\search\cmd\api> go run search.go -f etc/search-api.yaml

    image.png

  • 我们先不传jwt token,看看结果

    1. $ curl -i -X GET \
    2. 'http://127.0.0.1:8089/search/do?name=go-zero'

    ```http GET http://127.0.0.1:8089/search/do?name=go-zero 401 40 ms GET /search/do?name=go-zero HTTP/1.1 User-Agent: PostmanRuntime/7.28.1 Accept: / Cache-Control: no-cache Postman-Token: 08ae164a-1b08-4603-8983-2148f604879c Host: 127.0.0.1:8089 Accept-Encoding: gzip, deflate, br Connection: keep-alive

HTTP/1.1 401 Unauthorized Date: Sat, 10 Jul 2021 09:19:37 GMT Content-Length:

  1. 很明显,**jwt**鉴权失败了,返回**401**的**statusCode**,接下来我们带一下**jwt token**(即用户登录返回的**accessToken**)
  2. ```bash
  3. $ curl -i -X GET \
  4. 'http://127.0.0.1:8889/search/do?name=go-zero' \
  5. -H 'authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjU5OTU0NTEsImlhdCI6MTYyNTkwOTA1MSwidXNlcklkIjoxfQ.CXMXzF6Dok4X-BiBPC_dq5h6ivDOMGVNkln6VR-5Dl0'
  1. HTTP/1.1 200 OK
  2. Content-Type: application/json
  3. Date: Sat, 10 Jul 2021 09:35:41 GMT
  4. Content-Length: 30
  5. {"name":"go-zero","count":100}

Tip 服务启动错误,请查看常见错误处理

至此,jwt从生成到使用就演示完成了,jwt token的鉴权是go-zero内部已经封装了,你只需在api文件中定义服务时简单的声明一下即可。

获取jwt token中携带的信息

go-zerojwt token解析后会将用户生成token时传入的kv原封不动的放在http.RequestContext中,因此我们可以通过Context就可以拿到你想要的值

  1. $ vim /service/search/cmd/api/internal/logic/searchlogic.go

添加一个log来输出从jwt解析出来的userId

  1. func (l *SearchLogic) Search(req types.SearchReq) (*types.SearchReply, error) {
  2. logx.Infof("userId: %v",l.ctx.Value("userId"))// 这里的key和生成jwt token时传入的key一致
  3. return &types.SearchReply{}, nil
  4. }

运行结果

  1. {"@timestamp":"2021-02-09T10:29:09.399+08","level":"info","content":"userId: 1"}

猜你想看

原文链接

https://go-zero.dev/cn/jwt.html