引言
当我们完成登录后,服务器如何记住用户的状态?比如说,当我们查询我的订单时,服务端如何判断究竟是哪个用户进行的查询。
尽管我们可以很方便的去用cookie+session来记录用户的状态。但是这种方式会产生一些问题:
- 在前后端分离的架构下,因为同源策略的限制,使默认情况下cookie被禁用(当然我们可以启用),session的方式自然也就失效。
- session存取的数据会占用服务器的空间。
- 在分布式情况下,我们需要考虑分布式session,实现起来更加复杂。
针对上面的问题,在我个人的项目开发中,会更倾向于用token去记录用户的状态。
Token
对于Token来说,简单的说它就是一段含有用户信息的加密字符串,通常我们会采用JWT工具来完成Token的加密、解密流程。(关于JWT的具体信息,可自行查看官网jwt.io/introductio…)
Token的使用流程如下:
1.用户登录后,我们利用所登录用户的信息、过期时间、密钥等信息,通过JWT生存Token,并返回给前端。
用户信息:一定要加入能唯一标识用户的,一般会存入用户ID.
过期时间:我们需要为每一个Token设置过期时间,当超过过期时间后,解析Token时就会抛出异常。其目的就是避免Token永久生效,会产生安全隐患。
密钥:Token的生成和解析都需要使用同一密钥,密钥不能丢失(被窃取后所有的Token都可以被解密),一般会把它放到配置文件中。
2.前端每次登录后,都会在请求头中携带这个Token。具体格式如下:
Authorization: Bearer <Token>
3、服务端通过拦截器拦截所有需要登录的请求,Token解析成功才能通过(Token错误、Token过期均不能通过),并可以获得用户信息,完成后续操作。
Access Token 和 Refresh Token
前文中提到的,为避免Token永久生效,我们会为Token设置过期时间。但是如果说,用户此时用在使用系统,这时候Token恰好过期了,这时候就需要用户重新登录,会造成很差的用户体验。而因为我们每次请求都会携带token,被窃取的风险很高,如果我们把Token过期时间设置的很长,一旦这个Token被窃取,那么就会造成很大的安全问题。基于此,引入了Access Token 和 Refresh Token。
AccessToken:用于用户认证,每次用户请求都需要携带,过期时间较短。
RefreshToken:用于刷新Token,等到AccessToken过期后,可以通过RefreshToken获得新的AccessToken和RefreshToken,过期时间较长。
此时用户认证流程如下:
- 用户登录后,服务器生成AccessToken和RefreshToken并返回给前端。
- 前端每次请求接口都要携带AccessToken。
- 服务端通过拦截器拦截所有需要登录访问的请求,AccessToken解析成功后可获得用户信息并完成后续操作。
- 若AccessToken解析失败(错误或过期),那么服务端会返回给前端错误信息。此时客户端会利用RefreshToken,通过服务端提供的刷新Token接口,请求刷新Token。
- 服务端会判断RefreshToken是否有效,若有效(正确且未过期),则会根据RefreshToken中解析到的用户信息,重新生成AccessToken和RefreshToken并返回给前端,前端会拿着新的AccessToken重新请求接口。
- 若RefreshToken失效(错误或过期),那么服务端会返回给前端错误信息,此时需要用户重新登录。
单点登录
在默认情况下,只要我们生成的Token没有超出过期时间,那么它就是可以使用的,如果我们在不同的设备上登录了10次,那么系统也会为我们生成10个Token,且这10个Token都是可以使用的。但是如果我们想要实现单点登录(即系统只能在一个设备上登录),那我们就需要自己维护一个Token白名单。
我们可以用Redis来完成这一点,每次登录后,我们将最新生成的Token存到Redis中,之后的每次请求,我们都会判断请求所携带的Token是不是最新的Token,如果不是则说明该Token已失效,需要重新登录。