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

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.dtcms.com/a/365528.html

相关文章:

  • C++:类和对象(上)
  • 集成运算放大器的作用、选型和测量指南-超简单解读
  • 夸克网盘辅助工具 QuarkPanTool 分析
  • 代码随想录算法训练营第一天 || (双指针)27.移除元素 26.删除有序数组中的重复项 283.移动零 977.有序数组的平方
  • 从 “能说会道” 到 “能做会干”:AI Agent 技术突破,如何让人工智能拥有 “行动力”?
  • Linux 创建服务 使用systemctl 管理
  • uni app 的app端 写入运行日志到指定文件夹。
  • 腾讯云《意愿核身移动 H5》 快速完成身份验证接入
  • 国产CAD皇冠CAD(CrownCAD)建模教程:汽车驱动桥
  • HTML5 标题标签、段落、换行和水平线
  • shell-awk命令详解(理论+实战)
  • 【面试场景题】1GB 大小HashMap在put时遇到扩容的过程
  • 第七章 表达:成果展示--创建第二大脑读书笔记
  • 10名机械画图人员如何共享一台云服务器的软硬件资源进行设计办公
  • ArcGIS解决csv或者excel转换为矢量的坐标问题
  • 第二章 Windows 核心概念通俗解析
  • 03 - HTML常用标签
  • 【学Python自动化】 9.1 Python 与 Rust 类机制对比学习笔记
  • PyTorch 和 Transformer的区别与联系
  • Linux 入门到精通,真的不用背命令!零基础小白靠「场景化学习法」,3 个月拿下运维 offer,第二十五天
  • 农业XR数字融合工作站,赋能农业专业实践学习
  • Qt为什么要引入QML语言?
  • 八、算法设计与分析
  • 瑞芯微rv1126 linux内核使用spidev驱动
  • 【Unity基础】两个关于UGUI中Text对非英文字体支持的问题
  • 在线性代数里聊聊word embedding
  • 在Excel和WPS表格中隔多行插入一个空白行
  • 【Linux 内存管理】2 进程地址空间 - vm_area_struct 数据结构
  • 【Zotero】插入中文参考文献遇到的问题
  • 【数据处理工具】依据图层批量分割要素,并自动处理碎图斑