坏蛋Dan
知乎@坏蛋Dan
发布时间:2024.1.4

前言

昨天我们做了一些基础配置,今天我们正式开始来写路由了,我们先来实现用户相关的内容

项目地址:

由于我是先实现完再写的这篇文章,如果有些地方无法运行,可以看下我项目里的代码。

目前我还在往全栈的方向学习,所以如果看的不顺眼,请多多包涵。如果觉得那里可以改进,麻烦评论区说下,谢谢~


token

我们的服务器在请求之前必须要先登录才行,那么就需要有一个字段即token用于我们的验证。

token生成和解析

在我们开始实现校验部分之前,我们需要了解token的创建和解析。

这里我用的jsonwebtoken[1]这个crate

简单的看下它的用法

你可以自定义插入到header里的数据

其中exp是强制要求的字段,即你自定义的struct里必须要有一个exp字段,用于表示token的有效时间。

我们先在src下创建一个authmod,然后在里面分别创建token.rsmod.rs

mod.rs中我们创建一个UserTokenstruct,它是我们插入到header里的东西。

  • id:用户的id,为了确保安全,这个信息只会放在header_token字段中,并且是加密过后的。
  • exp:这个则是用于校验token是否过期的。

token.rs 存放我们的生成/解析token的方法

  • encode_token就是用来生成token,注意这里的Headerjsonwebtoken自己的Header,而不是rocketHeader,而user_token则是我们的数据,它有一个强制的字段要求exp,表示过期时间。key则是我们加密的key,加密的方式我们就用它默认的即可。
  • decode_token自然就是解析
  • set_token:这个用于返回我们生成的token信息,在这里我们用到了之前的MyConfig
  • get_current_timestamp[2]:获取当前时间的时间戳,加上我们在MyConfig中配置的exp时间间隔,这样就可以用来表示过期时间了。
  • req.rocket().state::():我们可以通过这种方式获取全局的state,注意这里必须用::来声明指定的类型。

校验

由于我这定义了请求前必须要登入,任何接口都是。所以这个时候我们就需要给每个路由都加上对于token的校验,根据我们学的知识,我们可以把这个放到全局的fairing,相当于一个路由守卫,能参与一次请求的任意生命周期。

回到auth文件夹,在mod.rs文件夹中我们新建一个名为AuthMsgstruct

这个struct用于临时数据,等会会用到。

然后我们新建一个fairing.rs的文件

实现Fairing[3]必须实现info这个method,目的是告知rocket哪些生命周期需要监听,这里我监听的是RequestResponse

request进入到路由之前就会触发这个on_request函数,而response准备发送给前端的时候即执行完处理器才会触发on_response

这里就用到了我们前面实现的decode_token方法,用于获取requestHeader里面的token信息。

on_request我通过判断是否存在tokentokenexp是否已经过期这两步来实现校验。

如果通过校验,那么请求的暂时缓存local_cache[4]就会放一个AuthMsg的数据,里面存放了一个标志位is_valid_token ,这个标志位用于response的时候判断是否符合要求,不符合直接重写response这么做其实不太好,因为处理器里面的逻辑实际上还是执行了,浪费了很多性能,但是我找不到较好的方案。。。如果你有好的方案,请务必评论区里说下,谢谢!

另外吐槽下这个local_cache,它并不能改动数据,是的。。。也可能是我姿势不对,我尝试了一段时间后就放弃了。

虽然说每个路由都需要校验token,但是实际上有些是不需要的,比如404500400等。 所以这里搞了个白名单EXCEPT_LIST由于过滤掉上面的几种错误场景

on_response中重写数据时,我用到了前面我们实现的RtData类型,但是如果直接用是会报错的,因为RtData并不符合类型约束,没有实现Responder[5]

那么我们回到type/mod.rs中,给RtData实现这个trait

给它实现Responder,支持我们的类型作为response

这样就可以了。

另外提一下这个std::io::Cursor::new,这个用来指向数据的起始位置

那么token校验相关的就都完成了

最后我们在main.rs中引入,不过在这之前,我们还需要在auth/mod.rs中引入,包括前面提到的一些文件,不要忘了在mod中引入以及pub出去,如果只是文件夹里面跨文件使用,那么可以不需要pub出去。

然后我们新建一个state的文件夹,在里面新建mod.rs文件。这个文件夹作为存放我们全局state的地方。

最后我们在mian.rs中引入

这样就行啦

下一章我们来开始编写user相关的路由了。


其它

另外有一点就是这个log,错误的收集,实际开发我们是需要把log都收集到.log文件中的,这样方便日志跟踪排错,我这里图省事就没这么做。我逛了一圈,发现有一个log4rs[6]的,显然是rust版本的log4j,但是我这里并没有采用,大家有需要可以自行引入。

当然你也可以自己实现,比如用channel收集错误信息,用队列存储,达到一定的量或者每隔一小时就批量写一次进文件中。。。


补充优化

前面我们用的是fairing监听on_requeston_response来限制反馈,但是这里有个大问题和bug

bug是路由的function实际上还是会执行,也就是说如果不在路由函数那边加上判断是否可执行和return的逻辑,那么数据还是可以修改成功。

另外有些路由是没必要走这个校验逻辑的,所以要么我们加上白名单,要么我们最好就是换一个方案。。

实现一个内部参数:user_msg

还记得我们在给路由参数实现的Request等类型么?实际上我们可以把token 校验放在参数中,这个参数不需要前端传递,而是一个内部参数。这里命名为user_msg,里面存放的是token解密之后的参数id,而另一个数据exp就没必要了,仅做为token校验用的。

代码很简单,和前面fairings中做的一样,不同点在于这里校验失败直接通过Outcome::Failure的方式直接去到error catcher中,这里就不会继续往下执行路由的逻辑了。

那么该如何使用这个UserMsg来限制一些借口呢?简单,如果有需要检验token的地方,就在参数中加上这个UserMsg即可。

比如:更新用户数据的,这里就必须要要有token才行。

这样也不需要使用白名单的方式来过滤路由

参考

  1. ^jsonwebtoken https://crates.io/crates/jsonwebtoken
  2. ^get_current_timestamp https://docs.rs/jsonwebtoken/8.3.0/jsonwebtoken/fn.get_current_timestamp.html
  3. ^Fairing https://api.rocket.rs/v0.5-rc/rocket/fairing/trait.Fairing.html
  4. ^local_cache https://api.rocket.rs/v0.5-rc/rocket/request/macro.local_cache.html
  5. ^Responder https://api.rocket.rs/v0.5-rc/rocket/response/trait.Responder.html
  6. ^log4rs https://crates.io/crates/log4rs

编辑于 2023-09-04 21:03・IP 属地广东