【electron+vue3】使用JustAuth实现第三方登录(前后端完整版)

实现过程

  1. 去第三方平台拿到client-id和client-secret,并配置一个能够外网访问回调地址redirect-uri供第三方服务回调
  2. 搭建后端服务,引入justauth-spring-boot-starter直接在配置文件中定义好第一步的三个参数,并提供获取登录页面的接口和回调接口
  3. 前端项目中新建一个登录窗口和一个登录中转页面,登录窗口的url从第二步第一个接口获取,中转页面从第二步的第二个接口返回
  4. 中转页面从url中读取登录成功的用户信息并存放到pinia中,关闭登录窗口并刷新主窗口

1,必要信息获取

第三方平台的client-id和client-secret一般注册开发者平台都能获取。
回调地址需要外网,可以使用花生壳内网穿透随便搞一个,映射到本地的后台服务端口,当后天服务启动成功后确保连接成功
在这里插入图片描述
前端代理也可以直接代理到这个域名,前后端完全分离

2,后台服务搭建

2.1 后台如果使用springboot2.x可以从开源框架直接使用:

https://gitee.com/justauth/justauth-spring-boot-starter
只需将上一步获取的三个参数配置到yml文件中

2.2 AuthRequestFactory错误

如果使用的springboot3.x,可能会报错提示:

‘com.xkcoding.justauth.AuthRequestFactory’ that could not be found.

只需要将AuthRequestFactory、JustAuthProperties、AuthStateRedisCache从源码复制一份到项目中,补全@Configuration、@Component,然后补上一个Bean即可

    @Beanpublic AuthRequestFactory getAuthRequest(JustAuthProperties properties, AuthStateRedisCache authStateCache) {return new AuthRequestFactory(properties,authStateCache);}
2.3 redis错误

justauth-spring-boot-starter项目中的redis配置是springboot2.x的配置,
如果是3.x的项目需要将 spring:reids改为 spring:data:reids

2.4 代码案例
import com.alibaba.fastjson.JSONObject;
import io.geekidea.springboot.cache.AuthRequestFactory;
import io.geekidea.springboot.service.UserService;
import io.geekidea.springboot.vo.ResponseResult;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthConfig;
import io.geekidea.springboot.cache.JustAuthProperties;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthBaiduRequest;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** @Description https://blog.csdn.net/weixin_46684099/article/details/118297276* @Date 2024/10/23 16:30* @Author 余乐**/
@Slf4j
@Controller
@RequestMapping("/oauth")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class JustAuthController {private final UserService userService;private final AuthRequestFactory factory;private final JustAuthProperties properties;@GetMappingpublic List<String> list() {return factory.oauthList();}@RequestMapping("/render/{source}")@ResponseBodypublic ResponseResult renderAuth(@PathVariable("source") String source) {AuthRequest authRequest = null;//特定平台需要自定义参数的可以单独写AuthConfigif ("baidu".equals(source)) {//百度账号默认只有basic,需要网盘权限需要单独定义List<String> list = new ArrayList<>();list.add("basic");list.add("netdisk");Map<String,AuthConfig> configMap = properties.getType();AuthConfig authConfig = configMap.get("BAIDU");authConfig.setScopes(list);authRequest = new AuthBaiduRequest(authConfig);} else {//其他平台账号登录authRequest = factory.get(source);}String state = AuthStateUtils.createState();String authorizeUrl = authRequest.authorize(state);return ResponseResult.success(authorizeUrl);}/*** oauth平台中配置的授权回调地址,以本项目为例,在创建github授权应用时的回调地址应为:http://127.0.0.1:8444/oauth/callback/github*/@RequestMapping("/callback/{source}")public void login(@PathVariable("source") String source, AuthCallback callback, HttpServletResponse response2) throws IOException {log.info("进入callback:{},callback params:{}", source, JSONObject.toJSONString(callback));AuthRequest authRequest = null;//特定平台需要自定义参数的可以单独写AuthConfigif ("baidu".equals(source)) {//百度账号默认只有basic,需要网盘权限需要单独定义List<String> list = new ArrayList<>();list.add("basic");list.add("netdisk");Map<String,AuthConfig> configMap = properties.getType();AuthConfig authConfig = configMap.get("BAIDU");authConfig.setScopes(list);authRequest = new AuthBaiduRequest(authConfig);} else {//其他平台账号登录authRequest = factory.get(source);}AuthResponse<AuthUser> response = authRequest.login(callback);String userInfo = JSONObject.toJSONString(response.getData());log.info("回调用户信息:{}", userInfo);if (response.ok()) {userService.save(response.getData());String userInfoParam = URLEncoder.encode(userInfo, "UTF-8");//将用户信息放到中转页面的路由参数中,前端从路由参数获取登陆结果response2.sendRedirect("http://localhost:5173/loginback?data=" + userInfoParam);}}/*** 注销登录 (前端需要同步清理用户缓存)** @param source* @param uuid* @return* @throws IOException*/@RequestMapping("/revoke/{source}/{uuid}")@ResponseBodypublic ResponseResult revokeAuth(@PathVariable("source") String source, @PathVariable("uuid") String uuid) throws IOException {AuthRequest authRequest = factory.get(source.toLowerCase());AuthUser user = userService.getByUuid(uuid);if (null == user) {return ResponseResult.fail("用户不存在");}AuthResponse<AuthToken> response = null;try {response = authRequest.revoke(user.getToken());if (response.ok()) {userService.remove(user.getUuid());return ResponseResult.success("用户 [" + user.getUsername() + "] 的 授权状态 已收回!");}return ResponseResult.fail("用户 [" + user.getUsername() + "] 的 授权状态 收回失败!" + response.getMsg());} catch (AuthException e) {return ResponseResult.fail(e.getErrorMsg());}}/*** 刷新token** @param source* @param uuid* @return*/@RequestMapping("/refresh/{source}/{uuid}")@ResponseBodypublic ResponseResult<String> refreshAuth(@PathVariable("source") String source, @PathVariable("uuid") String uuid) {AuthRequest authRequest = factory.get(source.toLowerCase());AuthUser user = userService.getByUuid(uuid);if (null == user) {return ResponseResult.fail("用户不存在");}AuthResponse<AuthToken> response = null;try {response = authRequest.refresh(user.getToken());if (response.ok()) {user.setToken(response.getData());userService.save(user);return ResponseResult.success("用户 [" + user.getUsername() + "] 的 access token 已刷新!新的 accessToken: " + response.getData().getAccessToken());}return ResponseResult.fail("用户 [" + user.getUsername() + "] 的 access token 刷新失败!" + response.getMsg());} catch (AuthException e) {return ResponseResult.fail(e.getErrorMsg());}}
}

3 新建登录窗口和中转页面

3.1 在src/main/index.ts中新增登录窗口
let loginWindow//监听打开登录窗口的事件
ipcMain.on('openLoginWin', (event, url) => {console.log('打开登录窗口', url)createLoginWindow(url)
})// 创建登录窗口
function createLoginWindow(url: string) {loginWindow = new BrowserWindow({width: 800,height: 600,frame: false,titleBarStyle: 'hidden',   autoHideMenuBar: true,parent: mainWindow,   //父窗口为主窗口modal: true,show: false,webPreferences: {preload: join(__dirname, '../preload/index.js'),nodeIntegration: true,contextIsolation: true}})// 加载登录 URLloginWindow.loadURL(url)loginWindow.on('ready-to-show', () => {loginWindow.show()})
}// 关闭登录窗口并刷新主窗口
ipcMain.handle('close-login', () => {if (loginWindow) {loginWindow.close()}if (mainWindow) {mainWindow.reload() // 刷新主窗口 }}
})
3.2 新增中转页面并配置路由

@/views/setting/LoginBack.vue

<template><el-row justify="center"><cl-col :span="17"><h2>登陆结果</h2><el-icon style="color:#00d28c;font-size: 50px"><i-mdi-check-circle /></el-icon></cl-col></el-row>
</template><script setup lang="ts">
import { useRoute } from 'vue-router'
import { onMounted } from 'vue'
import { useThemeStore } from '@/store/themeStore'const route = useRoute()
const data = route.query.data
const themeStore = useThemeStore()
//登陆成功自动关闭窗口
onMounted(() => {console.log("登陆结果",data)themeStore.setCurrentUser(JSON.parse(data))setTimeout(() => {//关闭当前登录回调的窗口,并且刷新主窗口页面window.electron.ipcRenderer.invoke('close-login')}, 1000)
})
</script>
3.3 新增路由
{path: 'loginback', component: ()=>import("@/views/setting/LoginBack.vue"),},

这里的路由对应的就是后台/callback 接口重定向的地址

4.管理用户登录信息

后端用户登录信息保存在redis中,如果过期可以使用客户端中缓存的用户uuid刷新token

前端的一般是使用pinia做持久化维护,安装piniad 插件
pinia-plugin-persistedstate
新增用户themeStore.ts

import { defineStore } from 'pinia';export const useThemeStore = defineStore('userInfoStore', {state: () => {// 从 localStorage 获取主题,如果没有则使用默认值//const localTheme = localStorage.getItem('localTheme') || 'cool-black';return {currentTheme: 'cool-black',userInfo: {}};},actions: {setCurrentThemeId(theme: string) {console.log("修改主题", theme);this.currentTheme = theme; // 更新当前主题document.body.setAttribute('data-theme', theme); // 更新 data-theme},setCurrentUser(user: any) {console.log("修改账号", user);this.userInfo = user; // 更新当前账号},},//开启持久化 = 》 localStoragepersist: {key: 'userInfoStore',onstorage: localStorage,path: ['currentTheme','userInfo']}
});

5. 运行调试

5.1 在顶部登录页面
<div v-if="userInfo.avatar"><el-avatar :src="userInfo.avatar" :size="30"/><el-popover :width="300" trigger="click"><template #reference><p>{{userInfo.nickname}}</p></template><template #default><div  class="demo-rich-conent" style="display: flex; gap: 16px; flex-direction: column"><el-avatar:size="60"src="https://avatars.githubusercontent.com/u/72015883?v=4"style="margin-bottom: 8px"/><el-divider /><h5 @click="logout(userInfo.uuid)">退出登录</h5></div></template></el-popover></div><div v-else @click.stop="openLoginCard"><el-avatar :icon="UserFilled" :size="30"/><p>未登录</p></div><script lang="ts" setup>
import {ref} from 'vue'
import { LoginOut } from '@/api/baidu'
import {useThemeStore} from "@/store/themeStore";
import { UserFilled } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
import { getLoginPageUrl } from '../../api/baidu'
const themeStore = useThemeStore();
const router = useRouter()
let searchVal = ref('')
let userInfo=ref({})if (themeStore.userInfo){userInfo.value = themeStore.userInfo
}
//打开登录弹窗
function openLoginCard(){getLoginPageUrl().then(resp => {console.log("获取登陆地址",resp.data)window.electron.ipcRenderer.send('openLoginWin',resp.data.data)});
}
//退出登录
function logout(uuid:string){LoginOut(uuid).then(resp => {console.log("注销登录",resp.data)themeStore.setCurrentUser({})window.location.reload()});
}
</script>

在这里插入图片描述
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vscode makfile编译c程序

编译工具安装 为了在 Windows 上安装 GCC&#xff0c;您需要安装 MinGW-w64。 MinGW-w64 是一个开源项目&#xff0c;它为 Windows 系统提供了一个完整的 GCC 工具链&#xff0c;支持编译生成 32 位和 64 位的 Windows 应用程序。 1. 下载MinGW-w64源代码&#xff0c;如图点…

这个操作惊呆我了!海康存储 R1竟然可以这样部署Portainer

这个操作惊呆我了&#xff01;海康存储 R1竟然可以这样部署Portainer 哈喽小伙伴们好&#xff0c;我是Stark-C~ 最近到手了海康存储&#xff08;HIKVISION&#xff09;私有云R1 &#xff0c;该机的卖点还是很多的&#xff0c;比如优秀的做工&#xff0c;强大的配置&#xff0…

雷电模拟器ls内部操作adb官方方法

正常情况下&#xff0c;我们通过adb操作模拟器&#xff0c;如安装软件、运行shell命令等&#xff0c;但是用windows系统&#xff0c;adb就经常掉线&#xff0c;端口被占用&#xff0c;或者发现不到设备&#xff0c;对于调试或者自动化非常痛苦。就在雷电安装目录下&#xff0c;…

TS学习笔记

一、TS运行环境搭建 1、安装 安装命令 npm i -g typescript 第一步&#xff1a;新建index.html和demo.ts 第二步&#xff1a;在index.html引入demo.ts文件 第三步&#xff1a;运行TS的命令 tsc demo.ts 注意&#xff1a;运行命令后&#xff0c;会将ts文件转换成js文件 …

使用Jest进行JavaScript单元测试

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用Jest进行JavaScript单元测试 引言 Jest 简介 安装 Jest 创建基本配置 编写测试用例 运行测试 快照测试 模拟函数 代码覆盖率…

机器学习算法之回归算法

一、回归算法思维导图 二、算法概念、原理、应用场景和实例代码 1、线性回归 1.1、概念 ‌‌线性回归算法是一种统计分析方法&#xff0c;用于确定两种或两种以上变量之间的定量关系。‌ 线性回归算法通过建立线性方程来预测因变量&#xff08;y&#xff09;和一个或多个自变量…

Android中同步屏障(Sync Barrier)介绍

在 Android 中&#xff0c;“同步屏障”&#xff08;Sync Barrier&#xff09;是 MessageQueue 中的一种机制&#xff0c;允许系统临时忽略同步消息&#xff0c;以便优先处理异步消息。这在需要快速响应的任务&#xff08;如触摸事件和动画更新&#xff09;中尤为重要。 在 An…

突破职场瓶颈,实现个人成长

在职场生涯中&#xff0c;我们总会遇到各种各样的瓶颈。这些瓶颈如同成长道路上的荆棘&#xff0c;让我们感到困惑、焦虑甚至恐惧。然而&#xff0c;瓶颈并非无法逾越&#xff0c;只要我们掌握正确的方法&#xff0c;勇敢面对&#xff0c;就能顺利突破&#xff0c;实现个人成长…

ubuntu 24.04中安装 Easyconnect,并解决版本与服务器不匹配问题

下载安装包 下载地址 https://software.openkylin.top/openkylin/yangtze/pool/all/ 页面搜索 easyconnect 选择 easyconnect_7.6.7.3.0_amd64.deb安装 sudo dpkg --install easyconnect_7.6.7.3.0_amd64.deb卸载 sudo dpkg --remove easyconnect出现的问题 安装以后第…

判断是否是变位词

题目&#xff1a;给定两个单词&#xff0c;判断这两个单词是否是变位词。如果两个单词的字母完全相同&#xff0c;只是位置有所不同&#xff0c;则称这两个单词为变位词。例如eat和tea是变位词。 答&#xff1a;问题分析&#xff1a;判断是否为变位词&#xff0c;只需要分别统计…

解决python matplotlib画图无法显示中文的问题

在用matplotlib做一个简单的可视化统计时&#xff0c;由于标签是中文&#xff0c;无法显示&#xff0c;只是显示出来一些方框&#xff08;如图&#xff09; 问题在于&#xff0c;当前matplotlib使用的字体不支持中文&#xff0c;我们进行替换就可以了 我想替换为黑体&#xff…

Docker:网络

Docker&#xff1a;网络 Docker 网络架构CNMLibnetwork驱动网络类型 命令docker network lsdocker network inspectdocker network createdocker network connectdocker network disconnectdocker network prunedocker network rm 网络操作bridgehostcontainernone Docker 网络…

力扣排序268题 数字丢失

题目&#xff1a; 丢失的数字 给定一个包含[0,n]中n各数的数组nums&#xff0c;找出[0,n]这个范围 内没有出现在数组中的那个数。 示例1&#xff1a; 输出&#xff1a;n 3,因为有3个数字&#xff0c;所以所有的数字都在范围 [0,3]内。2是丢失的数字&#xff0c;因为它没有出现…

自动化测试类型与持续集成频率的关系

持续集成是敏捷开发的一个重要实践&#xff0c;可是究竟多频繁的集成才算“持续”集成&#xff1f; 一般来说&#xff0c;持续集成有3种常见的集成频率&#xff0c;分别是每分钟集成、每天集成和每迭代集成。项目组应当以怎样的频率进行集成&#xff0c;这取决于测试策略&…

Gitlab-runner running on Kubernetes - hostAliases

*Config like this. *That in your helm values.yaml.

从头开始学PHP之面向对象

首先介绍下最近情况&#xff0c;因为最近入职了且通勤距离较远&#xff0c;导致精力不够了&#xff0c;而且我发现&#xff0c;人一旦上了班&#xff0c;下班之后就不想再进行任何脑力劳动了&#xff08;对大部分牛马来说&#xff0c;精英除外&#xff09;。 话不多说进入今天的…

【综合算法学习】(第十五篇)

目录 图像渲染&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 岛屿数量&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 图像渲染&#xff08;medium&#xff09; 题目解析 1.题目链接&#xff1a;. - 力扣&#xff08;LeetCode&…

教育技术革新:SpringBoot在线试题库系统开发

2 相关技术 2.1 Spring Boot框架简介 Spring Boot是由Pivotal团队提供的全新框架&#xff0c;其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置&#xff0c;从而使开发人员不再需要定义样板化的配置。通过这种方式&#xff0c;Sprin…

OTFS延迟多普勒信道模型(信道模型代码)

一、信道模型公式 1、延迟多普勒域信道模型 在一个M*N维的延迟多普勒域中&#xff0c;定义M为子载波数&#xff0c;子载波间隔为 对应倒数时隙长度为&#xff0c;信号总长度为,L-1表示最大径数。 公式中冲激响应延迟域移动的分辨率&#xff0c;如下图中Delay轴的一格也就是即,多…

Failed to search for file: Cannot update read-only repo

今天在读《Linux就该这么学》并上机操作RedHat Linux 8。结果在执行指令时却出现了问题: 我明明已经是root权限了&#xff0c;我于是上网去找&#xff0c;但也没看到合适的解答。为什么会和书上的操作结果不一样。 后来我突然意识到是不是我打了不该打的空格&#xff0c;于是…