Springboot3+SpringSecurity6Oauth2+vue3前后端分离认证授权-客户端

客户端服务

  • 整体流程
  • 前端
    • 技术栈
    • 项目结构
    • 代码
  • 后端
    • 技术栈
    • 项目结构
    • 代码

整体流程

客户端前端客户端后端授权服务前端授权服务后端资源服务后端请求/hello接口无权限返回code=1001跳转到登录页请求登录/login接口返回授权服务获取授权码页面地址跳转到获取授权码页面请求获取授权码/oauth2/authorize接口无权限返回code=1001跳转到登录页请求登录/login接口验证用户密码登录成功返回token跳转回获取授权码页面带token请求获取授权码/oauth2/authorize接口返回授权码和客户端回调地址(带token)跳转到客户端回调地址(带token)请求回调/callback接口带token请求获取access_token的/oauth2/token接口返回access_token返回access_token跳转回最初始地址/带access_token请求/hello接口带access_token请求/authentication接口返回认证授权信息Authentication带Authentication走接下来流程返回/hello接口结果客户端前端客户端后端授权服务前端授权服务后端资源服务后端

前端

技术栈

vue3+vite4+axios+pinia+naiveui

项目结构

在这里插入图片描述

代码

vite.config.ts

import { fileURLToPath, URL } from 'node:url'import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'// https://vitejs.dev/config/
export default defineConfig({plugins: [vue(),],resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url))}},server: {port: 3001,open: false,proxy: {'/api': {changeOrigin: true,target: "http://localhost:8083",rewrite: (p) => p.replace(/^\/api/, '')}}}
})

HomeView.vue

<script setup lang="ts">
import { ref } from 'vue'
import axios from 'axios'
import { useRoute, useRouter } from 'vue-router'
import { useDialog } from 'naive-ui'const route = useRoute()
const router = useRouter()
const dialog = useDialog()const h = ref('')function init() {let accessToken = localStorage.getItem('access_token')if (accessToken && accessToken.length) {accessToken = 'Bearer ' + accessToken}axios.get('/api/hello', {headers: {Authorization: accessToken}}).then(r => {let data = r.datah.value = dataif (data && data.code && data.code == 1001) {dialog.warning({title: '未登录',content: '去登录?' + data.msg,positiveText: '确定',negativeText: '取消',draggable: true,onPositiveClick: () => {router.push(`/login?back=${encodeURIComponent(route.fullPath)}`)},onNegativeClick: () => {// message.error('取消')console.log('取消')}})}}).catch(e => {console.error(e)})
}init()
</script><template><main><p>{{ h }}</p></main>
</template>

Login.vue

<script setup lang="ts">
import axios from 'axios'
import { useRoute } from 'vue-router'const route = useRoute()function handleValidateClick() {axios.get('/api/login', {params: {callback: route.query.back}}).then(r => {window.location.href = r.data}).catch(e => {console.error(e)})
}handleValidateClick()
</script><template>
</template>

Callback.vue

<script setup lang="ts">
import { ref } from 'vue'
import axios from 'axios'
import { useRoute, useRouter } from 'vue-router'const route = useRoute()
const router = useRouter()
const t = ref('')function handleValidateClick() {axios.get('/api/callback', {params: {code: route.query.code,token: route.query.token}}).then(r => {let data = r.datalocalStorage.setItem('access_token', data.access_token)t.value = datarouter.push(route.query.back)}).catch(e => {console.error(e)})
}handleValidateClick()
</script><template><main><div>{{ t }}</div></main>
</template>

后端

技术栈

springboot3
spring security6 oauth2

项目结构

在这里插入图片描述

代码

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.example</groupId><artifactId>security</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>security-client</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-client</artifactId></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId></dependency></dependencies></project>

application.yml

logging:level:org.springframework.security: TRACEserver:port: 8083spring:security:oauth2:client:registration:client1:provider: client1client-id: client1client-secret: secretauthorization-grant-type: authorization_coderedirect-uri: http://localhost:3003/callbackscope:- openid- profile- allclient-name: client1provider:client1:issuer-uri: http://localhost:8081

SecurityConfig.java

package org.example.client.config;import com.alibaba.fastjson2.JSON;
import org.example.client.security.JwtTokenFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter;import java.util.HashMap;
import java.util.Map;/*** Spring security配置** @author qiongying.huai* @version 1.0* @date 14:55 2025/6/23*/
@Configuration
@EnableWebSecurity
public class SecurityConfig {private final Logger logger = LoggerFactory.getLogger(getClass());@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http// 放在ExceptionTranslationFilter之后,自定义的filter中的异常才能被exceptionHandling中的自定义处理器处理.addFilterAfter(new JwtTokenFilter(), ExceptionTranslationFilter.class).authorizeHttpRequests(authorize -> authorize.requestMatchers("/login", "/error", "/callback").permitAll().anyRequest().authenticated()).oauth2Client(Customizer.withDefaults()).oauth2Login(AbstractHttpConfigurer::disable).exceptionHandling(e ->e.authenticationEntryPoint((request, response, authException) -> {logger.error("request: {}, error: ", request.getRequestURI(), authException);Map<String, Object> responseData = new HashMap<>(4);responseData.put("code", 1001);responseData.put("msg", authException.getMessage());response.setContentType("application/json;charset=utf-8");response.setStatus(200);response.getWriter().write(JSON.toJSONString(responseData));}));return http.build();}
}

WebMvcConfig.java

package org.example.server.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** 跨域配置** @author qiongying.huai* @version 1.0* @date 14:26 2025/7/14*/
@Configuration
public class WebMvcConfig {@Beanpublic WebMvcConfigurer corsConfigurer() {return new WebMvcConfigurer() {@Overridepublic void addCorsMappings(@NonNull CorsRegistry registry) {registry.addMapping("/**");}};}
}

TokenInvalidException.java

package org.example.client.security;import org.springframework.security.core.AuthenticationException;import java.io.Serial;/*** 自定义access_token异常** @author qiongying.huai* @version 1.0* @date 19:12 2025/7/19*/
public class TokenInvalidException extends AuthenticationException {@Serialprivate static final long serialVersionUID = 3054997322961458614L;public TokenInvalidException(String msg) {super(msg);}
}

JwtTokenFilter.java

package org.example.client.security;import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Optional;/*** 通过jwt从资源服务获取用户验证授权信息* 客户端服务是从本地缓存获取,对应TokenFilter.class** @author qiongying.huai* @version 1.0* @date 11:36 2025/7/17*/
public class JwtTokenFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String url = "http://localhost:8082/authentication";String authorization = request.getHeader("Authorization");if (StringUtils.hasLength(authorization)) {HttpClient httpClient = HttpClient.newHttpClient();HttpRequest build = HttpRequest.newBuilder().uri(URI.create(url)).GET()
//                    .header("Content-Type", "application/x-www-form-urlencoded").header("Authorization", authorization).build();try {HttpResponse<String> send = httpClient.send(build, HttpResponse.BodyHandlers.ofString());String body = send.body();if (StringUtils.hasLength(body)) {JSONObject jsonObject = JSON.parseObject(body);Integer code = jsonObject.getInteger("code");Authentication authentication = jsonObject.getObject("data", Authentication.class);if (code == 200 && authentication != null) {SecurityContextHolder.getContext().setAuthentication(authentication);} else {String msg = Optional.ofNullable(jsonObject.getString("msg")).orElse("Token invalid.");throw new TokenInvalidException(msg);}}} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new TokenInvalidException("Token invalid.");}}filterChain.doFilter(request, response);}
}

ClientLoginController.java

package org.example.client.controller;import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Base64;/*** 客户端接口** @author qiongying.huai* @version 1.0* @date 13:56 2025/7/14*/
@RestController
public class ClientLoginController {@GetMapping("/login")public String login(@RequestParam("callback") String callback) {return "http://localhost:3001/code?client_id=client1&response_type=code&scope=all+openid" +"&redirect_uri=http://localhost:3003/callback&back=" + callback;}@GetMapping("/callback")public String callback(@RequestParam("code") String code, @RequestParam("token") String token) throws IOException, InterruptedException {// 获取access_tokenString url = "http://localhost:8081/oauth2/token";// 构建请求参数String requestBody = "grant_type=authorization_code" +"&redirect_uri=http://localhost:3003/callback" +"&code=" + code;HttpClient httpClient = HttpClient.newHttpClient();HttpRequest build = HttpRequest.newBuilder().uri(URI.create(url)).POST(HttpRequest.BodyPublishers.ofString(requestBody)).header("token", token).header("Content-Type", "application/x-www-form-urlencoded").header("Authorization", "Basic " + Base64.getEncoder().encodeToString("client1:secret".getBytes())).build();HttpResponse<String> send = httpClient.send(build, HttpResponse.BodyHandlers.ofString());return send.body();}
}

HelloController.java

package org.example.client.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** 客户端资源接口** @author qiongying.huai* @version 1.0* @date 10:02 2025/6/24*/
@RestController
public class HelloController {@GetMapping("/hello")public String hello() {return "hello";}
}

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

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

相关文章

DEEP THINK WITH CONFIDENCE-Meta-基于置信度的深度思考

原文地址 摘要 大型语言模型(LLM)通过自我一致性和多数投票等测试时间缩放方法&#xff0c;在推理任务中显示出巨大的潜力。然而&#xff0c;这种方法经常导致精度回报递减和高计算开销。为了应对这些挑战&#xff0c;我们引入了深度自信思考(DeepConf)&#xff0c;这是一种简…

零基础学习数据采集与监视控制系统SCADA

新晋码农一枚&#xff0c;小编定期整理一些写的比较好的代码&#xff0c;作为自己的学习笔记&#xff0c;会试着做一下批注和补充&#xff0c;转载或者参考他人文献会标明出处&#xff0c;非商用&#xff0c;如有侵权会删改&#xff01;欢迎大家斧正和讨论&#xff01; 目录 一…

docker run 命令,不接it选项,run一个centos没有显示在运行,而run一个nginx却可以呢?

docker run 命令&#xff0c;不接it选项&#xff0c;run一个centos没有显示在运行&#xff0c;而run一个nginx却可以呢&#xff1f; ChatGPT said: 你问到的这个现象&#xff0c;其实就是 镜像默认启动命令 (ENTRYPOINT / CMD) 的差异导致的。&#x1f50d; 情况分析 1. docker…

【完整源码+数据集+部署教程】水培植物病害检测系统源码和数据集:改进yolo11-AKConv

背景意义 研究背景与意义 随着全球人口的不断增长&#xff0c;农业生产面临着前所未有的挑战&#xff0c;尤其是在资源有限的环境中&#xff0c;如何提高作物的产量和质量成为了亟待解决的问题。水培技术作为一种新兴的农业生产方式&#xff0c;因其高效的水资源利用和较少的土…

第2课:环境搭建:基于DeepSeek API的开发环境配置

概述 在开始大模型RAG实战之旅前&#xff0c;一个正确且高效的开发环境是成功的基石。本文将手把手指导您完成从零开始的环境配置过程&#xff0c;涵盖Python环境设置、关键库安装、DeepSeek API配置以及开发工具优化。通过详细的步骤说明、常见问题解答和最佳实践分享&#x…

Boost电路:稳态和小信号分析

稳态分析 参考张卫平的《开关变换器的建模与控制》的1.3章节内容&#xff1b;伏秒平衡&#xff1a;在稳态下&#xff0c;一个开关周期内电感电流的增量是0&#xff0c;即 dIL(t)dt0\frac{dI_{L}(t)}{dt} 0dtdIL​(t)​0。电荷平衡&#xff1a;在稳态下&#xff0c;一个开关周期…

Vue-25-利用Vue3大模型对话框设计之前端和后端的基础实现

文章目录 1 设计思路 1.1 核心布局与组件 1.2 交互设计(Interaction Design) 1.3 视觉与用户体验 1.4 高级功能与创新设计 2 vue3前端设计 2.1 项目启动 2.1.1 创建和启动项目(vite+vue) 2.1.2 清理不需要的代码 2.1.3 下载必备的依赖(element-plus) 2.1.4 完整引入并注册(main…

Elasticsearch面试精讲 Day 7:全文搜索与相关性评分

【Elasticsearch面试精讲 Day 7】全文搜索与相关性评分 文章标签&#xff1a;Elasticsearch, 全文搜索, 相关性评分, TF-IDF, BM25, 面试, 搜索引擎, 后端开发, 大数据 文章简述&#xff1a; 本文是“Elasticsearch面试精讲”系列的第7天&#xff0c;聚焦于全文搜索与相关性评…

Vllm-0.10.1:vllm bench serve参数说明

一、KVM 虚拟机环境 GPU:4张英伟达A6000(48G) 内存&#xff1a;128G 海光Cpu:128核 大模型&#xff1a;DeepSeek-R1-Distill-Qwen-32B 推理框架Vllm:0.10.1 二、测试命令&#xff08;random &#xff09; vllm bench serve \ --backend vllm \ --base-url http://127.0.…

B.50.10.11-Spring框架核心与电商应用

Spring框架核心原理与电商应用实战 核心理念: 本文是Spring框架深度指南。我们将从Spring的两大基石——IoC和AOP的底层原理出发&#xff0c;详细拆解一个Bean从定义到销毁的完整生命周期&#xff0c;并深入探讨Spring事务管理的实现机制。随后&#xff0c;我们将聚焦于Spring …

雅菲奥朗SRE知识墙分享(六):『混沌工程的定义与实践』

混沌工程不再追求“永不宕机”的童话&#xff0c;而是主动在系统中注入可控的“混乱”&#xff0c;通过实验验证系统在真实故障场景下的弹性与自我修复能力。混沌工程不是简单的“搞破坏”&#xff0c;也不是运维团队的专属游戏。它是一种以实验为导向、以度量为核心、以文化为…

从0死磕全栈第五天:React 使用zustand实现To-Do List项目

代码世界是现实的镜像,状态管理教会我们:真正的控制不在于凝固不变,而在于优雅地引导变化。 这是「从0死磕全栈」系列的第5篇文章,前面我们已经完成了环境搭建、路由配置和基础功能开发。今天,我们将引入一个轻量级但强大的状态管理工具 —— Zustand,来实现一个完整的 T…

力扣29. 两数相除题解

原题链接29. 两数相除 - 力扣&#xff08;LeetCode&#xff09; 主要不能用乘除取余&#xff0c;于是用位运算代替&#xff1a; Java题解 class Solution {public int divide(int dividend, int divisor) {//全都转为负数计算, 避免溢出, flag记录结果的符号int flag 1;if(…

【工具类】Nuclei YAML POC 编写以及批量检测

Nuclei YAML POC 编写以及批量检测法律与道德使用声明前言Nuclei 下载地址下载对应版本的文件关于检查cpu架构关于hkws的未授权访问参考资料关于 Neclei Yaml 脚本编写BP Nuclei Template 插件下载并安装利用插件编写 POC YAML 文件1、找到有漏洞的页面抓包发送给插件2、同时将…

自动化运维之ansible

一、认识自动化运维假如管理很多台服务器&#xff0c;主要关注以下几个方面“1.管理机与被管理机的连接&#xff08;管理机如何将管理指令发送给被管理机&#xff09;2.服务器信息收集&#xff08;如果被管理的服务器有centos7.5外还有其它linux发行版&#xff0c;如suse,ubunt…

【温室气体数据集】亚洲地区长期空气污染物和温室气体排放数据 REAS

目录 REAS 数据集概述 REAS 数据版本及特点 数据内容(以 REASv3.2.1 为例) 数据形式 数据下载 参考 REAS 数据集(Regional Emission inventory in ASia,亚洲区域排放清单)是由日本国立环境研究所(NIES)及相关研究人员开发的一个覆盖亚洲地区长期空气污染物和温室气体排放…

中州养老项目:利用Redis解决权限接口响应慢的问题

目录 在Java中使用Redis缓存 项目中集成SpringCache 在Java中使用Redis缓存 Redis作为缓存,想要在Java中操作Redis,需要 Java中的客户端操纵Redis就像JDBC操作数据库一样,实际底层封装了对Redis的基础操作 如何在Java中使用Redis呢?先导入Redis的依赖,这个依赖导入后相当于把…

MathJax - LaTeX:WordPress 公式精准呈现方案

写在前面&#xff1a;本博客仅作记录学习之用&#xff0c;部分图片来自网络&#xff0c;如需引用请注明出处&#xff0c;同时如有侵犯您的权益&#xff0c;请联系删除&#xff01; 文章目录前言安装 MathJax-LaTeX 插件修改插件文件效果总结互动致谢参考前言 在当今知识传播与…

详细解读Docker

1.概述Docker是一种优秀的开源的容器化平台。用于部署、运行应用程序&#xff0c;它通过将应用及其依赖打包成轻量级、可移植的容器&#xff0c;实现高效一致的运行效果&#xff0c;简单来说&#xff0c;Docker就是一种轻量级的虚拟技术。2.核心概念2.1.容器&#xff08;Contai…

GEE:基于自定义的年度时序数据集进行LandTrendr变化检测

本文记录了使用自己的年度时序数据集,进行 LandTrendr 变化检测的代码。结果输出变化年份、变化幅度以及变化持续时间。 结果如下图所示, 文章目录 一、核心函数 二、代码 三、代码链接 一、核心函数 var eeltgcm = require(users/949384116/lib:LandTrendr/getChangeMap)v…