前后端分离登录

前后端分离登录

  • 1.http无协议
  • 2.常见的几种登录方式
    • 2.1 Cookie + Session 登录
    • 2.2 Token 登录
    • 2.3 SSO 单点登录
    • 2.4 OAuth 第三方登录
  • 3.前端登录(vue+token)
    • 3.1在axios请求头里携带
    • 3.2判断状态码
    • 3.3进行路由守卫拦截
    • 3.4Vuex部分
  • 4.后端登录逻辑(express)
    • 4.1安装 express-session中间件

1.http无协议

因为HTTP是无状态的协议,也就是说,这个协议是无法记录用户访问状态的,其每次请求都是独立的无关联的,一笔是一笔。而我们的网站都是设计成多个页面的,所在页面跳转过程中我们需要知道用户的状态,尤其是用户登录的状态,这样我们在页面跳转后我们才知道是否可以让用户有权限来操作一些功能或是查看一些数据。

所以,我们每个页面都需要对用户的身份进行认证。为了实现这一功能,用得最多的技术就是浏览器的cookie,我们会把用户登录的信息存放在客户端的cookie里,这样,我们每个页面都从这个cookie里获得用户是否登录的信息,从而达到记录状态,验证用户的目的。下面是使用cookie的一些原则。

千万不要在cookie中存放用户的密码。加密的密码都不行。因为这个密码可以被人获取并尝试离线穷举。所以,一定不能把用户的密码保存在cookie中。
关于设计“记住密码”。一般的设计是当用户勾选了这个功能,系统会生成一个cookie,cookie包括用户名和一个固定的散列值,这个固定的散列值一直使用。这样,你就可以在所有的设备和客户上都可以登录,而且可以有多个用户同时登录。

2.常见的几种登录方式

2.1 Cookie + Session 登录

在cookie中,保存三个东西——用户名,登录序列,登录token。
用户名:明文存放。
登录序列:一个被MD5散列过的随机数,仅当强制用户输入口令时更新(如:用户修改了口令)。
登录token:一个被MD5散列过的随机数,仅一个登录session内有效,新的登录session会更新它。
上述三个东西会存在服务器上,服务器的验证用户需要验证客户端cookie里的这三个事。
流程

  • 1.用户访问 a.com/pageA,并输入密码登录。
  • 2.服务器验证密码无误后,会创建 SessionId,并将它保存起来。
  • 3.服务器端响应这个 HTTP 请求,并通过 Set-Cookie 头信息,将 SessionId 写入 Cookie 中。

二次访问时

  • 用户访问 a.com/pageB 页面时,会自动带上第一次登录时写入的 Cookie。
  • 服务器端比对 Cookie 中的 SessionId 和保存在服务器端的 SessionId是否一致。
  • 如果一致,则身份验证成功。

缺点

  • 由于服务器端需要对接大量的客户端,也就需要存放大量的 SessionId,这样会导致服务器压力过大。如果服务器端是一个集群,
  • 为了同步登录态,需要将 SessionId 同步到每一台机器上,无形中增加了服务器端维护成本。
  • 由于 SessionId 存放在 Cookie 中,所以无法避免 CSRF 攻击。

2.2 Token 登录

Token 是服务端生成的一串字符串,以作为客户端请求的一个令牌。当第一次登录后,服务器会生成一个 Token 并返回给客户端,客户端后续访问时,只需带上这个 Token 即可完成身份认证。
token生成方式:
最常见的 Token 生成方式是使用 JWT(Json Web Token,它是一种简洁的,自包含的方法用于通信双方之间以 JSON 对象的形式安全的传递信息。
JWT 算法主要分为 3 个部分:header(头信息),playload(消息体),signature(签名)。
登录流程

  • 1.首次登录时,后端服务器判断用户账号密码正确之后,根据用户id、用户名、定义好的秘钥、过期时间生成 token ,返回给前端;
  • 2.前端拿到后端返回的 token ,存储在 localStorage 和 Vuex 里
  • 3.前端每次路由跳转,判断 localStorage 有无 token ,没有则跳转到登录页,有则请求获取用户信息,改变登录状态;
  • 4.每次请求接口,在 Axios 请求头里携带 token;
  • 5.后端接口判断请求头有无 token,没有或者 token 过期,返回401;前端得到 401 状态码,重定向到登录页面。

2.3 SSO 单点登录

单点登录指的是在公司内部搭建一个公共的认证中心,公司下的所有产品的登录都可以在认证中心里完成,一个产品在认证中心登录后,再去访问另一个产品,可以不用再次登录,即可获取登录状态。

  • 1.用户访问网站 a.com 下的 pageA 页面。
  • 2.由于没有登录,则会重定向到认证中心,并带上回调地址 www.sso.com?return_uri=a.com/pageA,以便登录后直接进入对应页面。
  • 3.用户在认证中心输入账号密码,提交登录。
  • 4.认证中心验证账号密码有效,然后重定向 a.com?ticket=123 带上授权码 ticket,并将认证中心sso.com 的登录态写入 Cookie
  • 5.在 a.com 服务器中,拿着 ticket 向认证中心确认,授权码 ticket 真实有效。
  • 6.验证成功后,服务器将登录信息写入 Cookie(此时客户端有 2 个 Cookie 分别存有 a.comsso.com 的登录态)。

当某个产品 c.com 退出登录时:

  • 清空 c.com 中的登录态 Cookie。
  • 请求认证中心 sso.com 中的退出 api。
  • 认证中心遍历下发过 ticket 的所有产品,并调用对应的退出 api,完成退出。

2.4 OAuth 第三方登录

  • 1.首先,a.com 的运营者需要在微信开放平台注册账号,并向微信申请使用微信登录功能。
  • 2.申请成功后,得到申请的 appid、appsecret
  • 3.用户在 a.com 上选择使用微信登录。这时会跳转微信的 OAuth 授权登录,并带上 a.com 的回调地址。
  • 4.用户输入微信账号和密码,登录成功后,需要选择具体的授权范围,如:授权用户的头像、昵称等。
  • 5.授权之后,微信会根据拉起 a.com?code=123 ,这时带上了一个临时票据code
  • 6.获取 code 之后, a.com 会拿着 code 、appid、appsecret,向微信服务器申请 token,验证成功后,微信会下发一个 token
  • 7.有了 token 之后, a.com 就可以凭借 token 拿到对应的微信用户头像,用户昵称等信息了。
  • 8.a.com 提示用户登录成功,并将登录状态写入 Cooke,以作为后续访问的凭证。

3.前端登录(vue+token)

3.1在axios请求头里携带

使用 respone 拦截器,对 2xx 状态码以外的结果进行拦截。

if (window.localStorage.getItem('token')) {
  Axios.defaults.headers.common['Authorization'] = `Bearer ` + window.localStorage.getItem('token')
}

3.2判断状态码

如果状态码是401,则有可能是 Token 过期,跳转到登录页

instance.interceptors.response.use(
  response => {
    return response
  },
  error => {
    if (error.response) {
      switch (error.response.status) {
        case 401:
          router.replace({
            path: 'login',
            query: { redirect: router.currentRoute.fullPath } // 将跳转的路由path作为参数,登录成功后跳转到该路由
          })
      }
    }
    return Promise.reject(error.response)
  }
)

3.3进行路由守卫拦截

localStorage 里有 Token ,就调用获取 userInfo 的方法,并继续执行,如果没有 Token ,调用退出登录的方法,重定向到登录页。
router.js

  • 1.引入store
import { createRouter, createWebHashHistory, createWebHistory, useRouter } from 'vue-router';
import store from '../store/index.js'
  • 2.创建路由对象的时候配置meta信息,{ requireAuth: true } ,表示进入这个页面需要登录
const routes = [
    { path: '/', redirect: '/login' },
    //添加这个字段,表示进入这个页面需要登录
    { path: '/publish_found', component: PublishFound, meta: { title: "招领", requireAuth: true } },
    { path: '/publish_lost', component: PublishLost, meta: { title: "寻物", requireAuth: true } },
    { path: '/my_publish', component: MinePublish, meta: { title: "我的发布", requireAuth: true } },
    { path: '/my_msg', component: MineMessage, meta: { title: "我的信息", requireAuth: true } }
]
  • 3.因为使用的是vuex-persistedstate,所以state会存储在sessionStore里,然后获取store对象里的token,用来判断需要登录的页面是否已经登录
// 全局路由前置守卫(这里做权限的判断,路由的控制)
router.beforeEach((to, from, next) => {
    document.title = to.meta.title;
    if (to.meta.requireAuth) { // 判断该路由是否需要登录权限
        const token = store.state.token ? store.state.token : window.sessionStorage.getItem('token')
        if (token) {
            next()
        } else {
            next({ path: '/login' })
        }
    } else {
        next()
    }
})

// 3.导出路由对象
export default router;

3.4Vuex部分

  • state.js定义需要存储用户的数据,这里主要以token的存储和获取为例子
export default {
    user: '张三',
    token: '',
    userImage: '',
}
  • 在mutation_types里定义
export const GET_USER = 'getUser'
export const SET_TOKEN = 'setToken'
export const SET_USERIMAGE = 'setUserImage'
  • mutations.js
export default {
    GET_USER(state, value) {
        state.user = value;
    },
    SET_TOKEN(state, value) {
        state.token = value;
        console.log(value);
    },
    SET_USERIMAGE(state, value) {
        state.userImage = value;
    },
    OUT_LOGIN(state) {
        state.token = ''; //退出登录,清空token
    }
}
  • actions.js
export default {
    setToken({ commit }, value) {
        commit('SET_TOKEN', value)
    },
    setUser({ commit }, value) {
        commit('SET_USER', value)
    },
    setImage({ commit }, value) {
        commit('SET_IMAGE', value)
    },
}
  • login页面,登录成功后,把token存放在store里
 const submitUserMsg = () => {
      if (phone.value.trim() == "" || password.value.trim() == "") {
        Toast({
          message: "账号/密码不能为空",
          position: "top",
        });
      } else if (identify.value !== data.identifyCode) {
        {
          Toast({
            message: "验证码错误",
            position: "top",
          });
        }
      } else {
        //通过所有校验,获取token存储在store里
        request
          .post("/login", { data: formData })
          .then((res) => {
            console.log(res.token);
            store.dispatch("setToken", res.token);
            router.push("/user");
          })
          .catch((err) => {
            console.log(err);
          });
      }
    };
  • 在页面里调用退出登录的方法OUT_LOGIN
 const backLogin = () => {
      console.log("退出登录");
      Dialog.confirm({
        title: "确认退出登录吗",
        message: "",
      })
        .then(() => {
          store.commit("OUT_LOGIN");
          router.push({
            path: "/login",
          });
        })
        .catch(() => {});
    };
  • index.js
import { createStore } from 'vuex';
import createPersistedSatte from 'vuex-persistedstate'

import state from './state'
import getters from './getters'
import actions from './actions'
import mutations from './mutations'

const store = createStore({
    state,
    getters,
    actions,
    mutations,
    plugins: [createPersistedSatte()]
})

export default store;
  • 在main.js里进行注册
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/router.js'
import Vant from 'vant';
import 'vant/lib/index.css'
import ('./mock/mock.js');
import store from './store/index.js'

const app = createApp(App);
app.use(router);
app.use(Vant)
app.use(store);
app.mount('#app')

4.后端登录逻辑(express)

4.1安装 express-session中间件

npm install  express-session

你可能感兴趣的:(Nodejs,Vue3,vue.js,javascript)