之前的文章里,我们创建了一个用户服务,保存了一些用户。现在我们看下如何在用户服务中安全的保存用户密码,同时,通过 微服务 写几个功能,来验证用户,发布安全令牌。
注意,我现在已经把我们的服务拆分到几个不同的仓库里。 我觉得这样部署起来容易些。 最开始我打算做成一个单独的仓库,但是发现用 Go 的 dep 管理有点麻烦,有很多冲突。我也会说明下如何独立地运行和测试微服务。
遗憾的是,用这种方法我们就不能用 Docker -compose 了。 不过目前用起来还不错。如果你在这方面有什么建议,可以 给我发邮件 !
现在你要手动启动数据库:
最新的仓库地址在下面:
首先,我们要更新下 handler 文件,做密码哈希,这是非常必要的。绝对不能,也坚决不要使用明文密码。你们可能会想 ‘废话,那还用说么’ ,但我还要坚持强调下!
这里没做太多改动,除了增加了密码哈希功能,我们在保存新用户之前把哈希后的内容作为新的密码。同样的,在认证部分,我们会校验密码的哈希值。
现在我们能够安全的认证数据库里的用户信息,我们需要一个机制,能使用接口和 分布式 服务来做认证。实现这样的功能有许多方法,但是我发现,能通过服务和 web 使用的最简单的认证方法是用 JWT 。
不过在我们继续下面的内容之前,请查看下我在每个服务中的 Dockfiles 和 Makefiles 做的修改。为了匹配最新的 git 仓库,我也修改了 imports 。
JWT
JWT 是 JSON web tokens 的缩写,是一个分布式的安全协议。类似 OAuth。 概念很简单,用算法给用户生成一个唯一的哈希,这个哈希值可以用来比较和校验。不仅如此,token 自身也会包含用户的 元数据 (metadata)。也就是说,用户数据本身也可以是 token 的一部分。 我们看一个 JWT 的实例:
token 被 ‘.’ 分成三部分。每个部分都有各自的含义。第一部分是 token 自身的元数据,像 token 的类型,创建 token 使用的算法等。让客户端能知道怎么给 token 解码。第二部分是用户自定义的元数据。可以是你的用户详情,过期时间,任何你想填的内容。最后一个部分是认证签名,包括用什么方法,什么数据给 token 做哈希这类信息。
当然用 JWT 也有缺点和使用风险,这些缺点在 这篇文章 中总结的很好。同时,我推荐大家读 这篇文章 _Cheat_Sheet_for_Java),这里有安全层面上的最佳实践方法。
有一点我想要大家特别注意下,就是获取用户的源 IP,并用它作为令牌声明的一部分。这样可以确保没有人能盗取你的令牌,并在另一台机器上伪装成你。确保使用 https,减少这种类型的攻击,因为使用 https 能保护你的 token,免受中间人攻击。
用来做 JWT 哈希的算法有很多,大体分为两类。对称加密和非对称加密。对称加密类似我们现在用的方式,使用共享盐值 (salt)。非对称加密会在客户端和服务端分别使用公钥和私钥。用来在多个服务之间做认证是极好的。
更多资源:
现在我们知道 JWT 的基本概念了。我们更新下 token_service.go 实现这些操作。我们可以用这个 Go 的库 github.com/dgrijalva/jwt-go ,这个库写的很棒,有很多不错的实例。
照常,我写了一些注释解释了部分细节,不过这里的导言写的比较简单。Decode 方法,是将一个 字符串 token 解析成一个 token 对象,验证下,如果有效,则返回一个 CustomClaims 对象。这样我们能通过这个 CustomClaims 对象获取到用户的元数据来确认该用户是否有效。
Encode 方法,做的正好相反,将你的自定义的元数据,哈希成一个新的 JWT ,并返回。
注意我们在上面设置了一个 ‘key’ 变量,这个 key 是一个安全盐值(salt),在生产环境下请用一个比这个更安全的盐值。
现在我们有了一个验证 token 的服务。我们更新下 user-cli,我现在已经把这部分简化成一个脚本,因为这之前的 cli 部分的代码有点问题,我以后再来说这个问题,这个工具就是为了测试用:
目前我们有一些硬编码的值,替换下这些值,然后用 $make build && make run 执行脚本。你可以看到返回了一个 token。把这个长长的 token 值拷贝下,你马上就会用到!
现在我们更新下我们的 consignment-cli,生成一个 token 字符串,传给 consignment-service:
现在我们更新 consignment-service 来核对一下获取 token 的请求,并传给 user-service :
然后我们执行 consignment-cli工具,cd 到新的 shippy-consignment-cli 仓库,执行 $make build 来创建新的 docker 镜像, 现在运行:
注意下我们在运行 docker 容器的时候使用了 –net=”host” 标记。用来告诉 Docker 在本地局域网来运行我们的容器,如127.0.0.1 或 localhost ,而不是 Docker 的内网。注意,用这个方法不需要使用 端口转发 。因此只需要用 -p 8080 代替 -p 8080:8080 就可以了。 更多关于 Docker 网络的内容请看这里 。
到这一步,你就能看到新的 consignment 被创建了。试着从 token 里删除几个字母,token 就会变无效。会出现错误。
好了,现在我们创建了一个 JWT token 的服务,和一个用来验证 JWT token 的 中间件 ,而 JWT token 用来验证用户。
如果你不想用 go-micro,而是 vanilla grpc,你的中间件是类似这样的:
这个设置在本地运行有点不灵活。但是我们并不总在本地运行每一个服务。我们创建的服务应该互相独立,能单独测试。现在的情况下,如果我们只需要测试 consignment-service,就不需要启动 auth-service。因此我在这加了一个开关,打开或关闭其他的服务。
我更新 consignment-service 里的 auth 封装:
然后在 Makefile 里加个开关:
用这个方式,在本地运行微服务中的某个部分,会更简单,解决这个问题还有几个不同的方法,但是我认为这个最简单。希望你觉得有点帮助,尽管在方向上有小改动。同时,如果你对使用单个仓库运行微服务这方面有什么建议,非常欢迎告知我,那么这部分内容就简单多了。
如果你发现这篇文章里有任何 bug,错误,或者你有对这篇文章有任何反馈或者有帮助的建议, 请给我发邮件 。
via:
作者:André Carvalho 译者:ArisAries 校对:polaris1119
本文由 GCTT 原创编译,Go语言中文网 荣誉推出
历史文章