深入浅出Bearer Token:解析工作原理及其在Vue、Uni-app与Java中的实现Demo

ops/2025/3/13 0:46:12/

目录

  • 前言
  • 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>javascript">
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>javascript">
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实体类

java">@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枚举

java">public enum Role {USER, ADMIN
}

JwtConfig配置

java">@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工具类

java">@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

java">@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

java">@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

java">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.ppmy.cn/ops/165272.html

相关文章

基于PyTorch的深度学习6——可视化工具Tensorboard

先安装tensorflow&#xff08;CPU或GPU版&#xff09;​&#xff0c;然后安装tensorboardX&#xff0c;在命令行运行以下命令即可。 pip install tensorboardX 使用tensorboardX的一般步骤如下所示。 1)导入tensorboardX&#xff0c;实例化SummaryWriter类&#xff0c;指明记…

[项目]基于FreeRTOS的STM32四轴飞行器: 四.LED控制

基于FreeRTOS的STM32四轴飞行器: 四.LED控制 一.配置Com层二.编写驱动 一.配置Com层 先在Com_Config.h中定义灯位置的枚举类型&#xff1a; 之后定义Led的结构体&#xff1a; 定义飞行器状态&#xff1a; 在Com_Config.c中初始化四个灯&#xff1a; 在Com_Config.h外部声明…

Trae AI 开发工具使用手册

这篇手册将介绍 Trae 的基本功能、安装步骤以及使用方法&#xff0c;帮助开发者快速上手这款工具。 Trae AI 开发工具使用手册 Trae 是字节跳动于 2025 年推出的一款 AI 原生集成开发环境&#xff08;IDE&#xff09;&#xff0c;旨在通过智能代码生成、上下文理解和自动化任务…

YashanDB认证,YCA证书认证教程,免费证书,内含真题考试题库及答案——五分钟速成

目录 一.账号及平台注册登录流程 二.登录进行设备调试核验 三.考试&#xff08;考完获取分数&#xff09; 四.获取证书 五.题库及答案 一.账号及平台注册登录流程 1-点击这里进行账号注册&#xff08;首次学习必须先注册&#xff0c;有账号之后可以直接在2号链接登录&#…

[项目]基于FreeRTOS的STM32四轴飞行器: 七.遥控器按键

基于FreeRTOS的STM32四轴飞行器: 七.遥控器 一.遥控器按键摇杆功能说明二.摇杆和按键的配置三.按键扫描 一.遥控器按键摇杆功能说明 两个手柄四个ADC。 左侧手柄&#xff1a; 前后推为飞控油门&#xff0c;左右推为控制飞机偏航角。 右侧手柄&#xff1a; 控制飞机飞行方向&a…

【HarmonyOS Next】鸿蒙加固方案调研和分析

【HarmonyOS Next】鸿蒙加固方案调研和分析 一、前言 根据鸿蒙应用的上架流程&#xff0c;本地构建app文件后&#xff0c;上架到AGC平台&#xff0c;平台会进行解析。根据鸿蒙系统的特殊设置&#xff0c;仿照IOS的生态闭环方案。只能从AGC应用市场下载app进行安装。这样的流程…

(每日一题) 力扣 14 最长公共前缀

14. 最长公共前缀题解&#xff1a;多方法详解与C实现 问题描述 给定字符串数组&#xff0c;找出所有字符串的最长公共前缀。若不存在则返回空字符串。 示例&#xff1a; 输入&#xff1a;["flower","flow","flight"] 输出&#xff1a;"…

深入解析MySQL MVCC原理:从内核实现到高并发实践

深入解析MySQL MVCC原理&#xff1a;从内核实现到高并发实践 编程相关书籍分享&#xff1a;https://blog.csdn.net/weixin_47763579/article/details/145855793 DeepSeek使用技巧pdf资料分享&#xff1a;https://blog.csdn.net/weixin_47763579/article/details/145884039 一、…