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
英文地址,請注明出處:http://en.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…