登录的具体代码实现如下(位于src/pages/user/index.tsx,请求api位于src/services/auth/login中):

1、登录请求

在登录页面中通过提交方法,调用store中的Login方法进行登录。
src/views/user/Login.vue

  1. import type { LoginParamsType } from '@/services/auth/login';
  2. import request from 'umi-request';

获取登录验证码:

  1. const getCodeImage = (key:String) => {
  2. console.log(key)
  3. return request(`${codeUrl}?key=`+key, {
  4. method: 'GET',
  5. responseType: 'arrayBuffer',
  6. })
  7. .then((res) => {
  8. return (
  9. "data:image/png;base64," +
  10. btoa(
  11. new Uint8Array(res).reduce(
  12. (data, byte) => data + String.fromCharCode(byte),
  13. ""
  14. )
  15. )
  16. );
  17. })
  18. .then((res) => {
  19. setImageCode(res);
  20. })
  21. .catch((e) => {
  22. if (e.toString().indexOf("429") !== -1) {
  23. }
  24. });
  25. };

执行登录:

  1. const handleSubmit = (values: LoginParamsType) => {
  2. const { dispatch } = props;
  3. console.log(randomId)
  4. dispatch({
  5. type: 'login/login',
  6. payload: { ...values, type ,key:randomId},
  7. });
  8. };

src/services/auth/login.js

  1. export async function fakeAccountLogin(params: LoginParamsType) {
  2. params = {...params , grant_type : 'password'}
  3. return request('/auth/oauth/token?'+tansParams(params), {
  4. method: 'POST',
  5. data: params,
  6. headers: {
  7. Authorization: settings.authorizationValue+"",
  8. },
  9. });
  10. }
  11. export async function refresh(refresh_token: string) {
  12. return request('/auth/oauth/token?refresh_token'+refresh_token+'&grant_type=refresh_token', {
  13. method: 'POST',
  14. headers: {
  15. Authorization: settings.authorizationValue+"",
  16. },
  17. });
  18. }
  19. function tansParams(params:LoginParamsType) {
  20. let result = ''
  21. Object.keys(params).forEach((key) => {
  22. if (!Object.is(params[key], undefined) && !Object.is(params[key], null)) {
  23. result += encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) + '&'
  24. }
  25. })
  26. if(result.length>0){
  27. result = result.substring(0,result.length-1)
  28. }
  29. return result
  30. }

调用src/services/auth/login.js 获取token。
封装好的login方法到后端获取访问令牌(请求头携带了Authorization = Basic ZmViczoxMjM0NTY=)
请求中设置headers: ‘Authorization’: defaultSettings.authorizationValue

  1. export async function fakeAccountLogin(params: LoginParamsType) {
  2. params = {...params , grant_type : 'password'}
  3. return request('/auth/oauth/token?'+tansParams(params), {
  4. method: 'POST',
  5. data: params,
  6. headers: {
  7. Authorization: settings.authorizationValue+"",
  8. },
  9. });
  10. }

登录成功后获取返回数据

  1. {
  2. "access_token": "78bcf572-c7ec-40e3-bf22-35ce17c618ee",
  3. "token_type": "bearer",
  4. "refresh_token": "7d2c2478-07ef-43c4-b9f8-f57157b6abed",
  5. "expires_in": 86399,
  6. "scope": "all"
  7. }

上面JSON包含了访问令牌,令牌失效时间和刷新令牌等信息,我们对查询的信息进行保存。
上面代码中,我们将令牌等信息存储到了Vuex中。虽然Vuex能够存储信息并全局生效,但是当我们刷新页面后,这些信息就会丢失,我们通常借助LocalStorage来持久化信息。

2、令牌刷新

我们在qingfeng-auth定义的令牌有效时间为86400秒(即24小时),过了24小时候令牌就失效了。假如令牌即将要失效时,用户还在使用系统,那么用户的某个操作可能进行了一半时,系统突然弹出登录过期提示,非常影响用户体验。
要解决上面的问题,我们可以在令牌将要失效时,判断用户是否还在使用系统,如果是的话,我们可以偷偷地通过刷新令牌来获取一个新的访问令牌,存储到浏览器内存中。这样就可以在用户无感知的情况下,“延长”访问令牌的有效时间。
因为我们系统的请求都是通过封装的Axios对象来完成的,并且我们在request.js里配置了请求拦截,所以我们刷新令牌的动作也可以在请求拦截器里完成,大致步骤如下图所示:

根据这个流程图,我们通过代码来实现。在request.js的请求拦截器里添加如下代码:

  1. request.interceptors.request.use(async (url, options) => {
  2. // return (
  3. // {
  4. // url: url,
  5. // options: { ...options},
  6. // }
  7. // );
  8. const headers = options.headers;
  9. if(headers['Authorization']==''||headers['Authorization']==null){
  10. const expireTime = localStorage.getItem("expireTime")
  11. if (expireTime) {
  12. const left = Number(expireTime) - new Date().getTime()
  13. const refreshToken = localStorage.getItem("refresh_token")
  14. if (left < checkRegion && refreshToken) {
  15. if (left < 0) {
  16. localStorage.removeItem("expireTime");
  17. localStorage.removeItem("refresh_token");
  18. localStorage.removeItem("access_token");
  19. }
  20. let accessToken = queryRefreshToken(refreshToken);
  21. headers['Authorization'] = 'bezarer ' + accessToken;
  22. } else {
  23. const accessToken = localStorage.getItem("access_token");
  24. if (accessToken) {
  25. headers['Authorization'] = 'bearer ' + accessToken;
  26. }
  27. }
  28. }
  29. }
  30. return (
  31. {
  32. url: url,
  33. options: { ...options, headers: headers },
  34. }
  35. );
  36. })

上面代码中,我们在请求拦截器里判断当前时间和令牌过期时间间隔是否小于5分钟并且刷新令牌是否存在。当两个条件都满足的时候,我们就发送刷新令牌请求,否则直接在请求头中携带令牌发送请求。刷新令牌的逻辑定义在queryRefreshToken函数中,代码如下所示:

  1. async function queryRefreshToken(refresh_token:string) {
  2. let param = '';
  3. refresh(refresh_token).then(result => {
  4. if (result.status === 'success') {
  5. saveData(result.data)
  6. param = localStorage.get("access_token");
  7. }
  8. })
  9. return param
  10. }

逻辑较为简单,就是通过刷新令牌发送令牌刷新请求,请求成功后,通过saveData将新的令牌保存到浏览器内存中,这个过程和登录成功后保存令牌是一样的。saveData代码如下所示:

  1. function saveData(data:any) {
  2. localStorage.setItem("access_token",data.access_token);
  3. localStorage.setItem("refresh_token",data.refresh_token);
  4. const current = new Date()
  5. const expireTime = current.setTime(current.getTime() + 1000 * data.expires_in)
  6. localStorage.setItem("expireTime",expireTime+"");
  7. }

保存令牌后,我们在请求头中携带新的令牌,然后发送请求。值得注意的是,我们定义的queryRefreshToken方法是一个同步方法,这是因为:Axios请求是异步的,当我们发送令牌刷新请求时,可能别的Axios请求也在执行,而这时候令牌并没有更换完成,别的Axios请求可能会因令牌的过期而抛出401异常。设置为同步方法后,只有当新的令牌获取成功后,别的Axios请求才会继续执行,由于令牌刷新过程是非常快的,所以用户并不会有明显的感知。