第四阶段-12关于Spring Security框架,RBAC,密码加密原则
关于csmall-passport项目
此项目主要用于实现“管理员”账号的后台管理功能,主要实现:
- 管理员登录
- 添加管理员
- 删除管理员
- 显示管理员列表
- 启用 / 禁用管理员
关于RBAC
RBAC:Role-Based Access Control,基于角色的访问控制
在涉及权限管理的应用软件设计中,应该至少需要设计以下3张数据表:
- 用户表
- 角色表
- 权限表
并且,还至少需要2张关联表:
- 用户与角色的关联表
- 角色与权限的关联表
关于Spring Security框架
Spring Security主要解决了认证与授权相关的问题。
认证:判断某个账号是否允许访问某个系统,简单来说,就是验证登录
授权:判断是否允许已经通过认证的账号访问某个资源,简单来说,就是判断是否具有权限执行某项操作
添加依赖
在基于Spring Boot的项目中,使用Spring Security需要添加依赖项:
<!-- Spring Boot Security依赖项,用于处理认证与授权相关的问题 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
当在项目中添加以上依赖项后,你的项目会发生以下变化(Spring Boot中的Spring Security的默认行为):
-
所有的请求都是必须要登录才允许访问的,包括错误的URL
-
提供了默认的登录页面,当未登录时,会自动重定向到此登录页面
-
提供了临时的登录账号,用户名是
user
,密码是启动项目时在控制台中的UUID值(每次重启项目都会不同)
- 当登录成功后,将自动重定向到此前尝试访问的URL,如果此前没有尝试访问某个URL,则重定向到根路径
- 可以通过
/logout
路径访问到“退出登录”的页面,以实现登出 - 当登录成功后,
POST
请求都是不允许的,而GET
请求是允许的
关于Spring Security的配置类
在项目的根包下,创建config.SecurityConfiguration
类,继承自WebSecurityConfigurerAdapter
类,在类上添加@Configuration
注解:
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
}
然后,在类中重写void configure(HttpSecurity http)
方法:
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {}}
**注意:**在重写的方法中,不要使用super
调用父类的此方法!
由于没有调用父类此方法,再次重启项目后,与此前将有些不同:
- 所有请求都不再要求登录
- 登录、登出的URL不可访问
关于登录表单
在Spring Security配置类的configure(HttpSecurity http)
方法中,根据是否调用了参数对象的formLogin()
方法,决定是否启用登录表单页(/login
)和登出页(/logout
),例如:
@Override
protected void configure(HttpSecurity http) throws Exception {// 调用formLogin()表示启用登录表单页和登出页,如果未调用此方法,则没有登录表单页和登出页http.formLogin();
}
关于URL的访问控制
在Spring Security配置类的configure(HttpSecurity http)
方法中,
// 白名单
// 使用1个星号,表示通配此层级的任意资源,例如:/admin/*,可以匹配 /admin/delete、/admin/add-new
// 但是,不可以匹配多个层级,例如:/admin/*,不可以匹配 /admin/9527/delete
// 使用2个连续的星号,表示通配任何层级的任意资源,例如:/admin/**,可以匹配 /admin/delete、/admin/9527/delete
String[] urls = {"/doc.html","/**/*.js","/**/*.css","/swagger-resources","/v2/api-docs"
};// 配置URL的访问控制
http.authorizeRequests() // 配置URL的访问控制.mvcMatchers(urls) // 匹配某些URL.permitAll() // 直接许可,即:不需要通过认证就可以直接访问.anyRequest() // 任何请求.authenticated(); // 以上配置的请求需要是通过认证的
使用临时的自定义账号实现登录
可以自定义类,实现UserDetailsService
接口,并保证此类是组件类,则Spring Security框架会基于此实现类来处理认证。
在项目的根包下创建security.UserDetailsServiceImpl
类,实现UserDetailsService
接口,并在类上添加@Service
注解,重写接口中定义的抽象方法:
@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {return null;}
}
当项目中存在UserDetailsService
类型的组件对象时,尝试登录时,Spring Security会自动使用登录表单提交过来的用户名来调用以上loadUserByUsername()
方法,并得到UserDetails
类型的对象,此对象中应该包含用户的相关信息,例如密码、账号状态等,接下来,Spring Security会自动使用登录表单提交过来的密码与UserDetails
中的密码进行对比,且判断账号状态,以决定此账号是否能够通过认证。
所以,重写以上方法:
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {// 假设存在可用的账号信息:用户名(root),密码(123456)if ("root".equals(s)) {UserDetails userDetails = User.builder().username("root").password("123456").disabled(false).accountLocked(false).accountExpired(false).credentialsExpired(false).authorities("暂时给个山寨权限,暂时没有作用,只是避免报错而已").build();return userDetails;}return null;
}
**提示:**当项目中存在UserDetailsService
类型的组件对象时,Spring Security框架不再提供临时的账号(用户名为user
密码为启动项目时的UUID值的账号)!
**注意:**Spring Security在处理认证时,要求密码必须经过加密码处理,即使你执意不加密,也必须明确的表示出来!
在SecurityConfiguration
中,通过@Bean
方法配置PasswordEncoder
,并返回NoOpPasswordEncoder
的对象,表示“不对密码进行加密处理”:
@Bean
public PasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();
}
完成后,重启项目,通过/login
可以测试访问。
使用数据库中的账号数据实现登录
需要实现“根据用户名查询用户的登录信息”,需要执行的SQL语句大致是:
select id, username, password, enable from ams_admin where username=?
在项目的根包下创建pojo.vo.AdminLoginInfoVO
类:
@Data
public class AdminLoginInfoVO implements Serializable {private Long id;private String username;private String password;private Integer enable;
}
在AdminMapper.java
接口中添加抽象方法:
AdminLoginInfoVO getLoginInfoByUsername(String username);
在AdminMapper.xml
中配置以上抽象方法映射的SQL:
<select ...></select><sql></sql><resultMap></resultMap>
在AdminMapperTests
中编写并执行测试:
接下来,在UserDetailsServiceImpl
中,先自动装配AdminMapper
对象,然后,调整loadUserByUsername()
方法:
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {// 使用参数s作为参数,调用AdminMapper对象的getLoginInfoByUsername()方法执行查询// 判断查询结果是否为null// 是:无此用户名对应的账号信息,返回null// 返回UserDetails对象// username:来自查询结果// password:暂时写死为123456,后续再改成来自查询结果// disable:来自查询结果中的enable,判断enable是否为0// accountExpired等:参考此前的Demo,将各值写死
}
完成后,可以使用数据库中的账号测试登录(暂时不方便测试密码)。
tLoginInfoByUsername()方法执行查询
// 判断查询结果是否为null
// 是:无此用户名对应的账号信息,返回null
// 返回UserDetails对象
// username:来自查询结果
// password:暂时写死为123456,后续再改成来自查询结果
// disable:来自查询结果中的enable,判断enable是否为0
// accountExpired等:参考此前的Demo,将各值写死
}
完成后,可以使用数据库中的账号测试登录(暂时不方便测试密码)。#### 密码为什么需要加密如果未加密,将密码的原文(原始密码)直接存入到数据库中,可以被轻松获取账户的关键信息!以目前主流的网络结构和技术,通常,密码加密主要防范的是内部工作人员(能够接触到服务器的人员)!需要注意:即使密码加密了,也要防范相关的内部工作人员,例如程序员!#### 如何对密码进行加密直接使用现有的某种算法,也就是说,不会自行设计某个算法!#### 使用什么算法对密码进行加密一定**不可以**使用**加密算法**!因为所有加密算法都是可以被逆向运算的,也就是说,可以根据加密得到的结果,进行反向运算,还原出原始密码!通常,加密算法仅用于保障数据在传输过程中的安全!在对密码进行加密处理并存入到数据库中时,应该使用**不可逆**的算法!许多**哈希算法**,或基于哈希算法的**消息摘要算法**都是不可逆的!#### 关于消息摘要算法典型的消息摘要算法有:- SHA(Secure Hash Algorithm)家族算法- SHA-1(160位算法)- SHA-256(256位算法)- SHA-384(384位算法)- SHA-512(512位算法)
- MD(Message Digest)系列算法- MD2(128位算法)- MD4(128位算法)- MD5(128位算法)消息摘要算法原本是用于验证接收方所接收的数据与发送方所发出的数据是否一致。消息摘要算法有几个典型特征:- 如果消息相同,则摘要一定相同
- 如果消息不同,则摘要极大概率会不同- 必然存在n个不同的消息,摘要完全相同
- 使用同一种算法时,无论消息长度是多少,摘要的长度是固定的#### 在项目中使用MD5算法在Spring框架中,提供了`DigestUtils`,可以非常便利的使用MD5算法将消息处理为摘要:```java
public class Md5Tests {@Testvoid encode() {String rawPassword = "123456";String encodedPassword = DigestUtils.md5DigestAsHex(rawPassword.getBytes());System.out.println("原文:" + rawPassword);System.out.println("密文:" + encodedPassword);}}
算法位数对安全性的影响
以MD5算法为例,它是128位的算法,即其运算结果是由128个二进制位组成的,所以,其运算结果的排列组件有2的128次方种,这个数字转换成十进制是:340282366920938463463374607431768211456。
理论上,使用MD5算法时,要想找到2个不同的消息运算出相同的摘要,概率应该是340282366920938463463374607431768211456分之1!或者,也可以认为,你至少需要运算340282366920938463463374607431768211456次,才可以找到2个不同的消息运算出相同的摘要。
相比之下,更高位数的算法,理论上,更难找出不同的消息运算出相同的摘要!
一般情况下,由于MD5的安全系数已经较高,所以,不一定需要使用位数更高的算法!
关于消息摘要算法的破解 – 学术
当2个不同的消息,运算出相同的摘要,从学术上,称之为“碰撞”。
理论上,128位的算法,其碰撞概率应该是2的128次方分之1。
关于消息算法的破解,主要是研究其碰撞概率,是否可以使用更少次数的运算实现碰撞!而不是尝试根据摘要进行逆向运算还原出消息!
目前,SHA-1算法已经被视为不安全的算法,它是160位算法,经过研究,只需要经过2的60几次方的运算就可以发生碰撞,即SHA-1的安全系数与60几位的算法几乎相当。
关于消息摘要算法的“破解” – 根据摘要得到消息
网上有许多平台可以做到“根据密文还原出原文”,这些平台都是记录大量的原文与密文的对应关系,当尝试“破解”时,本质上是在做查询操作,大概是:
select 原文 from 数据表 where 密文=?
例如,某平台明确的说明了:
本站针对md5、sha1、sha256等全球通用公开的加密算法进行反向查询,通过穷举字符组合的方式,创建了明文密文对应查询数据库,创建的记录约90万亿条,占用硬盘超过500TB,查询成功率95%以上,很多复杂密文只有本站才可查询。本站专注于各种公开算法,已稳定运行17年。
如果密码可以使用全部的可打印字符,7位长度的密码的排列组合有约70万亿种,8位长度的密码的排列组件在此基础上需要乘以95,则以上平台不可能记录8位长度的所有明文密文的对应关系!也就是说,只要原始密码的长度达到8位,这些平台就可能无法根据密文查询出原文,原始密码的长度越长,或原始密码的强度越高(由多种元素组成,例如大小写字母、数字、标点符号),被这些平台收录的可能性就越低!
如何进一步保障用户的密码安全 – 加盐
盐值的本质就只是一个外部人员很难预测到的字符串,它将作用于处理加密过程中,例如:
// 以下1行定义了盐值
String salt = "fsd4W87i78oiAsUu43IEF";String rawPassword = "123456";
String encodedPassword = DigestUtils.md5DigestAsHex((rawPassword + salt).getBytes());
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 将原始密码和盐值一起被处理System.out.println("原文:" + rawPassword);
System.out.println("密文:" + encodedPassword);
当然,盐值应该如何使用,也没有明确的规定,你可以:
String encodedPassword = DigestUtils.md5DigestAsHex((rawPassword + salt).getBytes());
或者:
String encodedPassword = DigestUtils.md5DigestAsHex((salt + rawPassword).getBytes());
甚至:
String encodedPassword = DigestUtils.md5DigestAsHex((salt + rawPassword + salt + rawPassword + salt + salt).getBytes());
总而言之,使用盐的目的是”使得被MD5运算的原始数据变得更加复杂“。
你甚至可以使用随机的盐值,例如:
String salt = UUID.randomUUID().toString(); // 使用UUID作为盐值
String rawPassword = "123456";
String encodedPassword = DigestUtils.md5DigestAsHex((rawPassword + salt).getBytes());
使用随机盐时必须注意:你需要将随机的盐值保存下来,否则,后续你将无法验证密码!
至于如何保存,方式有许多,例如在数据表中添加新的字段来保存盐值,或者,把盐值直接作为密码的一部分,例如:
String salt = UUID.randomUUID().toString();
String rawPassword = "123456";
String encodedPassword = DigestUtils.md5DigestAsHex((rawPassword + salt).getBytes()) + salt;
System.out.println("盐值:" + salt);
System.out.println("原文:" + rawPassword);
System.out.println("密文:" + encodedPassword);
密码加密原则 – 小结
关于密码加密处理:
- 不可以使用加密算法,只能使用消息摘要算法或其它哈希算法
- 不建议使用SHA-1
- 应该要求用户使用更长的、强度更高的密码,避免容易被反查(根据密文查询得到原文)
- 应该进行加盐处理
- 你还可以使用多重加密(使用同一个算法,或不同算法,对数据进行反复运算)
- 可以考虑使用位数更长的算法(在MD5的基础上,改为使用SHA-256 / SHA-384 / SHA-512)
**注意:**无论你综合使用以上哪些做法,最终,可能都无法避免内部人员泄密(算法、加密参数、加密过程、密文都是破解时的已知条件)导致的穷举式的暴力破解,而BCrypt算法是被设计得运算效率极低的算法,可以非常有效的避免被暴力破解。
相关文章:

第四阶段-12关于Spring Security框架,RBAC,密码加密原则
关于csmall-passport项目 此项目主要用于实现“管理员”账号的后台管理功能,主要实现: 管理员登录添加管理员删除管理员显示管理员列表启用 / 禁用管理员 关于RBAC RBAC:Role-Based Access Control,基于角色的访问控制 在涉及…...
JPA——Date拓展之Calendar
Java Calendar 是时间操作类,Calendar 抽象类定义了足够的方法,在某一特定的瞬间或日历上,提供年、月、日、小时之间的转换提供方法 一、获取具体时间信息 1. 当前时间 获取此刻时间的年月日时分秒 Calendar calendar Calendar.getInstance(); int …...

一文吃透 Spring 中的 AOP 编程
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...

Apple主推的智能家居是什么、怎么用?一篇文章带你从零完全入门 HomeKit
如果你对智能家居有所了解,那应该或多或少听人聊起过 HomeKit。由 Apple 开发并主推的的 HomeKit 既因为产品选择少、价格高而难以成为主流,又因其独特的优秀体验和「出身名门」而成为智能家居领域的焦点。HomeKit 究竟是什么?能做什么&#…...

SpringCloud系列知识快速复习 -- part 1(SpringCloud基础知识,Docker,RabbitMQ)
SpringCloud知识快速复习SpringCloud基础知识微服务特点SpringCloud常用组件服务拆分和提供者与消费者概念Eureka注册中心原理Ribbon负载均衡原理负载均衡策略饥饿加载Nacos注册中心服务分级存储模型权重配置环境隔离Nacos与Eureka的区别Nacos配置管理拉取配置流程配置热更新配…...

2023上半年北京/上海/广州/深圳NPDP产品经理认证报名
产品经理国际资格认证NPDP是国际公认的唯一的新产品开发专业认证,集理论、方法与实践为一体的全方位的知识体系,为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会(PDMA)成立于1979年…...

面试半年,总结了1000道2023年Java架构师岗面试题
半年前还在迷茫该学什么,怎样才能走出现在的困境,半年后已经成功上岸阿里,感谢在这期间帮助我的每一个人。 面试中总结了1000道经典的Java面试题,里面包含面试要回答的知识重点,并且我根据知识类型进行了分类…...
通过MySQL驱动拦截器实现执行sql耗时计算
文章目录背景具体实现MySQL5MySQL6MySQL8使用方法测试结果背景 公司的一个需求,公司既有的链路追踪日志组件要支持MySQL的sql执行时间打印,要实现链路追踪常用的手段就是实现第三方框架或工具提供的拦截器接口或者是过滤器接口,对于MySQL也不…...

易基因|独家分享:高通量测序后的下游实验验证方法——DNA甲基化篇
大家好,这里是专注表观组学十余年,领跑多组学科研服务的易基因。此前,我们分享了DNA甲基化研究的测序数据挖掘思路(点击查看详情),进而鉴定出研究的目的基因或目标区域的DNA甲基化。做完测序后,…...
java基础系列(七) 同步和异步理解
一. 问题描述 同步传输和异步传输是web和数据库的重要知识点,会被很多老师强调。那么,它们有什么相同点和不同点?它们对于我们学习编程的意义在哪里? 二. 概念 首先什么是同步和异步? 这里的同步是指&…...
吉林大学 程序设计基础 2022级 OJ期末考试 2.23
本人能力有限,发出只为帮助有需要的人。 以下为实验课的复盘,内容会有大量失真,请多多包涵。 1.双手剑士的最优搭配 每把剑有攻击力和防御力两个属性。双手剑士可以同时拿两把剑,其得到攻击力为两把剑中的攻击力的最大值&#…...
【项目实战】SpringMVC拦截器实战 - 自定义拦截器防止重复提交
一、背景说明 如何能够实现防止重复提交呢?以下是一种可选的方式。 二、代码实战 2.1 注册重复提交拦截器到SpringMVC中 @Configuration @AllArgsConstructor public class ResourcesConfig implements WebMvcConfigurer {private final RepeatSubmitInterceptor repeatSu…...

C++ STL:容器 Container
文章目录1、序列容器1.1、容器共性1.2、vectorvector 结构* vector 扩容原理* vector 迭代器失效1.3、dequedeque 结构deque 迭代器deque 模拟连续空间1.4、listlist 特殊操作list 结构list 迭代器2、关联式容器2.1、容器共性2.2、容器特性3、无序关联式容器3.1、容器共性3.2、…...

urllib之urlopen和urlretrieve的headers传入以及parse、urlparse、urlsplit的使用
urllib库是什么?urllib库python的一个最基本的网络请求库,不需要安装任何依赖库就可以导入使用。它可以模拟浏览器想目标服务器发起请求,并可以保存服务器返回的数据。urllib库的使用:1、request.urlopen(1)只能传入url的方式from http.clie…...

【C++】二叉搜索树的模拟实现
一、概念 二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树: 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值若它的右子树不为空,则右子树上所有节点的值都大于根节点的值它的左右子树也分别…...

HNU工训中心:元器件及测量基础实验报告
工训中心的牛马实验 1.实验目的 1.熟悉测量验证常用元器件参数、 并采用替代法(测量回路电流)测量其伏安特性的方法。 2.熟悉测量误差及减小测量误差注意事项 2.实验仪器和器材 1.实验仪器. 直流稳压电源型号:IT6302 台式多用表型号:UT805A 2.实验( 箱)器材 电路实验箱…...

博客系统--自动化测试
项目体验地址(账号:123,密码:123)http://120.53.20.213:8080/blog_system/login.html项目后端说明:http://t.csdn.cn/32Nnv项目码云Gitee地址:https://gitee.com/GoodManSS/project/tree/master…...

Day903.自增主键不能保证连续递增 -MySQL实战
自增主键不能保证连续递增 Hi,我是阿昌,今天学习记录的是关于自增主键不能保证连续递增的内容。 MySql保证了主键是自增,但不相对连续;帮助开发人员快速识别每个行的唯一性,并提高查询效率。 自增主键可以让主键索引…...

02-MyBatis查询-
文章目录Mybatis CRUD练习1,配置文件实现CRUD1.1 环境准备Debug01: 别名mybatisx报错1.2 查询所有数据1.2.1 编写接口方法1.2.2 编写SQL语句1.2.3 编写测试方法1.2.4 起别名解决上述问题1.2.5 使用resultMap解决上述问题1.2.6 小结1.3 查询详情1.3.1 编写接口方法1.…...
外盘国际期货招商:2023年3月关注日历,把握重要投资机会
2023年3月大事件日历 关注大事日历,把握重要投资机会 3月1日:马斯克推出特斯拉宏图第三篇章 3月1-2日:G20外长会议 3月4-5日:全国两会召开 3月9日:中国2月CPI、PPI数据 待定(前次进行日期:…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...

Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...