springboot整合shiro的实战教程(一)

文章目录

    • 1.权限的管理
      • 1.1 什么是权限管理
      • 1.2 什么是身份认证
      • 1.3 什么是授权
    • 2.什么是shiro
    • 3.shiro的核心架构
      • 3.1 Subject
      • 3.2 SecurityManager
      • 3.3 Authenticator
      • 3.4 Authorizer
      • 3.5 Realm
      • 3.6 SessionManager
      • 3.7 SessionDAO
      • 3.8 CacheManager
      • 3.9 Cryptography
    • 4. shiro中的认证
      • 4.1 认证
      • 4.2 shiro中认证的关键对象
      • 4.3 认证流程
      • 4.4 认证的开发基础流程
        • 引入依赖
        • 引入shiro配置文件并加入如下配置
        • 开发认证代码
        • 自定义realm
      • 4.5认证的开发的自定义realm
        • 自定义realm
        • 认证开发认证代码
      • 4.6使用MD5和Salt
        • 自定义MD5和Salt realm
        • 使用md5 + salt 认证
  • 5. shiro中的授权
      • 5.1 授权
      • 5.2 关键对象
  • 5.3 授权流程
  • 5.4 shiro中授权方式
    • **基于角色的访问控制**
    • **基于资源的访问控制**
      • 具体的使用方式如下:
  • 5.5 shiro的开发授权
    • realm的实现
    • 授权

在这里插入图片描述

1.权限的管理

1.1 什么是权限管理

基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。

权限管理包括用户身份认证授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。

1.2 什么是身份认证

身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。

1.3 什么是授权

授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的


2.什么是shiro

Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.

Shiro 是一个功能强大且易于使用的Java安全框架,它执行身份验证、授权、加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序—从最小的移动应用程序到最大的web和企业应用程序。

Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。


3.shiro的核心架构

在这里插入图片描述

3.1 Subject

Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权

3.2 SecurityManager

SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。

SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。

3.3 Authenticator

Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。

3.4 Authorizer

Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。

3.5 Realm

Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。

  • ​ 注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。

3.6 SessionManager

sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。

3.7 SessionDAO

SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。

3.8 CacheManager

CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。

3.9 Cryptography

Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

4. shiro中的认证

4.1 认证

身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。

4.2 shiro中认证的关键对象

  • Subject:主体

访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;

  • Principal:身份信息

是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。

  • credential:凭证信息

是只有主体自己知道的安全信息,如密码、证书等。

4.3 认证流程

在这里插入图片描述

4.4 认证的开发基础流程

引入依赖
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.5.3</version>
</dependency>
引入shiro配置文件并加入如下配置

创建一个shiro.ini的文件
在这里插入图片描述

开发认证代码
public class TestAuthenticator {public static void main(String[] args) {//1.创建安全管理器对象DefaultSecurityManager securityManager = new DefaultSecurityManager();//2.给安全管理器设置realmsecurityManager.setRealm(new IniRealm("classpath:shiro.ini"));//3。将安装工具类中设置默认安全管理器SecurityUtils.setSecurityManager(securityManager);//4.获取主体对象 subjectSubject subject = SecurityUtils.getSubject();//5.创建token令牌UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");try {subject.login(token);//用户登录System.out.println("登录成功~~"+subject.isAuthenticated());} catch (UnknownAccountException e) {e.printStackTrace();System.out.println("用户名错误!!"+subject.isAuthenticated());}catch (IncorrectCredentialsException e){e.printStackTrace();System.out.println("密码错误!!!"+subject.isAuthenticated());}}
}

在这里插入图片描述
当然我们这里使用的文件传输的realm,我们也可以自定义realm

自定义realm
public class CustomerRealm extends  AuthorizingRealm{//授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {return null;}//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String principal = (String) token.getPrincipal();System.out.println(principal);//根据身份信息使用jdbc mybatis查询相关数据库if("xiaochen".equals(principal)){//参数1:正确的用户名 参数2:返回数据库中正确的密码 参数3:提供当前realm的名字return new SimpleAuthenticationInfo(principal,"123",this.getName());}return null;}
}

4.5认证的开发的自定义realm

自定义realm
public class CustomerRealm extends  AuthorizingRealm{//授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {return null;}//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String principal = (String) token.getPrincipal();System.out.println(principal);//根据身份信息使用jdbc mybatis查询相关数据库if("xiaochen".equals(principal)){//参数1:正确的用户名 参数2:返回数据库中正确的密码 参数3:提供当前realm的名字return new SimpleAuthenticationInfo(principal,"123",this.getName());}return null;}
}
认证开发认证代码
public class TestAuthenticatorCusttomerRealm {public static void main(String[] args) {//创建securityManagerDefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();//IniRealm realm = new IniRealm("classpath:shiro.ini");//设置为自定义realm获取认证数据defaultSecurityManager.setRealm(new CustomerRealm());//将安装工具类中设置默认安全管理器SecurityUtils.setSecurityManager(defaultSecurityManager);//获取主体对象Subject subject = SecurityUtils.getSubject();//创建token令牌UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");try {subject.login(token);//用户登录System.out.println("登录成功~~");} catch (UnknownAccountException e) {e.printStackTrace();System.out.println("用户名错误!!");}catch (IncorrectCredentialsException e){e.printStackTrace();System.out.println("密码错误!!!");}}
}

4.6使用MD5和Salt

自定义MD5和Salt realm
   @Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {//获取身份信息String principal= (String) authenticationToken.getPrincipal();//根据用户名查询数据库if ("xiaochen".equals(principal)){//参数1:数据库用户名, 参数2:数据库md5+salt之后的密码。参数3:注册时的随机盐,参数4:realm的名字return new SimpleAuthenticationInfo(principal,"e4f9bf3e0c58f045e62c23c533fcf633",ByteSource.Util.bytes("X0*7ps"),this.getName());}return null;}
使用md5 + salt 认证
 public static void main(String[] args) {
//创建securityManagerDefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();//IniRealm realm = new IniRealm("classpath:shiro.ini");//设置为自定义realm使用hash凭证匹配器 明确使用什么比较器。CustomerMd5Realm realm =new CustomerMd5Realm();HashedCredentialsMatcher credentialsMatcher=new HashedCredentialsMatcher();credentialsMatcher.setHashAlgorithmName("md5");credentialsMatcher.setHashIterations(1024);//设置散列次数realm.setCredentialsMatcher(credentialsMatcher);defaultSecurityManager.setRealm(realm);//将安装工具类中设置默认安全管理器SecurityUtils.setSecurityManager(defaultSecurityManager);//获取主体对象Subject subject = SecurityUtils.getSubject();//创建token令牌UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");try {subject.login(token);//用户登录System.out.println("登录成功~~");} catch (UnknownAccountException e) {e.printStackTrace();System.out.println("用户名错误!!");}catch (IncorrectCredentialsException e){e.printStackTrace();System.out.println("密码错误!!!");}}

5. shiro中的授权

5.1 授权

授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。

5.2 关键对象

授权可简单理解为who对what(which)进行How操作:

Who,即主体(Subject),主体需要访问系统中的资源。

What,即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。

How,权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。

5.3 授权流程

在这里插入图片描述

5.4 shiro中授权方式

基于角色的访问控制

RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制

if(subject.hasRole("admin")){//操作什么资源
}

基于资源的访问控制

RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制

if(subject.isPermission("user:update:01")){ //资源实例//对01用户进行修改
}
if(subject.isPermission("user:update:*")){  //资源类型//对01用户进行修改
}

具体的使用方式如下:

  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String primaryPrincipal = (String) principals.getPrimaryPrincipal();System.out.println("身份信息 " + primaryPrincipal);//根据身份信息,用户名,获取用户角色信息,以及权限信息。xiaochen admin userSimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();//将数据库中查询的角色信息,以及权限信息simpleAuthorizationInfo.addRole("admin");simpleAuthorizationInfo.addRole("user");//将数据库查询权限赋值个权限对象simpleAuthorizationInfo.addStringPermission("user:*:01");simpleAuthorizationInfo.addStringPermission("product:create:*");return simpleAuthorizationInfo;}

5.5 shiro的开发授权

realm的实现

public class CustomerMd5Realm extends AuthorizingRealm {//授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String primaryPrincipal = (String) principals.getPrimaryPrincipal();System.out.println("身份信息 " + primaryPrincipal);//根据身份信息,用户名,获取用户角色信息,以及权限信息。xiaochen admin userSimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();//将数据库中查询的角色信息,以及权限信息simpleAuthorizationInfo.addRole("admin");simpleAuthorizationInfo.addRole("user");//将数据库查询权限赋值个权限对象simpleAuthorizationInfo.addStringPermission("user:*:01");simpleAuthorizationInfo.addStringPermission("product:create:*");return simpleAuthorizationInfo;}//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {//获取身份信息String principal= (String) authenticationToken.getPrincipal();//根据用户名查询数据库if ("xiaochen".equals(principal)){//参数1:数据库用户名, 参数2:数据库md5+salt之后的密码。参数3:注册时的随机盐,参数4:realm的名字return new SimpleAuthenticationInfo(principal,"e4f9bf3e0c58f045e62c23c533fcf633",ByteSource.Util.bytes("X0*7ps"),this.getName());}return null;}
}

授权

public class TestCustomerMd5Authenticator {public static void main(String[] args) {//创建securityManagerDefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();//IniRealm realm = new IniRealm("classpath:shiro.ini");//设置为自定义realm使用hash凭证匹配器 明确使用什么比较器。CustomerMd5Realm realm =new CustomerMd5Realm();HashedCredentialsMatcher credentialsMatcher=new HashedCredentialsMatcher();credentialsMatcher.setHashAlgorithmName("md5");credentialsMatcher.setHashIterations(1024);//设置散列次数realm.setCredentialsMatcher(credentialsMatcher);defaultSecurityManager.setRealm(realm);//将安装工具类中设置默认安全管理器SecurityUtils.setSecurityManager(defaultSecurityManager);//获取主体对象Subject subject = SecurityUtils.getSubject();//创建token令牌UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");try {subject.login(token);//用户登录System.out.println("登录成功~~");} catch (UnknownAccountException e) {e.printStackTrace();System.out.println("用户名错误!!");}catch (IncorrectCredentialsException e){e.printStackTrace();System.out.println("密码错误!!!");}//认证用户进行授权if(subject.isAuthenticated()){//基于角色权限管理boolean admin = subject.hasRole("admin");System.out.println(admin);//基于多角色的控制subject.hasAllRoles(Arrays.asList("admin","user"));//基于是否有其中一个角色boolean[] booleans=subject.hasRoles(Arrays.asList("admin","user","super"));for (boolean aboolean:booleans){System.out.println(aboolean);}System.out.println("=====================");//基于权限字符串的访问控制 资源标识符:操作:资源操作boolean permitted = subject.isPermitted("user:*:01");System.out.println("权限"+permitted);}}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/733279.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我的 4096 创作纪念日

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

YOLOv8+DeepSort/ByteTrack-PyQt-GUI / yolov5 deepsort 行人/车辆(检测 +计数+跟踪+测距+测速)

YoloV8结合可视化界面和GUI&#xff0c;实现了交互式目标检测与跟踪&#xff0c;为用户提供了一体化的视觉分析解决方案。通过YoloV8算法&#xff0c;该系统能够高效准确地检测各类目标&#xff0c;并实时跟踪它们的运动轨迹。 用户可以通过直观的可视化界面进行操作&#xff…

Unity性能优化篇(七) UI优化注意事项以及使用Sprite Atlas打包精灵图集

UI优化注意事项 1.尽量避免使用IMGUI(OnGUI)来做游戏时的UI&#xff0c;因为IMGUI的开销比较大。 2.如果一个UGUI的控件不需要进行射线检测&#xff0c;则可以取消勾选Raycast Target 3.尽量避免使用完全透明的图片和UI控件。因为即使完全透明&#xff0c;我们看不见它&#xf…

常见BUG如何在测试过程中分析定位

前言 在测试的日常工作中&#xff0c;相信经常有测试的小伙伴遇到类似的情况&#xff1a;在项目上线时&#xff0c;只要出现问题&#xff08;bug&#xff09;&#xff0c;就很容易成为“背锅侠”。 软件测试人员在工作中是无法避免的要和开发人员和产品经理打交道的&#xff…

117.龙芯2k1000-pmon(16)- linux下升级pmon

pmon的升级总是有些不方便&#xff0c;至少是要借助串口和串口工具 如果现场不方便连接串口&#xff0c;是不是可以使用网线升级pmon呢&#xff1f; 答案当然是可行的。 环境&#xff1a;2k1000linux3.10麒麟的文件系统 如今我已经把这个工具开发出来了。 GitHub - zhaozhi…

网络工程师笔记10 ( RIP / OSPF协议 )

RIP 学习路由信息的时候需要配认证 RIP规定超过15跳认定网络不可达 链路状态路由协议-OSPF 1. 产生lsa 2. 生成LSDB数据库 3. 进行spf算法&#xff0c;生成最有最短路径 4. 得出路由表

【探索C++容器:set和map的使用】

[本节目标] 1. 关联式容器 2. 键值对 3. 树形结构的关联式容器 1. 关联式容器 在初阶阶段&#xff0c;我们已经接触过STL中的部分容器&#xff0c;比如&#xff1a;vector、list、deque、forward_list(C11)等&#xff0c;这些容器统称为序列式容器&#xff0c;因为其底层为…

Toyota Programming Contest 2024#3(AtCoder Beginner Contest 344)(A~C)

A - Spoiler 竖线里面的不要输出&#xff0c;竖线只有一对&#xff0c;且出现一次。 #include <bits/stdc.h> //#define int long long #define per(i,j,k) for(int (i)(j);(i)<(k);(i)) #define rep(i,j,k) for(int (i)(j);(i)>(k);--(i)) #define debug(a) cou…

链表|面试题 02.07.链表相交

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {ListNode *l NULL, *s NULL;int lenA 0, lenB 0, gap 0;// 求出两个链表的长度s headA;while (s) {lenA ;s s->next;}s headB;while (s) {lenB ;s s->next;}// 求出两个链表长度差if (lenA &…

stm32学习笔记:SPI通信协议原理(未完)

一、SPI简介(serial Peripheral Interface&#xff08;串行 外设 接口&#xff09;) 1、电路模式&#xff08;采用一主多从的模式&#xff09;、同步&#xff0c;全双工 1 所有SPI设备的SCK、MOSI、MISO分别连在一起 2 主机另外引出多条SS控制线&#xff0c;分别接到各从机的S…

DetNet论文速读

paper&#xff1a;DetNet: A Backbone network for Object Detection 存在的问题 最近的目标检测模型通常依赖于在ImageNet分类数据集上预训练的骨干网络。由于ImageNet的分类任务不同于目标检测&#xff0c;后者不仅需要识别对象的类别&#xff0c;而且需要对边界框进行空间…

音视频开发_音频基础知识

如何采集声音——模数转换原理 声音模数转换是将声音信号从模拟形式转换为数字形式的过程。它是数字声音处理的基础&#xff0c;常用于语音识别、音频编码等应用中。 音视频通信流程 音视频采集&#xff1a;首先是从麦克风、摄像头等设备中采集音频和视频数据&#xff0c;将现…

【Windows】VMware虚拟机应用(一):下载安装 VMware Workstation

目录 一、下载 二、注意事项 三、安装 四、密钥激活 4.1 密钥 4.2 激活 一、下载 进入官网下载页 VMware Customer Connect | The All-In-One VMware Product Support Portal 先登录&#xff0c;下载时要求登录。 点【Downloads】 进入产品下载页面&#xff0c;切换到…

手写简易操作系统(三)--加载Loader

前情提要 上一节我们讲了如何启动计算机&#xff0c;这一节我们讲如何加载内核&#xff0c;内核是存在于硬盘上的一段程序&#xff0c;要加载这段程序&#xff0c;那么必然需要从硬盘上读取数据&#xff0c;这里我们就需要使用 ATA PIO 模式 根据ATA规范&#xff0c;所有符合A…

docker部署springboot jar包项目

docker部署springboot jar包项目 前提&#xff0c;服务器环境是docker环境&#xff0c;如果服务器没有安装docker&#xff0c;可以先安装docker环境。 各个环境安装docker&#xff1a; Ubuntu上安装Docker&#xff1a; ubuntu离线安装docker: CentOS7离线安装Docker&#xff1…

可视化场景(2):电商大屏-引爆业绩,直观呈现

hello&#xff0c;我是贝格前端工场&#xff0c;本期分享可视化大屏在电商领域的应用&#xff0c;如需要定制&#xff0c;可以与我们联络&#xff0c;开始了。 电商领域的可视化大屏可以提供实时的销售数据、用户行为分析、库存管理等信息&#xff0c;帮助企业实时监控经营状况…

不知道吧,腾讯云轻量应用服务器使用有一些限制!

腾讯云轻量应用服务器相对于云服务器CVM是有一些限制的&#xff0c;比如轻量服务器不支持更换内网IP地址&#xff0c;不支持自定义私有网络VPC&#xff0c;内网连通性方面也有限制&#xff0c;轻量不支持CPU内存、带宽或系统盘单独升级&#xff0c;只能整个套餐整体升级&#x…

Anthropic 公司最新宣布,他们的 AI 聊天机器人模型击败了 OpenAI 的 GPT-4

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Unity Text文本实现滚动跑马灯效果

在一些公告上我们经常会看到文字滚动跑马灯的效果。 那么在Unity上如何实现&#xff1f; 1、首先创建一个Text(或者TextMeshPro)组件&#xff0c;然后输入需要显示的文本内容&#xff0c;如图&#xff1a; 2、编写控制脚本TextRoll.cs&#xff1a; using System.Collections…

钉钉h5应用 globalthis is not defined vite client

钉钉h5应用 globalthis is not defined vite client problem 背景 钉钉h5应用使用 vue3 vite 构建的前端工程 问题 h5页面在pc端浏览器和pc端钉钉打开正常h5页面在移动端钉钉打开异常 页面空白 通过调试工具找到报错信息 globalthis is not defined vite client reason …