【springboot+vue项目(十五)】基于Oauth2的SSO单点登录(二)vue-element-admin框架改造整合Oauth2.0

Vue-element-admin 是一个基于 Vue.js 和 Element UI 的后台管理系统框架,提供了丰富的组件和功能,可以帮助开发者快速搭建现代化的后台管理系统。

一、基本知识

(一)Vue-element-admin 的主要文件和目录

vue-element-admin/
  |-- build/                          # 构建相关配置文件
  |    |-- build.js                   # 生产环境构建脚本
  |    |-- check-versions.js          # 检查 Node.js 和 npm 版本的脚本
  |    |-- logo.png                   # 构建 Logo
  |    |-- utils.js                   # 构建工具函数
  |    |-- vue-loader.conf.js         # Vue loader 配置
  |    |-- webpack.base.conf.js       # webpack 基础配置
  |    |-- webpack.dev.conf.js        # webpack 开发环境配置
  |    |-- webpack.prod.conf.js       # webpack 生产环境配置
  |
  |-- config/                         # 项目配置
  |    |-- dev.env.js                 # 开发环境变量配置
  |    |-- index.js                   # 项目配置文件
  |    |-- prod.env.js                # 生产环境变量配置
  |
  |-- src/                            # 源代码
  |    |-- api/                       # 接口请求相关
  |    |-- assets/                    # 静态资源
  |    |-- components/                # 全局公用组件
  |    |-- directive/                 # 自定义指令
  |    |-- icons/                     # 图标
  |    |-- layout/                    # 全局布局
  |    |-- router/                    # 路由配置
  |    |-- store/                     # 全局状态管理
  |    |-- styles/                    # 全局样式
  |    |-- utils/                     # 工具函数
  |    |-- views/                     # 页面组件
  |    |-- App.vue                    # 入口页面
  |    |-- main.js                    # 入口 JS 文件

  |    |-- permission.js           # 路由守卫 文件
  |
  |-- static/                         # 静态资源
  |
  |-- .babelrc                        # Babel 配置
  |-- .editorconfig                   # 编辑器配置
  |-- .eslintignore                   # ESLint 忽略文件配置
  |-- .eslintrc.js                    # ESLint 配置
  |-- .gitignore                      # Git 忽略文件配置
  |-- index.html                      # 入口 HTML 文件
  |-- package.json                    # 项目信息和依赖配置
  |-- README.md                       # 项目说明文档
  |-- vue.config.js                   # Vue CLI 配置 

(二)单点登录系统涉及的文件和概念

  1. 客户端ID和客户端密钥:前端应用和后端应用在与单点登录系统通信时,需要提供客户端ID和客户端密钥进行身份验证。
  2. 授权码(authorization code):单点登录系统验证用户身份成功后生成的一次性授权码,用于换取访问令牌和刷新令牌。
  3. 访问令牌(access token):单点登录系统验证成功后返回给前端应用的令牌,用于后续请求时进行身份验证。
  4. 刷新令牌(refresh token):单点登录系统验证成功后返回给前端应用的令牌,用于在访问令牌过期时更新访问令牌。
  5. 前端应用使用的SDK或库文件:前端应用需要集成相应的SDK或库文件以便与单点登录系统进行通信。
  6. 单点登录系统的API文档:开发人员需要根据API文档了解单点登录系统提供的接口和参数,以便正确调用API接口。

二、 前端实现单点登录的基本流程

设计思路:为了避免code和access_token的泄露,所以大部分的和单点登录系统(统一认证)的交互都放到后端进行,前端尽可能的复用原来的代码,进行小的改动。整体思路如下:

1、登录页面--login.vue(修改原login.vue):用户访问前端应用,前端应用将用户重定向到后端登录接口。

<template>
  <div class="login-container">
    <div>正在重定向到登录页面...</div>
  </div>
</template>

<script>
export default {
  created() {
    const baseUrl = process.env.VUE_APP_BASE_API;
    //重定向到后端的SSOlogin/login接口
    window.location.href = `${baseUrl}/SSOlogin/login`;
  },
};
</script>

<style scoped>
.login-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  text-align: center;
}
</style>

2、 响应页面--callback.vue(新建):检查后端返回的URL+token访问链接中是否存在后端返回的token,如果存在则调用登录函数user/login进行前端的登录操作(调用login函数进行前端的登录,目的是将token持久化和存储到VUEX中,尽可能少的改动原来的代码。),并根据登录结果进行不同的处理。如果没有获取到token,则输出错误信息并终止后续逻辑。

<template>
  <div>
    <h1>Loading...</h1>
  </div>
</template>

<script>
export default {
  name: "Callback",
  data() {
    return {
    };
  },
  created() {
    // 获取 URL 查询参数中的授权码
    const token = this.$route.query.token;
    // 利用后端传递的token,调用login函数进行前端的登录,目的是将token持久化和存储到VUEX中,尽可能少的改动原来的代码
    if (token) {
      this.loading = true;
      this.$store.dispatch("user/login",token)
        .then(() => {
          // 登录成功,路由跳转
          this.$router.push({ path: this.redirect || "/" });
          this.loading = false;
        })
        .catch(() => {
          this.loading = false;
        });
    } else {
      console.log("error submit!!");
      return false;
    }
  },
};
</script>

 对应的store/user.js。

3、 数据仓库 store/user.js,修改一下原来的Login函数即可。

原Login函数为:

//这里在处理登录业务
  async login({ commit }, userInfo) {
    //解构出用户名与密码
    const { username, password } = userInfo;
    let result = await login({ username: username.trim(), password: password });
    if(result.code==200){
      //vuex存储token
      commit('SET_TOKEN',result.data.token);
      //本地持久化存储token
      setToken(result.data.token);
      return 'ok';
    }else{
      return Promise.reject(new Error('faile'));
    }
  },

 修改后的Login为:

 // 登录 action
  async login({ commit }, token) {
    try {
      // 设置 token 持久化
      commit('SET_TOKEN', token);
      // 将 token 值保存在浏览器的本地存储中
      setToken(token);
      // 可以根据需要返回其他数据或状态
      return "ok";
    } catch (error) {
      // 异常时返回一个被拒绝的 Promise 对象
      return Promise.reject(error);
    }
  },

 所以,修改login函数,去掉了使用用户名和密码发送请求到后端接口验证登录,完整的代码如下:

// 引入需要使用的函数和模块
import { getInfo, logout  } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'

// 定义获取默认状态的函数
const getDefaultState = () => {
  return {
    token: getToken(), // 使用 getToken 函数获取 token 值
    name: '',
    avatar: ''
  }
}

// 定义 Vuex 模块的状态
const state = getDefaultState()

// 定义 Vuex 模块的变更操作
const mutations = {
  // 重置状态为默认状态
  RESET_STATE: (state) => {
    Object.assign(state, getDefaultState())
  },
  // 设置 token
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  // 设置用户名
  SET_NAME: (state, name) => {
    state.name = name
  },
  // 设置用户头像
  SET_AVATAR: (state, avatar) => {
    state.avatar = avatar
  }
}

// 定义 Vuex 模块的异步操作
const actions = {
  // 用户登录
  // 登录 action
  async login({ commit }, token) {
    try {
      // 设置 token
      commit('SET_TOKEN', token);
      // 将 token 值保存在浏览器的本地存储中
      setToken(token);
      // 可以根据需要返回其他数据或状态
      return "ok";
    } catch (error) {
      // 异常时返回一个被拒绝的 Promise 对象
      return Promise.reject(error);
    }
  },
  async getInfo({ commit, state }) {
    try {
      // 发送请求获取用户信息
      const response = await getInfo(state.token)
      // 如果响应数据不存在,说明验证失败
      if (!response.data) {
        throw new Error('验证失败,请重新登录。')
      }
      // 获取用户名和头像
      const { name, avatar } = response.data
      // 设置用户名
      commit('SET_NAME', name)
      // 设置用户头像
      commit('SET_AVATAR', avatar)
      // 返回完整的响应对象
      return response
    } catch (error) {
      return Promise.reject(error)
    }
  },
  // 用户注销
  async logout({ commit, state }) {
    try {
      // 发送请求注销用户登录状态
      await logout(state.token)
      // 从本地存储中删除 token
      removeToken()
      // 重置路由
      resetRouter()
      // 重置状态
      commit('RESET_STATE')
    } catch (error) {
      return Promise.reject(error)
    }
  },
  // 重置 token 值
  async resetToken({ commit }) {
    try {
      // 从本地存储中删除 token
      removeToken()
      // 重置状态
      commit('RESET_STATE')
    } catch (error) {
      return Promise.reject(error)
    }
  }
}

// 导出 Vuex 模块
export default {
  namespaced: true, // 开启命名空间
  state,
  mutations,
  actions
}

4、  数据仓库store/user.js引入的函数和模块

 为了更清晰的了解整个过程,将上面store/user.js引入的函数和模块也贴在下面即:

因为 store/user.js中不用再向后端发请求,所以api/user.js中就可以把login删掉了。

api/user.js 

//api/user.js
import request from '@/api/request/request'

export function getInfo(token) {
  return request({
    url: '/user/getInfo',
    method: 'get',
    params: { token }
  })
}

export function logout() {
  return request({
    url: '/user/logout',
    method: 'post'
  })
}

utils/auth.js:用于处理用户身份验证 token 的工具函数,主要涉及对浏览器 Cookie 的操作。getToken:获取用户的身份验证 token,setToken:设置用户的身份验证 token,removeToken:移除用户的身份验证 token。

//utils/auth.js
import Cookies from 'js-cookie'

const TokenKey = 'vue_admin_template_token'

export function getToken() {
  return Cookies.get(TokenKey)
}

export function setToken(token) {
  return Cookies.set(TokenKey, token)
}

export function removeToken() {
  return Cookies.remove(TokenKey)
}

5、路由守卫--permission.js:修改原来的)在每次路由导航之前进行身份验证和权限控制,确保用户在正确的身份状态下访问页面,并在页面切换后完成进度条的展示。

        如果用户已登录且获取了用户信息,则直接跳转到目标页面;

        如果用户未登录或获取用户信息失败,则跳转至登录页。

        同时,白名单内的页面可以在未登录状态下直接访问。

修改:配置白名单,把callback加进去

注意:通过 user/getInfo 获取用户信息  await  store.dispatch('user/getInfo'),所以api/user.js 里面要有getInfo函数。

 permission.js 完整的代码如下:

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // 引入进度条库
import 'nprogress/nprogress.css' // 引入进度条样式
import { getToken } from '@/utils/auth' // 引入获取 token 的方法
import getPageTitle from '@/utils/get-page-title' // 引入获取页面标题的方法

NProgress.configure({ showSpinner: false }) // 配置进度条

const whiteList = ['/login','/callback'] // 定义无需登录即可访问的白名单路由

router.beforeEach(async(to, from, next) => {
  NProgress.start() // 开始进度条

  document.title = getPageTitle(to.meta.title) // 设置页面标题

  const hasToken = getToken() // 获取 token

  if (hasToken) {
    if (to.path === '/login') {
      // 如果已登录却访问登录页,则重定向到首页
      next({ path: '/' })
      NProgress.done() // 完成进度条
    } else {
      const hasGetUserInfo = store.getters.name // 判断是否已获取用户信息
      if (hasGetUserInfo) {
        // 如果已获取,则直接进入路由
        next()
      } else {
        try {
          // 如果未获取,则通过 user/getInfo 获取用户信息
          await store.dispatch('user/getInfo')
          console.log("获取用户信息")
          next()
        } catch (error) {
          // 如果获取用户信息失败,则重置 token 并跳转至登录页
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          // window.location.href = `www.baidu.com`;
          NProgress.done()
        }
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      // 如果在白名单中,则直接进入路由
      next()
    } else {
      // 否则跳转至登录页
      next(`/login?redirect=${to.path}`)
      
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  NProgress.done() // 完成进度条
})

6、请求拦截器中添加token到请求头:和原来一样)在请求拦截器中,从cookie中获取token,并将其添加到请求的头信息中,这样可以确保每次请求都带上了token。

import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'

// 创建一个axios实例
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,  // 接口的基础路径
  timeout: 5000 // 请求超时时间
})

// 请求拦截器
service.interceptors.request.use(
  config => {
    // 在请求发送前做一些处理
    if (store.getters.token) {
      // 如果有token就在请求头中加上token
      config.headers['token'] = getToken()
    }
    return config
  },
  error => {
    // 对请求错误做些什么
    console.log(error)
    return Promise.reject(error)
  }
)

// 响应拦截器
service.interceptors.response.use(
  response => {
    // 对响应数据做一些处理,这里只返回响应数据中的data部分
    const res = response.data

    // 如果自定义的响应码不是20000,就判断为错误
    if (res.code !== 20000 && res.code !== 200) {
      // 在页面上显示错误信息
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })

      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // 重新登录
        MessageBox.confirm('您已经登出,您可以取消以留在此页面,或重新登录', '确认登出', {
          confirmButtonText: '重新登录',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
        })
      }
      // 返回一个被拒绝的Promise对象,用来表示错误
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
      // 如果没有错误,就返回响应数据中的data部分
      return res
    }
  },
  error => {
    // 对响应错误做些什么
    console.log('err' + error)
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service

7、获取用户信息并渲染页面:在需要展示用户信息的地方,从cookie中获取用户信息,并将其渲染到页面上。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/393401.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一个很牛逼的保存页面为html的插件

目录 安装 使用 今天突然发现一个牛逼的插件 直接上链接 https://github.com/gildas-lormeau/SingleFile 安装 SingleFile can be installed on: Firefox: SingleFile – Get this Extension for &#x1f98a; Firefox (en-US)Firefox for Android: SingleFile – Get thi…

C++11新特性(深度剖析+总结)

文章目录 1. 前言1. C11简介2. 统一的列表初始化2.1 {} 初始化2.2 std::initializer_list 3. 声明3.1 auto3.2 decltype3.3 nullptr 4. 范围for循环5. STL中的一些变化6. 右值引用和移动语义6.1 左值引用和右值引用6.2 左值引用与右值引用比较6.3 右值引用使用场景和意义6.4 右…

【Java多线程】线程中几个常见的属性以及状态

目录 Thread的几个常见属性 1、Id 2、Name名称 3、State状态 4、Priority优先级 5、Daemon后台线程 6、Alive存活 Thread的几个常见属性 1、Id ID 是线程的唯一标识&#xff0c;由系统自动分配&#xff0c;不同线程不会重复。 2、Name名称 用户定义的名称。该名称在各种…

Mac软件打开提示:已损坏,无法打开。您应该将它移到废纸娄 怎么解决?

新入手的苹果电脑打开软件出现&#xff1a;“已损坏&#xff0c;无法打开。您应该将它移到废纸娄” 或 “已损坏&#xff0c;打不开。推出磁盘映像”。这个怎么解决&#xff1f; 第一部分&#xff1a;&#xff08;注意&#xff1a;任何来源打开过了的&#xff0c;就直接去看下…

医用软管用双轴测径仪 外径与椭圆度的双重检测!

摘要&#xff1a;软管的一大特点就是容易产生形变&#xff0c;接触式测量稍施压力可能导致测量不准&#xff0c;因此非接触式的高精高速测径仪被广泛的应用于生产中。 关键词&#xff1a;双轴测径仪,医用软管测径仪,软管测径仪,测径仪,软管外径测量仪 引言 非接触式的外径测量仪…

山西电力市场日前价格预测【2024-02-18】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2024-02-18&#xff09;山西电力市场全天平均日前电价为347.21元/MWh。其中&#xff0c;最高日前电价为535.89元/MWh&#xff0c;预计出现在07:45。最低日前电价为165.05元/MWh&#xff0c;预计…

数据分析 — 动画图 pyecharts

目录 一、概念二、安装和导入三、绘图逻辑四、绘图1、柱状图2、折线图3、散点图4、饼图5、南丁格尔图6、Geo() 地理坐标第7、Map() 绘制区域8、词云图9、层叠图10、3D 图11、仪表板 一、概念 Pyecharts 是一个基于 Echarts 的 Python 可视化库&#xff0c;它通过 Python 生成 …

【动态规划】【C++算法】2742. 给墙壁刷油漆

作者推荐 【数位dp】【动态规划】【状态压缩】【推荐】1012. 至少有 1 位重复的数字 本文涉及知识点 动态规划汇总 LeetCode2742. 给墙壁刷油漆 给你两个长度为 n 下标从 0 开始的整数数组 cost 和 time &#xff0c;分别表示给 n 堵不同的墙刷油漆需要的开销和时间。你有…

差异表达分析和PPI网络图构建

原文链接&#xff1a;差异分析和PPI网路图绘制教程 写在前面 在原文中&#xff0c;作者获得285个DEG&#xff0c;在此推文中共获得601个DEG。小杜的猜想是标准化的水段不同的原因吧&#xff0c;或是其他的原因。此外&#xff0c;惊奇的发现发表医学类的文章在附件中都不提供相…

【MySQL】学习多表查询和笛卡尔积

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-N8PeTKG6uLu4bJuM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

vue自定义指令(图文示例)

第085个 查看专栏目录: VUE 本文章目录 示例效果图示例源代码API 参考网址 Vue 自定义指令是一种用于扩展 Vue 模板功能的强大工具。通过自定义指令&#xff0c;你可以在 Vue 模板中添加自定义的行为和逻辑&#xff0c;使模板更加灵活和可定制。 以下是对 Vue 自定义指令的详细…

基于springboot车辆充电桩管理系统源码和论文

随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;车辆充电桩管理系统也不例外&#xff0c;但目前国内仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;同时信息量也越来越庞大&#xff0c;人工管理显然已无法应对时代的变化&#xff0c;…

线程池如何知道一个线程的任务已经执行完成

一个小伙伴私信了一个小米的面试题&#xff0c;问题是&#xff1a; “线程池如何知道一个线程的任务已经执行完成”&#xff1f; 说实话&#xff0c;这个问题确实很刁钻&#xff0c;毕竟像很多工作 5 年多的小伙伴&#xff0c;连线程池都没用过&#xff0c;怎么可能回答出来这个…

2024.02.18作业

1. 使用fgets统计给定文件的行数 #include <stdio.h> #include <stdlib.h> #include <string.h>int main(int argc, char const *argv[]) {if (argc ! 2){puts("input file error");puts("usage:./a.out filename");return -1;}FILE* f…

【论文阅读笔记】Contrastive Learning with Stronger Augmentations

Contrastive Learning with Stronger Augmentations 摘要 基于提供的摘要&#xff0c;该论文的核心焦点是在对比学习领域提出的一个新框架——利用强数据增强的对比学习&#xff08;Contrastive Learning with Stronger Augmentations&#xff0c;简称CLSA&#xff09;。以下…

黑猫带你学NandFlash第5篇:NAND的封装与引脚定义

本文依据ONFI5.1及个人工作经验整理而成&#xff0c;如有错误请留言。 文章为付费内容&#xff0c;已加入原创侵权保护&#xff0c;禁止私自转载及抄袭。 文章所在专栏&#xff1a;《黑猫带你学&#xff1a;NandFlash详解》 1 封装类型 spec中规定nand封装尺寸有以下几种&…

.螺旋矩阵

54. 螺旋矩阵 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5]示例 2&#xff1a; 输入&#xff1a;ma…

【FastAPI】P1 安装与第一个 FastAPI 应用

目录 FastAPI 安装第一个 FastAPI 应用代码拆解分析 FastAPI 安装 FastAPI 是用于快速构建 API 的 web 框架&#xff0c;依赖 Python 3.8 及更高版本。使用 pip 命令安装 fastapi&#xff1a; pip install fastapi安装异步处理 ASGI 的服务器 Uvicorn&#xff1a; pip insta…

HAL STM32通过multi_button库处理按键事件

HAL STM32通过multi_button库处理按键事件 &#x1f4cd;作者&#xff1a;0x1abin的multi_button库:https://github.com/0x1abin/MultiButton &#x1f4d8;MultiButton简介 MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块&#xff0c;可无限量扩展按键&#xff0c;…

分享一个学英语的网站

名字叫&#xff1a;公益大米网​​​​​​​ Freerice 这个网站是以做题的形式来记忆单词&#xff0c;题干是一个单词&#xff0c;给出4个选项&#xff0c;需要选出其中最接近题干单词的选项。 答对可以获得10粒大米&#xff0c;网站的创办者负责捐赠。如图 触发某些条件&a…