一、登录部分
1、第一部分:获取token
前面我们主要是在获取数据上下功夫,到目前为止我们已经能获取首页和详情页的数据了,现在我们将数据转移到权限管理上来,也就是说我们要处理用户登录、注册等一系列的行为,在这部分会接触挺多新知识,比如vuex怎样发送post请求、token是什么、jwt的工作原理、jwt和session的区别、怎样持久化获取用户的状态、针对不同请求的权限管理、怎样显示全局数据提示等,可谓非常多的知识,而且是是SPA中非常核心的内容,这里要学好
登录过程就是一个发送post请求的过程,这个过程中我们需要在body当中传入这个含有email也就是用户唯一识别身份和密码的这么一个数据,在邮箱密码匹配的情况下,后端会创建一个持久化会话,然后将一个特别的标识符传回给前端,这个标识符就是每次请求的特殊钥匙,这个东西就代表你已经经过身份验证
这里我们需要实现,点击登录获取这个token,我们到接口文档中看看
我们先用如下这个创建好的账户,如下,然后点击Execute就会出现如下
可以看到data中有一个token字段,token字段是一段看似随机的一串字符串,这就是我们的神秘令牌,登录的身份象征,我们称之为token,这里面不包含任何的客户信息,也就是说其他和用户相关的信息我们还需要使用这个令牌去获取,这个是后话,我们这里先获取token即可
我们先获取邮箱和密码成功的情况,出错的情况我们后面通过通用组件来处理整体的error的情况
我们来到store.ts,如下,在action中发起post请求,所以定义postAndCommit方法,注意方法中要返回data,因为我们要获取请求后返回的token,然后将返回的token赋值给state中的token中保存起来
然后我们到Login.vue中处理整个登录的流程
整个过程就是,在登录页中,用户输入邮箱、密码,验证格式正确后;我们去获取用户输入的这个邮箱和密码,因为axios请求数据是异步请求,所以去触发vuex中的action;action中接收邮箱和密码,然后去post请求,请求后服务端会返回数据的,返回的数据中有token,我们就在请求回来后接着把这个action存到vuex的state的token中保存起来
此时登录后,我们就能打印出data
现在我们还不知道怎么使用令牌,在拥有权限的后端系统进行自由的沟通
2、jwt运行机制
上面我们获取了token,它是我们告诉后端我已经登录的重要标识符,那么它是怎样工作的呢,这里我们就来了解一下web应用中是怎样完成权限验证的
我们先来说经典的解决方案就是cookie session的解决方案,来看它的过程
首先浏览器向服务器发送这个login的post请求,我们把用户名和密码都发送过去;这时候服务器验证,发现用户名和密码是对的,然后服务器就会创建对应的session数据并且保存,保存完毕以后,服务器就会发送返回一个HTTP 200 OK的response,这个response一般会有一个header叫Set-Cookie把sessionid唯一的id给带上;浏览器端拿到这个response以后因为有Set-Cookie这个hearder,那么浏览器就会把这个cookie保存在浏览器中;下次要访问需要权限的接口时比如这个get的/api/user,这时候它就会自动的把这个cookie带上;服务器端就使用cookie中的信息插件服务器中是否存在该session的数据;如果存在则返回200对应的信息,如果不存在则返回401。
基于cookie的身份验证是有状态的,这意味着这个验证记录或者会话必须同时保存在服务器端和客户端,服务器端需要跟踪记录session并且存入内存或者数据库,前端在cookie中要保存这个sessionid作为session的唯一标识符,这种模式的问题在于它的扩展性不好,加入我们就一台服务器就没有问题,如果是服务器集群即很多台服务器,这就要求session要数据共享,因为我们现在session存在在一台数据,所以我们需要给每台服务器都读取到这个session,那么一种解决方案就是session的数据持久化,我们可以做一个持久城,把这个数据写到持久城去,持久城有一个数据库之类的东西,即受到请求之后都向持久城发送数据,这种方案优点是架构清晰,缺点是工程量比较大,而且我们单独的持久城万一挂了就会失败
那么就催生了另外一种替代的解决方案,即基于token的解决方案
不同于之前在服务器要保存信息的特点,它是把所有的信息都保存在客户端,之后每次请求都将生成的信息发回到服务器,我们看看它的流程
首先post login把用户名和密码发送给服务端;这时候服务器验证我们的登录信息登录成功的时候,这时候它会使用JWT算法,生成对应的token;这个token会随着HTTP 200 OK返回给浏览器;然后浏览器会把这个token储存在客户端,最常见就是储存在localStorage或者sessionStorage中;之后再次向服务器发送请求的时候都会带上这个token,我们可以把token放在cookie里面自动发送,但是这样不能跨域,所以最好做法就是官方文档里面写的,把token放在HTTP请求的头部信息即Header的Authorization中发送;服务器拿到token信息后,JWT反向验证对应的token是否正确,如果正确则返回200信息,如果不正确则返回401信息。如果用户退出登录,我们token就会在客户端销毁,你把这个localstorage清除就可以了,这一步与服务器无关。
我们发现基于token的身份验证是没有状态的,也就是服务器不需要记录哪些用户已经登录或者哪些JWT已经处理,每个发送到服务器的请求都会带上token,服务器利用这个token检查确认请求的真实性。
那么这个token是什么组成的呢,通过特点的加密算法,将用户登录后的一些信息比如用户id等,信息储存在一个加密过的字符串中,以后用户和服务器端通信的时候都会发送这个token,服务器完全只靠这个对象就可以认定用户的身份,也就是说我们现在这个服务器就变成这个无状态的,从而非常容易实现扩展即多少台服务器都可以。
3、登录第二部分:axios设置通用header
现在我们来完成这个登录的流程,现在我们发现登录其实分几个部分,第一个是访问login这个url获取token,第二步就是设置header,第三步是获取用户信息,即访问user这个API获取信息并且更新store,最后还会显示到这个页面上
上面我们已经完成了第一步即获取token,接下来第二步就是设置header,我们可以在请求中写config来设置header,但是axios有没有提供给我们一劳永逸的方法设置或删除某个特点的header呢,让它的每次请求中都出现,我们看看文档,如下,可以通过axios.defaults.common['Authorization']=
我们到store.ts中,登录获取到token后,我们是想把token放到header中,请求时token就随着header带给服务端啦,所以我们要去获取到token的login方法中把token给header带上,如下
然后我们去请求当前用户信息的API,把当前用户信息数据请求回来,然后保存到state的user中
我们发现我们需要一个操作触发两个action,即用户输入信息点击登录后,我们应该去发送post的axios请求把用户输入的信息给服务端,然后再去发送get的axios请求把当前用户信息获取回来。
一个axios是请求用户信息的即actions中那个login,一个axios是请求当前用户信息的即actions中那个fetchCurrentUser,这两个是有先后顺序的。所以我们是可以在如下这个dispatch中发送login的axios请求后再dispatch另一个axios
但是axios给了我们更强大的力量,也就是我们可以新建一个action,然后组合多个axios的行为,我们看看文档,
所以如下,我们在actions中定义一个loginAndFectch来整合两个,即这个函数中去调用actions中的login方法和fecthCurrentUser方法,这样的话即登录的时候直接调用这个loginAndFectch即是同时调用了login方法和fecthCurrentUser方法,而且这两个是有先后顺序的
然后我们在首页登录后,可以看到登录后把token放到header后,再次请求去获取当前用户信息时的header中就带上了这个Authorization,并且这个返回的数据如下
接收到返回的当前用户的信息数据后,我们应该保存到store中,而且注意到我们定的user数据结构和防护数据结构不同,所以我们应该修改一下我们的user的数据结构,如下:
应该改成返回的数据这种
所以如下
4、登录第三部分 持久化登录状态
前面我们设置了axios的通用header,还使用组合action完成了登录和获取用户信息,但是现在有个问题,就是登录以后,在下次刷新加入应用的时候,登录状态就消失了,我们需要重新登录这一系列操作,这是网站常用需求就是记住用户的登录状态,现在我们就来搞这个功能
如果要记住登录状态,那么就需要将这个数据持久化在一个地方,我们有三种选择方案,一个是cookie,一个是localstorage,一个是sessionstorage。cookie不支持跨域,所以不考虑,sessionstorage在会话结束就会清除,所以我们用localstorage,可以长期保存
我们应该存什么呢,所有的user数据都存进去吗,我们需要把token存入localstorage,然后再每次进入app的时候,用已经存好的token 发送一次获取用户信息的请求即可。token也是有时效性的,加入这个请求的时候后端返回错误信息,这时候我们就可以弹出提示,同时把localstorage清空
现在你已经登录了,然后刷新再次进入,所以要重新获取用户信息,然后接下来整个过程就是,
首先看localstorage中有没有token:有则localstorage中的token存到store中,然后把token放置到header中,然后再发起获取当前用户信息的请求,如果请求成功则显示用户登录的信息,如果请求失败则显示错误提示并且说明当前token已经过期所以清空localstorage;
如下,第一步我们要初始化这个store的时候,从localstorage中getItem获取存这的token;加入之前设置过token在localstorage中,那么这里就是有值的,如果没有值就是空的;所以App第一次加载的时候,我们会判断token是否存在,并且我们还判断用户是否未登录;假如token存在且用户未登录,那么我们就把token给header中带上,并且请求当前用户信息,即设置Authorization头并且发送获取用户信息的请求即fetchCurrentUser请求;假如请求成功我们就显示用户信息,假如请求失败,则显示错误提示并且清空localstorage
具体代码如下:
把请求回来的token保存到localstorage中
store初始化的时候token初始化不再是一个空了,而是要么是localstorage中的token要么为空
App第一次加载即onMounted的时候,我们就需要去判断store中的token是否存在,如果用户当前未登录并且有token,说明用户之前登录过,所以我们做持久化登录,如下做以下逻辑,设置header带上这个token,并且获取当前用户信息。获取到用户信息的方法中就会把当前用户信息存到store中,并且设置登录状态字段为已登录,所以当前用户就是这个用户并且已经登录
所以最终登录后重新刷新后,获取的用户信息还是这个用户,并且还是已登录状态,所以就还是这个用户已经登录的状态,那么就实现了登录持久化
5、通用错误处理
请求服务器回4xx等错误我们的处理,我们就需要一个全局的错误这么一个机制,这种全局错误明显就是一个应该显示在store中的内容,所以我们到store.ts中添加如下
现在我们需要一个统一的地方来处理这个错误状态的修改,即我们什么时候对错误进行处理,我们想到在我们之前用过的axios的全局拦截器中,即在数据返回的时候即response的时候我们拦截它,然后进行我们想要的逻辑操作
看文档,我们看到axios的拦截器中,在返回数据的时候不仅有成功时的操作会有返回失败时的操作
如下,打开main.ts
加入逻辑
最后我们在根组件中把错误展示出来
然后我们接下来做一个弹出框组件,让错误提示展示出来即可