基于Oauth2的SSO单点登录---前端

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/943732.html

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

相关文章

【Artificial Intelligence篇】AI 携手人类:共铸未来创作新纪元

引言&#xff1a; 随着科技的飞速发展&#xff0c;人工智能已逐渐渗透到各个领域&#xff0c;尤其是在创作领域&#xff0c;其与人类的合作展现出了前所未有的可能性和潜力。从艺术作品的生成到文学作品的创作&#xff0c;从复杂软件的开发到创新设计的构思&#xff0c;AI 正在…

Easy-Trans反向翻译+Excel导入最佳实践

1、概述 实现用户excel上传、解析、对于用户输入的中文翻译为字典码或者id&#xff0c;实现用户输入的参数校验&#xff0c;最后入库。如果用户输入的参数有问题&#xff0c;返回校验结果给前端。 excel解析使用My-Excel组件&#xff0c;校验使用hibernate-validator&#xff…

OpenCV-Python实战(6)——图相运算

一、加法运算 1.1 cv2.add() res cv2.add(img1,img2,dstNone,maskNone,dtypeNone) img1、img2&#xff1a;要 add 的图像对象。&#xff08;shape必须相同&#xff09; mask&#xff1a;图像掩膜。灰度图&#xff08;维度为2&#xff09;。 dtype&#xff1a;图像数据类型…

Leetcode打卡:查询数组中元素出现的位置

执行结果&#xff1a;通过 题目 3159 查询数组中元素出现的位置 给你一个整数数组 nums &#xff0c;一个整数数组 queries 和一个整数 x 。 对于每个查询 queries[i] &#xff0c;你需要找到 nums 中第 queries[i] 个 x 的位置&#xff0c;并返回它的下标。如果数组中 x 的出…

向量组学习

向量组的秩及其线性组合 线性相关性 先看a1,a2 如果这两个向量不对应成比例的话,那必然内部不可能存在多余的向量,也就是无关. 主元所在的列都是独立向量 ,最大无关组就是b1,b2,b4,但这个是初等行变换后的,题目要的是A的,与之对应的就是a1,a2,a4 方程组解的结构

影视仓最新接口+内置本包方法的研究(2024.12.27)

近日喜欢上了研究影视的本地仓库内置&#xff0c;也做了一个分享到了群里。 内置本地仓库包的好处很明显&#xff0c;当前线路接口都是依赖网络上的代码站存放&#xff0c;如果维护者删除那就GG。 虽然有高手制作了很多本地包&#xff0c;但推送本地包到APP&#xff0c;难倒一片…

redis相关数据类型介绍

当然&#xff0c;Redis 作为一个高性能的键值存储系统&#xff0c;提供了多种数据类型来支持不同的应用场景。 1. String&#xff08;字符串&#xff09; • 定义&#xff1a;Redis 最基本的数据类型&#xff0c;用于存储字符串值。 • 操作&#xff1a;SET、GET、INCR、DECR、…

教师管理系统

大概功能&#xff1a; 1.显示所有教师 2.按姓名查找教师 3.按工号查找教师 4.增加教师 5.删除教师 6.退出 数据会保存到 txt 文件里面 姓名&#xff1a;必须是中文 手机号码&#xff1a;必须是11位&#xff0c;必须是数字 效果展示&#xff1a; 代码展示&#xff1a; Teache…

lombok-macros

GITHUB 地址 LTPP-GIT 地址 官方文档 API 文档 一组提供 Lombok 类似功能的 Rust 宏。 安装 要使用此 crate&#xff0c;可以运行以下命令&#xff1a; cargo add lombok-macros用法 use lombok_macros::*;/// 定义一个结构体&#xff0c;使用 Lombok 宏派生所需的方法 #…

uniapp开发微信小程序实现获取“我的位置”

1. 创建GetLocation项目 使用HBuilder X创建一个项目GetLocation,使用Vue3。 2. 在腾讯地图开放平台中创建应用 要获取位置,在小程序中需要使用腾讯地图或是高德地图。下面以腾讯地图为例。 (1)打开腾讯地图开放平台官方网址:腾讯位置服务 - 立足生态,连接未来 (2)注册…

Docker基础知识 Docker命令、镜像、容器、数据卷、自定义镜像、使用Docker部署Java应用、部署前端代码、DockerCompose一键部署

目录 1.Docker 2.镜像和容器 2.1 定义 2.2 开机自动启动容器 3.docker命令 3.1 docker run 参数说明 3.2 常见命令 3.3 命令演示 3.4 命令别名 4.Docker命令详解 5.数据卷 5.1 定义 5.2 数据卷的相关命令 5.3 数据卷命令 5.4 挂载本地目录或文件 5.4.1 定义 5.4.2 mysql容器目录…

Linux | Ubuntu零基础安装学习cURL文件传输工具

目录 介绍 检查安装包 下载安装 手册 介绍 ‌cURL是一个利用URL语法在命令行下工作的文件传输工具&#xff0c;首次发行于1997年‌‌12。cURL支持多种协议&#xff0c;包括FTP、FTPS、HTTP、HTTPS、TFTP、SFTP、Gopher、SCP、Telnet、DICT、FILE、LDAP、LDAPS、IMAP、POP3…

c# 2024/12/27 周五

6《详解类型、变量与对象》36 详解类型、变量与对象 _1_哔哩哔哩_bilibili

yarn list --pattern vuex-module-decorators

dgqdgqdeMac-mini spid-admin % yarn list --pattern vuex-module-decorators yarn list v1.22.22 └─ vuex-module-decorators0.16.1 ✨ Done in 0.24s.好的&#xff0c;这段代码是一个典型的 Vuex 模块定义&#xff0c;使用了 vuex-module-decorators 库。这个库为 Vuex 提…

uniapp 判断多选、选中取消选中的逻辑处理

一、效果展示 二、代码 1.父组件: :id=“this.id” : 给子组件传递参数【id】 @callParentMethod=“takeIndexFun” :给子组件传递方法,这样可以在子组件直接调用父组件的方法 <view @click="$refs.member.open()"

IDEA自己常用的几个快捷方式(自己的习惯)

TOC 背景 换工作了, 新的IDEA, 又要重新设置自己的快捷方式了. 灵感 1.这些个性话的配置应该是可以导出的. 然后在新的IDEA直接导入就行了, 感觉应该是有这个功能. 就是这个文件: <keymap version"1" name"Personal KeyMap" parent"$default…

学习AndroidPerfetto基础一

1.哔哩哔哩学习视频&#xff1a; Android Perfetto 基础和案例分享_哔哩哔哩_bilibili 2.Perfetto的简单介绍 Perfetto 是一个用于性能检测进而追踪分析的生产级开源工具 Perfetto提供上帝视角&#xff0c;背后需要整个Android系统的知识储备 Perfetto由Google开发&#x…

ffmpeg: stream_loop报错 Error while filtering: Operation not permitted

问题描述 执行ffmpeg命令的时候&#xff0c;报错&#xff1a;Error while filtering: Operation not permitted 我得命令如下 ffmpeg -framerate 25 -y -i /data/workerspace/mtk/work_home/mtk_202406111543-l9CSU91H1f1b3/tmp/%08d.png -stream_loop -1 -i /data/workerspa…

快速掌握Elasticsearch检索之二:滚动查询获取全量数据(golang)

Elasticsearch8.17.0在mac上的安装 Kibana8.17.0在mac上的安装 Elasticsearch检索方案之一&#xff1a;使用fromsize实现分页 1、滚动查询的使用场景 滚动查询区别于上一篇文章介绍的使用from、size分页检索&#xff0c;最大的特点是&#xff0c;它能够检索超过10000条外的…

StableAnimator模型的部署:复旦微软提出可实现高质量和高保真的ID一致性人类视频生成

文章目录 一、项目介绍二、项目部署模型的权重下载提取目标图像的关节点图像&#xff08;这个可以先不看先用官方提供的数据集进行生成&#xff09;提取人脸&#xff08;这个也可以先不看&#xff09;进行图片的生成 三、模型部署报错 一、项目介绍 由复旦、微软、虎牙、CMU的…