深入淺出Bearer Token:解析工作原理及其在Vue、Uni-app與Java中的實現Demo

目錄

  • 前言
  • 1. 基本知識
  • 2. Demo
  • 3. 實戰

前言

🤟 找工作,來萬碼優才:👉 #小程序://萬碼優才/r6rqmzDaXpYkJZF

1. 基本知識

Bearer Token是一種基于Token的認證機制,用于在HTTP請求中傳遞用戶的身份信息

應用于RESTful API和各種Web應用中,提供了一種輕量且高效的身份驗證方式

基本的作用如下:

  • 身份驗證:通過Token驗證用戶的身份,確定其是否有權訪問某個資源
  • 授權:Token中可以包含用戶的權限信息,服務器可以根據Token中的信息決定用戶可以進行的操作
  • 無狀態認證:服務器不需要保存用戶的會話狀態,只需解析Token即可驗證用戶身份,使系統更易于擴展和管理

具體的工作原理如下:

  1. 用戶登錄:用戶向服務器發送登錄請求,提供用戶名和密碼等認證信息
  2. Token生成:服務器驗證用戶信息后,生成一個包含用戶身份和權限信息的Bearer Token。 Token可以是純字符串,也可以是經過簽名的JWT(JSON Web Token)
  3. Token傳輸:服務器將生成的Token返回給客戶端,客戶端將其存儲在本地(如瀏覽器的Local Storage或Cookie中)
  4. 請求資源:客戶端在后續的HTTP請求中,將Bearer Token放在Authorization頭中,發送到服務器
  5. Token驗證:服務器接收到請求后,提取Bearer Token,解析其內容以驗證用戶身份和權限,決定是否允許請求

融入個人的一點小思考:

Uni-app部分,由于它是跨平臺的框架,語法和Vue類似,我可以使用類似的邏輯來實現登錄和.Token的管理。只需確保在不同設備上的本地存儲方式一致即可。


在后端,使用Java開發時,我需要編寫一個控制器來處理登錄請求,生成Token,并將其返回給前端。同時,還需要編寫過濾器或攔截器,用于檢查每個請求的Authorization頭,解析Token并驗證。如果Token有效,則允許請求繼續;否則,拒絕訪問。


那么,如何辨別前端傳來的Token是否有效呢?在后端,可以利用JWT的特性,比如檢查Token的簽名是否正確,Token是否過期,以及Token中的用戶信息是否合法。也可以實現Token的黑名單機制,支持用戶注銷或停止某些Token的使用。


在實際項目中,還需要考慮Token的安全性,比如防止CSRF攻擊、存儲Token的方式(本地存儲、Cookie等)、Token的有效期等。此外,還需要應對Token被泄露或被截獲的風險,建議在傳輸中使用HTTPS協議。


經過以上的思考,我對Bearer Token有了基本的理解。它是一種基于Token的認證方式,適用于分布式系統和無狀態的API設計。通過Token的生成、存儲和驗證,可以在沒有會話管理的情況下實現用戶的身份認證

~ 具體JWT的流程如下:
前端發送登錄請求,包含用戶名和密碼
后端驗證用戶信息,創建Header和Payload
使用保密密鑰對Header和Payload進行簽名,生成完整的JWT
將JWT返回給前端,前端存儲它

~ jwt驗證流程:
前端在每次請求中攜帶JWT
后端提取JWT,驗證其簽名,確保未被篡改
解析Payload中的用戶信息,進行權限檢查

2. Demo

接下來會以Vue 以及 Uniapp對接Java的方式呈現一個Demo,主要是提供一個思路

實現Bearer Token的步驟

  1. 用戶登錄:客戶端發送登錄請求,攜帶用戶名和密碼;服務器驗證用戶信息,成功后生成Bearer Token;返回Token給客戶端
  2. 存儲Token:客戶端將Token存儲在安全的位置,如HTTP-only Cookie或Local Storage中;確保Token不會被惡意腳本竊取,推薦使用HTTPS傳輸
  3. 發送請求:客戶端在后續請求中,在Authorization頭添加"Bearer ";服務器接收到請求后,解析Token,驗證用戶身份
  4. Token驗證:服務器檢查Token的簽名是否有效,確保Token未被篡改;驗證Token是否過期,獲取用戶信息和權限;授權或拒絕請求

Bearer Token的過期與刷新
過期時間定義Token的存活時間,通常幾分鐘到幾小時不等。Token過期后,用戶需要重新登錄以獲取新的Token
刷新Token:用戶在Token過期前,可以請求刷新Token,獲取新的有效Token

  1. Vue實現

登錄組件

<template><div class="login"><h2>登錄</h2><form @submit.prevent="handleLogin"><div class="form-group"><label for="username">用戶名</label><input type="text" id="username" v-model="username" required></div><div class="form-group"><label for="password">密碼</label><input type="password" id="password" v-model="password" required></div><button type="submit">登錄</button></form></div>
</template><script>
export default {data() {return {username: '',password: ''};},methods: {async handleLogin() {try {const response = await this.$axios.post('/api/login', {username: this.username,password: this.password});const token = response.data.token;localStorage.setItem('authToken', token);this.$router.push('/dashboard');} catch (error) {console.error('登錄失敗:', error);}}}
};
</script>

請求攔截器

// main.js
import Vue from 'vue';
import axios from 'axios';axios.interceptors.request.use(config => {const token = localStorage.getItem('authToken');if (token) {config.headers.Authorization = `Bearer ${token}`;}return config;
});Vue.config.productionTip = false;
new Vue({render: h => h(App),
}).$mount('#app');
  1. Uni-app實現

登錄頁面

<template><view class="login"><h2>登錄</h2><form @submit="handleLogin"><view class="form-group"><label>用戶名</label><input type="text" v-model="username" /></view><view class="form-group"><label>密碼</label><input type="password" v-model="password" /></view><button form-type="submit">登錄</button></form></view>
</template><script>
export default {data() {return {username: '',password: ''};},methods: {async handleLogin() {try {const response = await this.$axios.post('/api/login', {username: this.username,password: this.password});const token = response.data.token;uni.setStorageSync('authToken', token);uni.navigateTo({ url: '/pages/dashboard/dashboard' });} catch (error) {console.error('登錄失敗:', error);}}}
};
</script>

Uni-app 請求攔截器

// app.vue
import Vue from 'vue';
import axios from 'axios';axios.interceptors.request.use(config => {const token = uni.getStorageSync('authToken');if (token) {config.headers.Authorization = `Bearer ${token}`;}return config;
});Vue.config.productionTip = false;App.mpType = 'app';const app = new Vue({...App
});app.$mount();

四、后端實現:Java

User實體類

@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String username;private String password;@Enumerated(EnumType.STRING)private Role role;
}

Role枚舉

public enum Role {USER, ADMIN
}

JwtConfig配置

@Configuration
public class JwtConfig {@Value("${ jwt.secret}")private String secret;@Value("${ jwt.expiration}")private Long expiration;@Value("${ jwt.header}")private String header;public String getSecret() {return secret;}public Long getExpiration() {return expiration;}public String getHeader() {return header;}
}

JwtUtil工具類

@Component
public class JwtUtil {@Autowiredprivate JwtConfig config;public String generateToken(User user) {Map<String, Object> claims = new HashMap<>();claims.put("id", user.getId());claims.put("username", user.getUsername());claims.put("role", user.getRole());claims.put("iat", new Date());claims.put("exp", new Date(System.currentTimeMillis() + config.getExpiration()));return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256, config.getSecret().getBytes()).compact();}public boolean validateToken(String token) {try {Jwts.parser().setSigningKey(config.getSecret().getBytes()).parseClaimsJws(token);return true;} catch (Exception e) {return false;}}public Claims getTokenBody(String token) {return Jwts.parser().setSigningKey(config.getSecret().getBytes()).parseClaimsJws(token).getBody();}
}

LoginController

@RestController
@RequestMapping("/api")
public class LoginController {@Autowiredprivate JwtUtil jwtUtil;@Autowiredprivate UserRepository userRepository;@PostMapping("/login")public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {User user = userRepository.findByUsername(loginRequest.getUsername()).orElseThrow(() -> new BadCredentialsException("用戶不存在"));if (!user.getPassword().equals	loginRequest.getPassword()) {throw new BadCredentialsException("密碼錯誤");}String token = jwtUtil.generateToken(user);return ResponseEntity.ok(new ApiResponse("登錄成功", token));}
}

SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/api/login").permitAll().anyRequest().authenticated().and().addFilterBefore(new JwtAuthenticationFilter(), BasicAuthenticationFilter.class);}
}

JwtAuthenticationFilter

public class JwtAuthenticationFilter extends OncePerRequestFilter {@Autowiredprivate JwtUtil jwtUtil;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {String header = request.getHeader("Authorization");if (header != null && header.startsWith("Bearer ")) {String token = header.substring(7);if (jwtUtil.validateToken(token)) {Claims claims = jwtUtil.getTokenBody(token);UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(claims.getSubject(), null, getAuthorities((List<String>) claims.get("role")));SecurityContextHolder.getContext().setAuthentication(authentication);} else {response.setStatus(HttpServletResponse.SC_FORBIDDEN);return;}}filterChain.doFilter(request, response);}private Collection<? extends GrantedAuthority> getAuthorities(List<String> roles) {return roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());}
}

3. 實戰

實戰中的Demo測試如下:

在這里插入圖片描述

以下代碼用于實戰中的講解,代碼來源:https://gitee.com/zhijiantianya/ruoyi-vue-pro

uniapp中封裝獨特的request請求:

import store from '@/store'
import config from '@/config'
import { getAccessToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { toast, showConfirm, tansParams } from '@/utils/common'let timeout = 10000
const baseUrl = config.baseUrl + config.baseApi;const request = config => {// 是否需要設置 tokenconst isToken = (config.headers || {}).isToken === falseconfig.header = config.header || {}if (getAccessToken() && !isToken) {config.header['Authorization'] = 'Bearer ' + getAccessToken()}// 設置租戶 TODO 芋艿:強制 1 先config.header['tenant-id'] = '1';// get請求映射params參數if (config.params) {let url = config.url + '?' + tansParams(config.params)url = url.slice(0, -1)config.url = url}return new Promise((resolve, reject) => {uni.request({method: config.method || 'get',timeout: config.timeout ||  timeout,url: config.baseUrl || baseUrl + config.url,data: config.data,// header: config.header,header: config.header,dataType: 'json'}).then(response => {let [error, res] = responseif (error) {toast('后端接口連接異常')reject('后端接口連接異常')return}const code = res.data.code || 200const msg = errorCode[code] || res.data.msg || errorCode['default']if (code === 401) {showConfirm('登錄狀態已過期,您可以繼續留在該頁面,或者重新登錄?').then(res => {if (res.confirm) {store.dispatch('LogOut').then(res => {uni.reLaunch({ url: '/pages/login' })})}})reject('無效的會話,或者會話已過期,請重新登錄。')} else if (code === 500) {toast(msg)reject('500')} else if (code !== 200) {toast(msg)reject(code)}resolve(res.data)}).catch(error => {let { message } = errorif (message === 'Network Error') {message = '后端接口連接異常'} else if (message.includes('timeout')) {message = '系統接口請求超時'} else if (message.includes('Request failed with status code')) {message = '系統接口' + message.substr(message.length - 3) + '異常'}toast(message)reject(error)})})
}export default request

其中token都是存放本地:

const AccessTokenKey = 'ACCESS_TOKEN'
const RefreshTokenKey = 'REFRESH_TOKEN'// ========== Token 相關 ==========export function getAccessToken() {return uni.getStorageSync(AccessTokenKey)
}export function getRefreshToken() {return uni.getStorageSync(RefreshTokenKey)
}export function setToken(token) {uni.setStorageSync(AccessTokenKey, token.accessToken)uni.setStorageSync(RefreshTokenKey, token.refreshToken)
}export function removeToken() {uni.removeStorageSync(AccessTokenKey)uni.removeStorageSync(RefreshTokenKey)
}

后續只需要發送給后端即可

再說說前段也同理:

制作一個Jwt的格式:

import { useCache, CACHE_KEY } from '@/hooks/web/useCache'
import { TokenType } from '@/api/login/types'
import { decrypt, encrypt } from '@/utils/jsencrypt'const { wsCache } = useCache()const AccessTokenKey = 'ACCESS_TOKEN'
const RefreshTokenKey = 'REFRESH_TOKEN'// 獲取token
export const getAccessToken = () => {// 此處與TokenKey相同,此寫法解決初始化時Cookies中不存在TokenKey報錯return wsCache.get(AccessTokenKey) ? wsCache.get(AccessTokenKey) : wsCache.get('ACCESS_TOKEN')
}// 刷新token
export const getRefreshToken = () => {return wsCache.get(RefreshTokenKey)
}// 設置token
export const setToken = (token: TokenType) => {wsCache.set(RefreshTokenKey, token.refreshToken)wsCache.set(AccessTokenKey, token.accessToken)
}// 刪除token
export const removeToken = () => {wsCache.delete(AccessTokenKey)wsCache.delete(RefreshTokenKey)
}/** 格式化token(jwt格式) */
export const formatToken = (token: string): string => {return 'Bearer ' + token
}

后端Java的token只需校驗即可:

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/897546.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/897546.shtml
英文地址,請注明出處:http://en.pswp.cn/news/897546.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

kubernetes——part3-5 核心概念 Service

一、 service作用 使用kubernetes集群運行工作負載時&#xff0c;由于Pod經常處于用后即焚狀態&#xff0c;Pod經常被重新生成&#xff0c;因此Pod對應的IP地址也會經常變化&#xff0c;導致無法直接訪問Pod提供的服務&#xff0c;Kubernetes中使用了Service來解決這一問題&am…

從零開始 | C語言基礎刷題DAY1

?個人主頁&#xff1a;折枝寄北的博客 DAY1[2025.3.11] 1. 求兩個數的較大值2.從鍵盤輸入的兩個數的大小關系3.一個整數的奇偶性&#xff0c;請判斷4. 考試分數是否通過5.考試成績是否完美&#xff0c;請判斷 1. 求兩個數的較大值 題目&#xff1a; 寫一個函數求兩個整數的較…

開源模型時代的 AI 開發革命:Dify 技術深度解析

開源模型時代的AI開發革命&#xff1a;Dify技術深度解析 引言&#xff1a;AI開發的開源新紀元 在生成式AI技術突飛猛進的2025年&#xff0c;開源模型正成為推動行業創新的核心力量。據統計&#xff0c;全球超過80%的AI開發者正在使用開源模型構建應用&#xff0c;這一趨勢不僅…

Dify Web 前端獨立部署指南(與后端分離,獨立部署)

背景:單獨拆分前端出來部署,二開前后端 本文檔專注于 Dify Web 前端的部署流程和配置,適用于需要將項目部署到各種環境的運維人員和開發者。 1. 環境準備 1.1 部署環境要求 Node.js >= 18.17.0Nginx 或其他Web服務器(生產環境推薦)Docker(可選,用于容器化部署)1.…

《蒼穹外賣》SpringBoot后端開發項目核心知識點整理(DAY1 to DAY3)

目錄 一、在本地部署并啟動Nginx服務1. 解壓Nginx壓縮包2. 啟動Nginx服務3. 驗證Nginx是否啟動成功&#xff1a; 二、導入接口文檔1. 黑馬程序員提供的YApi平臺2. YApi Pro平臺3. 推薦工具&#xff1a;Apifox 三、Swagger1. 常用注解1.1 Api與ApiModel1.2 ApiModelProperty與Ap…

大數據hadoop課程筆記

1.課程導入 柯潔 Alpha Go是人工智能領域的里程碑。 深度學習 大模型deepseek chatgpt 大模型 和 大數據 之間有著非常緊密的關系。可以說&#xff0c;大數據是大模型發展的基石&#xff0c;而大模型是大數據價值挖掘的重要工具。 https://youtu.be/nN-VacxHUH8?sifj7Ltk…

架構學習第八周--Kubernetes博客搭建

目錄 一、整體架構 二、部署MySQL主從 三、部署Redis哨兵 四、部署WordPress 五、注意事項 一、整體架構 本項目為在一主三從的Kubernetes集群上部署WordPress博客。因為WordPress部分容器版本自行集成Apache和PHP服務&#xff0c;因此在Kubernetes上部署WordPress只需提供…

Application.OnTime如何引用帶參數的過程

Application.OnTime方法本身并不直接支持傳遞參數給被調用的過程。不過&#xff0c;有幾種方法可以間接實現這個需求。 方法1&#xff1a;使用單引號表達式 使用單引號表達式來傳遞參數時&#xff0c;不能在表達式中使用變量&#xff0c;需要把參數值直接寫到表達中&am…

網絡安全之tcpdump工具

引言 wireshark是一款非常不錯的抓包軟件&#xff0c;在圖形化界面占絕對統治地位&#xff1b;盡管其在字符界面下有些許選項可供使用&#xff0c;但終究不太方便&#xff0c;下面我再介紹一款NB的終端抓包工具 tcpdump 1、混雜模式 linux的網卡有混雜模式一說&#xff0c;當開…

VC++ 獲取目的IP的路由

GetBestRoute 函數獲取到目的IP的最佳匹配路由。 第一個參數為&#xff1a;destination&#xff08;目的IP&#xff09; 第二個參數為&#xff1a;source&#xff08;源IP&#xff09; 通常不需要指定第二個source&#xff0c;這個一般用來匹配具體某一個網卡接口路由的&…

JavaScript 模塊 vs C# 類:封裝邏輯的兩種哲學

引言 在現代軟件開發中&#xff0c;模塊化和面向對象設計是代碼組織的核心課題。本文通過對比 JavaScript 模塊&#xff08;ES6 Module&#xff09;與 C# 類&#xff08;Class&#xff09;的實現方式&#xff0c;探討兩種語言在封裝邏輯時的不同哲學&#xff0c;并給出實際應用…

大模型在甲狀腺癌診療全流程預測及方案制定中的應用研究

目錄 一、引言 1.1 研究背景與意義 1.2 研究目的與創新點 1.3 國內外研究現狀 二、大模型預測甲狀腺癌的理論基礎 2.1 甲狀腺癌相關醫學知識 2.2 大模型技術原理與特點 2.3 大模型在醫療領域的應用潛力 三、術前預測方案 3.1 預測模型構建 3.1.1 數據收集與預處理 …

electron+vue+webview內嵌網頁并注入js

vue內嵌網頁可以使用iframe實現內嵌網頁&#xff0c;但是只能通過postMessage間接通信&#xff0c;在electron環境下&#xff0c;vue可以直接使用webview來內嵌網頁&#xff0c;支持 executeJavaScript、postMessage、send 等豐富的通信機制。 使用 webview的優勢 性能更佳&…

leetcode日記(95)將有序數組轉換為二叉搜索樹

很簡單&#xff0c;感覺自己越來越適應數據結構題目了…… /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : va…

【threejs實戰教程一】初識Three.js,場景Scene、相機Camera、渲染器Renderer

Three.js是一個基于WebGL的JavaScript 3D圖形庫&#xff0c;用于在瀏覽器中創建和顯示3D內容 Three.js中最基礎的三個關鍵要素就是場景Scene、相機Camera、渲染器Renderer 通俗一點理解&#xff0c;場景就是我們生活中一個具體的場景&#xff0c;比如自然環境中的一棟建筑&…

【leetcode hot 100 138】隨機鏈表的復制

解決一&#xff1a;回溯 哈希表 本題要求我們對一個特殊的鏈表進行深拷貝。如果是普通鏈表&#xff0c;我們可以直接按照遍歷的順序創建鏈表節點。而本題中因為隨機指針的存在&#xff0c;當我們拷貝節點時&#xff0c;「當前節點的隨機指針指向的節點」可能還沒創建&#xf…

木馬查殺之AST初識篇

一、AST 定義 抽象語法樹&#xff08;Abstract Syntax Tree&#xff0c;AST&#xff09;是源代碼的一種抽象表示形式。它以樹狀結構描述源代碼的語法構成&#xff0c;樹上的每個節點都對應源代碼中的一個語法結構或元素&#xff0c;像變量聲明、函數調用、表達式等。通過這種結…

vscode接入DeepSeek 免費送2000 萬 Tokens 解決DeepSeek無法充值問題

1. 在vscode中安裝插件 Cline 2.打開硅基流動官網 3. 注冊并登陸&#xff0c;邀請碼 WpcqcXMs 4.登錄后新建秘鑰 5. 在vscode中配置cline (1) API Provider 選擇 OpenAI Compatible &#xff1b; (2) Base URL設置為 https://api.siliconflow.cn](https://api.siliconfl…

如何在保持安全/合規的同時更快地構建應用程序:DevOps 指南

隨著敏捷思維方式的興起&#xff0c;開發和 DevOps 團隊都面臨著持續的壓力&#xff0c;他們需要以迭代方式縮短發布周期并加快部署速度&#xff0c;以滿足不斷增長的客戶期望。隨著這種對速度的追求越來越強烈&#xff0c;維護安全性和合規性標準的復雜性也隨之增加。 當今 D…

Java中常見的PO、VO、DAO、BO、DO、DTO、POJO、Query類解釋(通俗易懂)

文章目錄 先點擊收藏和點贊,切勿白嫖,感謝一丶PO(persistant object)持久對象二丶VO(value object)值對象三丶DAO(Data Access Objects) 數據訪問對象接口四丶BO/DO(Business Object) 業務對象層五丶DTO(Data Transfer Object) 數據傳輸對象六丶POJO(Plain Old Java Objects) 簡…