当前位置: 首页 > news >正文

【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>

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

相关文章:

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

实现过程 去第三方平台拿到client-id和client-secret&#xff0c;并配置一个能够外网访问回调地址redirect-uri供第三方服务回调搭建后端服务&#xff0c;引入justauth-spring-boot-starter直接在配置文件中定义好第一步的三个参数&#xff0c;并提供获取登录页面的接口和回调…...

Amcor 如何借助 Liquid UI 实现SAP PM可靠性

背景介绍 安姆科是塑料行业的全球领军企业&#xff0c;该企业认识到 SAP 工厂维护&#xff08;SAP PM&#xff09;对于确保高效的维护管理的重要性。 在诸如制造业等高度依赖机械设备的行业中&#xff0c;SAP PM是一种通过数据驱动决策来最大限度减少停机时间、降低间接成本、…...

【Redis】常见基本全局命令

一、Redis俩大核心命令 由于Redis是以键值对的形式进行数据存取&#xff0c;自然就离不开不断的存储和获取&#xff0c;而其所对应的命令则是set和get&#xff0c;如此说来二者为Redis的核心基础命令也不为过。 作用&#xff1a;用于存储Stirng类型的数据 返回&#xff1a;当…...

探索国际数据空间(IDS)架构(上)

在当今数字化时代&#xff0c;数据的重要性日益凸显&#xff0c;而国际数据空间&#xff08;IDS&#xff09;作为一个新兴的概念&#xff0c;正逐渐成为数据管理和共享的关键领域。今天&#xff0c;我们就来一起探索一下 IDS 的精妙架构。 参考文章&#xff1a;国际数据空间&am…...

如何选择好用的U盘数据恢复软件免费版?2024年热门榜单有哪些?

U盘是我们用来存数据的小玩意儿&#xff0c;又方便又好用。但是&#xff0c;有时候因为不小心删掉了、格式化了或者中病毒了&#xff0c;U盘里的东西就没了&#xff0c;这可让人头疼。好在有很多免费的U盘数据恢复软件能帮我们找回这些丢失的数据。那怎么挑一个好用的免费数据恢…...

音视频入门基础:AAC专题(12)——FFmpeg源码中,解码AudioSpecificConfig的实现

音视频入门基础&#xff1a;AAC专题系列文章&#xff1a; 音视频入门基础&#xff1a;AAC专题&#xff08;1&#xff09;——AAC官方文档下载 音视频入门基础&#xff1a;AAC专题&#xff08;2&#xff09;——使用FFmpeg命令生成AAC裸流文件 音视频入门基础&#xff1a;AAC…...

UDP组播测试

支持组播的接口&#xff1a; ip a | grep MULTICAST 环回接口虽然显示不支持组播&#xff0c;实际也可以用于本地测试。 添加路由&#xff08;非必须&#xff1f;&#xff09;&#xff1a; ip route add 239.0.0.0/24 via 10.10.10.206 dev eth0 开放防火墙&#xff1a; 查…...

【Nas】X-Doc:jellyfin“该客户端与媒体不兼容,服务器未发送兼容的媒体格式”问题解决方案

【Nas】X-Doc&#xff1a;jellyfin“该客户端与媒体不兼容&#xff0c;服务器未发送兼容的媒体格式”问题解决方案 当使用Jellyfin播放视频时出现“该客户端与媒体不兼容&#xff0c;服务器未发送兼容的媒体格式”&#xff0c;这是与硬件解码和ffmpeg设置有关系&#xff0c;具体…...

504 Gateway Time-outopenresty

504 Gateway Time-out openresty 问题背景&#xff1a; 当自己点开知乎页面以后&#xff0c;发现官网没有出现任何问题&#xff0c;点击官网以后开始出现各种各样的报错&#xff01; 一下是来源ai的介绍&#xff1a;&#xff08;通过搜索这种形式帮助自己进行记忆&#xff09;…...

SpringBoot篇(自动装配原理)

目录 一、自动装配机制 1. 简介 2. 自动装配主要依靠三个核心的关键技术 3. run()方法加载启动类 4. 注解SpringBootApplication包含了多个注解 4.1 SpringBootConfiguration 4.2 ComponentScan 4.3 EnableAutoConfiguration 5. SpringBootApplication一共做了三件事 …...

《Web性能权威指南》-WebRTC-读书笔记

本文是《Web性能权威指南》第四部分——WebRTC的读书笔记。 第一部分——网络技术概览&#xff0c;请参考网络技术概览&#xff1b; 第二部分——无线网络性能&#xff0c;请参考无线网络性能&#xff1b; 第三部分——HTTP&#xff0c;请参考HTTP&#xff1b; 第四部分——浏览…...

跨境电商独立站:打造你的全球品牌

什么是跨境电商独立站&#xff1f; 跨境电商独立站是指一个独立的电子商务网站&#xff0c;企业可以通过这个网站直接向全球消费者销售产品。与入驻亚马逊、eBay等第三方平台不同&#xff0c;独立站拥有完全自主权&#xff0c;可以自由定制店铺风格、营销策略&#xff0c;并直…...

基于uniapp微信小程序的旅游系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…...

怿星科技薛春宇丨智能汽车软件研发工具链国产化的挑战和探索

2024年7月25日&#xff0c;由上海良益企业管理咨询有限公司主办的“2024域控制器技术论坛“在上海成功举办&#xff0c;十位嘉宾做了精彩分享。“整零有道”将陆续刊出部分演讲的文字实录&#xff0c;以飨读者。 本期刊出怿星科技副总经理薛春宇的演讲实录&#xff1a;智能汽车…...

Flutter动画渐变

User experience is everything. One way to improve it is by making transitions between different UI elements smoother and more visually appealing. This is where the AnimatedCrossFade widget comes in handy. 用户体验就是一切。改善用户体验的方法之一就是让不同…...

Python毕业设计选题:基于Web学生会网站的设计与实现-django

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 系统首页界面 用户注册界面 用户登录界面 校内报道界面 品牌活动界面 个人中心界面 …...

如何选购高性价比百元头戴式耳机?六大选购技巧加性价比耳机推荐

在日益繁忙的生活中&#xff0c;头戴式耳机已成为许多人享受音乐、放松心情的重要工具。然而&#xff0c;市面上的头戴式耳机种类繁多&#xff0c;价格各异&#xff0c;如何选购高性价比百元头戴式耳机&#xff1f;成为了许多消费者的难题。为了帮助大家更好地做出选择&#xf…...

Java爬虫的京东“寻宝记”:揭秘商品类目信息

开篇&#xff1a;Java特工的神秘任务 在这个数据驱动的时代&#xff0c;我们就像一群特工&#xff0c;穿梭在数字的海洋中&#xff0c;寻找着隐藏的宝藏——商品类目信息。今天&#xff0c;我们将带领你一起&#xff0c;用Java这把精密的瑞士军刀&#xff0c;深入京东的神秘领…...

React前端框架

React 是一个用于构建用户界面的 JavaScript 库&#xff0c;由 Facebook 开发和维护。React 采用组件化的开发方式&#xff0c;使得开发者可以构建可复用的 UI 组件&#xff0c;从而提高开发效率和代码的可维护性。 React 的基本概念 组件&#xff1a;React 的核心概念是组件…...

React-query vs. 神秘新工具:前端开发的新较量

流畅的分页体验&#xff1a;AlovaJS的分页请求策略 在现代web应用中&#xff0c;分页是一个常见的功能需求。无论是浏览商品列表、查看文章集合&#xff0c;还是管理后台的数据表格&#xff0c;用户都需要一种高效且流畅的方式来浏览大量数据。然而&#xff0c;实现一个流畅且…...

s2-pro语音合成新玩法:用标签控制语气,轻松制作带情绪的语音内容

s2-pro语音合成新玩法&#xff1a;用标签控制语气&#xff0c;轻松制作带情绪的语音内容 1. 语音合成技术的新突破 在数字内容创作领域&#xff0c;语音合成技术正变得越来越重要。传统的语音合成系统往往只能生成单调、机械的语音&#xff0c;缺乏情感表达和自然韵律。而s2-…...

SGMICRO圣邦微 SGM803B-JXN3G/TR SOT-23-3 监控和复位芯片

特性 适用于MAX803/MAX809/MAX810和ADM803/ADM809/ADM810的卓越升级版 高精度固定检测选项:3V、3.3V和5V 低供电电流:300nA(典型值)上电复位脉冲宽度:150毫秒(最小值) 复位输出选项: 开漏nRESET输出(SGM803B)推挽nRESET输出(SGM809B) . . 推挽复位输出(SGM810B)复位有效电压低至…...

5分钟零代码部署:Live2D AI虚拟助手让你的网站活起来

5分钟零代码部署&#xff1a;Live2D AI虚拟助手让你的网站活起来 【免费下载链接】live2d_ai 基于live2d.js实现的动画小人ai&#xff0c;拥有聊天功能&#xff0c;还有图片识别功能&#xff0c;可以嵌入到网页里 项目地址: https://gitcode.com/gh_mirrors/li/live2d_ai …...

新手必看:Neeshck-Z-lmage_LYX_v2界面状态管理,让你的设置不再丢失

新手必看&#xff1a;Neeshck-Z-lmage_LYX_v2界面状态管理&#xff0c;让你的设置不再丢失 1. 工具简介&#xff1a;为什么需要状态管理&#xff1f; 当你第一次打开Neeshck-Z-lmage_LYX_v2这个绘画工具时&#xff0c;可能会被它简洁的界面所吸引。但真正让它与众不同的&…...

Vivado 时序约束文件 (.xdc) 管理与维护实战指南:从单文件到团队协作

Vivado 时序约束文件 (.xdc) 管理与维护实战指南&#xff1a;从单文件到团队协作 在FPGA设计流程中&#xff0c;时序约束文件&#xff08;.xdc&#xff09;如同交通信号灯&#xff0c;为设计指明方向与规则。随着项目规模扩大和团队协作需求增加&#xff0c;如何高效管理这些约…...

isaac lab5.0与ROS2通信

问题&#xff1a;isaac lab 5.0是基于python3.11 ros2是基于python3.10&#xff0c;因此不能在isaac sim的代码中直接写ros2的代码 在isaac sim中加import socketdef send_to_ros2(v, w):try:sock socket.socket(socket.AF_INET, socket.SOCK_STREAM)sock.connect((127.0.0.1…...

解决企业级流程建模挑战:基于Vue与bpmn.js的Flowable工作流设计器深度集成指南

解决企业级流程建模挑战&#xff1a;基于Vue与bpmn.js的Flowable工作流设计器深度集成指南 【免费下载链接】workflow-bpmn-modeler &#x1f525; flowable workflow designer based on vue and bpmn.io7.0 项目地址: https://gitcode.com/gh_mirrors/wo/workflow-bpmn-mode…...

郭老师-悟性高的人,为何不合群?

悟性高的人&#xff0c;为何不合群&#xff1f; ——他们在独处中&#xff0c;与道同行“你以为他孤独&#xff0c; 其实—— 他正与万物对话。”&#x1f33f; 不合群&#xff0c;不是缺陷&#xff0c; 而是—— 为悟性留出呼吸的空间。&#x1f9d8; 一、独处 ≠ 孤独&#x…...

门店小程序和收银系统有什么区别?

门店小程序和收银系统有什么区别&#xff1f;在门店数字化过程中&#xff0c;很多企业会同时接触到小程序与收银系统&#xff0c;但两者在功能定位和使用场景上存在明显差异。门店小程序和收银系统的本质区别&#xff0c;在于一个偏向“获客与转化入口”&#xff0c;一个偏向“…...

PySide6多线程避坑指南:你的‘暂停’和‘停止’真的安全吗?

PySide6多线程避坑指南&#xff1a;你的‘暂停’和‘停止’真的安全吗&#xff1f; 在PySide6的多线程开发中&#xff0c;暂停和停止线程看似简单的操作背后&#xff0c;隐藏着许多开发者容易忽视的陷阱。本文将深入剖析这些潜在问题&#xff0c;并提供经过实战验证的安全解决方…...