首页
友情链接
精美壁纸
给我留言
更多
关于我
Search
1
uniapp Click点击事件冲突解决
4,587 阅读
2
【插件】UNI APP 实现商米打印机功能支持T1,T2,V2机型
4,001 阅读
3
【测试可用】个人码免签支付系统源码/免签支付系统/微信支付平台
2,040 阅读
4
windows10下docker:给已存在的容器添加端口映射的方法
1,252 阅读
5
Java Validation参数校验注解使用
1,234 阅读
Java
Spring Boot
Spring Mvc
Java基础
进阶知识
前端
uniapp
小程序/公众号
JavaScript
HTML/CSS
Vue
PHP
开源软件
商城
营销工具
开发工具
视频/教程
Discuz主题/插件
typecho主题/插件
SEO杂谈
数据库
MongoDB
MySQL
Redis
单片机
概念说明
电路相关
Python
devops
docker
k8s
linux
职场杂谈
登录
/
注册
Search
标签搜索
python
mysql
人人商城
php
java
docker
typecho
插件
微擎
seo
spring boot
discuz
队列
uni-app
phpcms
教程视频
开源系统
源码
工具
css
哈根达斯
累计撰写
108
篇文章
累计收到
161
条评论
首页
栏目
Java
Spring Boot
Spring Mvc
Java基础
进阶知识
前端
uniapp
小程序/公众号
JavaScript
HTML/CSS
Vue
PHP
开源软件
商城
营销工具
开发工具
视频/教程
Discuz主题/插件
typecho主题/插件
SEO杂谈
数据库
MongoDB
MySQL
Redis
单片机
概念说明
电路相关
Python
devops
docker
k8s
linux
职场杂谈
页面
友情链接
精美壁纸
给我留言
关于我
搜索到
22
篇与
Java
的结果
2025-01-15
Java的泛型应用与了解
Java泛型的功能主要是在编译阶段进行类型安全的检查,让开发者能够编写出更具通用性和灵活性的代码,有效避免在程序运行过程中出现类型转换的错误。泛型的作用:确保类型安全:泛型能够在编译时进行严格的类型检查,确保在操作集合或其他泛型类时,不会出现类型不一致的情况,从而降低运行时出现ClassCastException异常的风险。提升代码复用性:借助泛型,代码可以适用于多种不同的数据类型,减少了代码的重复编写,使得代码的可读性和可维护性得到提升。避免显式类型转换:通过在编译时明确指定类型参数,泛型省去了在运行时进行显式类型转换的步骤,简化了代码的编写。示例代码:List<String> stringList = new ArrayList<>(); stringList.add("Hello"); String message = stringList.get(0); // 无需进行类型转换泛型的实际应用场景集合框架中的应用:Java的集合框架大量运用了泛型。像List<T>、Set<T>以及Map<K, V>等接口,能够针对不同的数据类型实现统一的操作方式。泛型方法的定义:不仅可以定义泛型类,还可以定义泛型方法,这样可以使方法具备处理多种不同数据类型的能力。例如:public static <T> void printElements(T[] elements) { for (T element : elements) { System.out.println(element); } }为何需要泛型的通俗解释在Java 5版本之前,泛型是不存在的,那时的代码也能正常运行,那么为何要引入泛型呢,它能为我们带来哪些好处呢?先来看下面这段代码:List list = new ArrayList(); list.add("yes"); // 添加字符串 list.add(233); // 添加整数在没有泛型的时期,集合中添加的数据不会受到任何类型限制,全部被视为Object类型。或许有人会觉得这样很自由,确实,自由度很高,但是代码的约束性越弱,出错的概率就越高,在使用上也会带来诸多不便,比如在获取数据时需要进行强制类型转换。如果一不小心获取了错误的类型,虽然代码能够通过编译,但在运行时却会抛出异常。综上所述,Java引入了泛型机制。泛型的作用就是增加了一层类型约束。有了这层约束,由于已经声明了类型,所以在编译阶段就能识别出不准确的类型元素。这样可以让错误提前被发现,避免在运行时才暴露出来。并且也不需要在代码中显式地进行强制类型转换,从下面的代码可以看出,可以直接获取到String类型的元素。我们再总结一下泛型的优势:提升了代码的可读性,能够一眼看出集合(或其他泛型类)所存储的数据类型能在编译阶段检查类型安全,增强程序的健壮性省去了显式类型转换的麻烦(实际上内部已经完成了类型转换)提高了代码的复用性,定义好的泛型可以让一个方法(或类)适用于所有类型(虽然以前使用Object也可以实现,但相对较为繁琐)为何有人说Java的泛型是伪泛型来看下面这段代码:可以看到,虽然声明的是一个String类型的集合,但通过反射的方式却能够成功地向集合中插入int类型的数据。这表明在运行时,泛型并没有起到任何作用!也就是说,在运行过程中,JVM无法获取到泛型的信息,也不会对其进行任何约束。可以认为Java的泛型仅在编译时有效,而在运行时则不存在泛型,这也是人们常说Java是伪泛型的原因。简而言之,Java的泛型仅在编译阶段发挥作用,而在JVM运行时则不存在泛型的概念。近期才哥整理出了一个可用于快速刷面试题的小程序,其中收录了常见面试题及其答案,涵盖了基础、并发、JVM、MySQL、Redis、Spring、SpringMVC、SpringBoot、SpringCloud、消息队列等多个类型,感兴趣的扫下方的小程序码进行体验。
2025年01月15日
3 阅读
0 评论
0 点赞
2025-01-14
Java 插件模式SPI学习与理解(Java SPI 、Spring SPI、Dubbo SPI)
SPI 全称为 Service Provider Interface,是一种服务发现机制,其核心在于将接口实现类的全限定名配置在文件中,由服务加载器读取并加载实现类,从而可在运行时为接口动态替换实现类,为程序提供拓展功能。1. 重新阐述 Java SPI 示例定义接口和实现类:首先定义一个名为 Robot 的接口,包含 sayHello 方法。public interface Robot { void sayHello(); } 然后创建两个实现类 OptimusPrime 和 Bumblebee,它们都实现了 Robot 接口并实现了 sayHello 方法。 public class OptimusPrime implements Robot { @Override public void sayHello() { System.out.println("Hello, I am Optimus Prime."); } } public class Bumblebee implements Robot { @Override public void sayHello() { System.out.println("Hello, I am Bumblebee."); } } 配置文件:在 META-INF/services 文件夹下创建名为 org.apache.spi.Robot 的文件(这里假设接口全限定名为 org.apache.spi.Robot),文件内容包含实现类的全限定名: org.apache.spi.OptimusPrime org.apache.spi.Bumblebee 测试代码: public class JavaSPITest { @Test public void sayHello() throws Exception { ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class); System.out.println("Java SPI"); // 两种遍历模式 serviceLoader.forEach(Robot::sayHello); Iterator<Robot> iterator = serviceLoader.iterator(); while (iterator.hasNext()) { Robot robot = iterator.next(); // 可以调用 robot.sayHello() 进行测试 } } } 2. 经典 Java SPI 应用:JDBC DriverManager传统 JDBC 驱动加载:在 JDBC4.0 之前,需要先使用 Class.forName("com.mysql.jdbc.Driver") 加载数据库驱动,然后进行连接操作。 // STEP 1: Register JDBC driver Class.forName("com.mysql.jdbc.Driver"); // STEP 2: Open a connection String url = "jdbc:xxxx://xxxx:xxxx/xxxx"; Connection conn = DriverManager.getConnection(url,username,password); 使用 SPI 后的 JDBC 驱动加载:JDBC4.0 之后利用 Java 的 SPI 扩展机制,无需手动调用 Class.forName 加载驱动,可直接获取连接。DriverManager 类是驱动管理器,在其静态初始化块中调用 loadInitialDrivers 方法,其中涉及 SPI 的使用: static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } 加载驱动的四个步骤:从系统变量获取驱动定义。用 SPI 获取驱动实现类(字符串形式)。ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); 遍历使用 SPI 获取的实现,实例化各实现类。 Iterator<Driver> driversIterator = loadedDrivers.iterator(); while(driversIterator.hasNext()) { driversIterator.next(); } 根据第一步的驱动列表实例化具体实现类。3. Java SPI 机制源码解析ServiceLoader 类的 load 方法:public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); } 该方法根据服务类型和类加载器创建 ServiceLoader 对象。- `ServiceLoader` 构造器:private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null)? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager()!= null)? AccessController.getContext() : null; reload(); } private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader); } 私有构造器创建懒迭代器 LazyIterator 对象,只有调用迭代方法时才执行加载逻辑。迭代器的 hasNext() 方法: public boolean hasNext() { if (acc == null) { return hasNextService(); } else { PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { public Boolean run() { return hasNextService(); } }; return AccessController.doPrivileged(action, acc); } } 该方法最终调用 hasNextService 方法,会通过加载器获取配置对象并解析 META-INF/services/ 目录下的文件。迭代器的 next() 方法: public S next() { if (acc == null) { return nextService(); } else { PrivilegedAction<S> action = new PrivilegedAction<S>() { public S run() { return nextService(); } }; return AccessController.doPrivileged(action, acc); } } 本质是调用 nextService 方法,通过反射加载类对象,实例化类并缓存到 providers 对象中。4. Java SPI 机制的缺陷无法按需加载,会遍历并实例化所有实现,即使部分实现类不需要或实例化耗时。获取实现类的方式仅能通过 Iterator,不够灵活,不能根据参数获取特定实现类。多线程使用 ServiceLoader 实例时不安全。5. Spring SPI 机制创建接口和实现类:定义 MyTestService 接口: public interface MyTestService { void printMylife(); } 创建实现类 WorkTestService 和 FamilyTestService: public class WorkTestService implements MyTestService { public WorkTestService(){ System.out.println("WorkTestService"); } public void printMylife() { System.out.println("我的工作"); } } public class FamilyTestService implements MyTestService { public FamilyTestService(){ System.out.println("FamilyTestService"); } public void printMylife() { System.out.println("我的家庭"); } } 配置文件:在 META-INF/spring.factories 中配置接口和实现类:com.courage.platform.sms.demo.service.MyTestService = com.courage.platform.sms.demo.service.impl.FamilyTestService,com.courage.platform.sms.demo.service.impl.WorkTestService 测试代码: List<MyTestService> myTestServices = SpringFactoriesLoader.loadFactories( MyTestService.class, Thread.currentThread().getContextClassLoader() ); for (MyTestService testService : myTestServices) { testService.printMylife(); } 与 Java SPI 的区别:Java SPI 一个服务接口对应一个配置文件,Spring SPI 一个 spring.factories 配置文件存放多个接口及实现类,以接口全限定名作为 key,实现类作为 value,多个实现类用逗号分隔。两者都无法获取特定的实现,只能按顺序获取所有实现。6. Dubbo SPI 机制配置文件:配置文件放在 META-INF/dubbo 路径下,以键值对方式配置实现类: optimusPrime = org.apache.spi.OptimusPrime bumblebee = org.apache.spi.Bumblebee 测试代码: public class DubboSPITest { @Test public void sayHello() throws Exception { ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class); Robot optimusPrime = extensionLoader.getExtension("optimusPrime"); optimusPrime.sayHello(); Robot bumblebee = extensionLoader.getExtension("bumblebee"); bumblebee.sayHello(); } } 特点:支持按需加载接口实现类,可通过键值对方式指定要加载的实现。相比 Java SPI 增加了 IOC 和 AOP 等特性。近期才哥整理出了一个可用于快速刷面试题的小程序,其中收录了常见面试题及其答案,涵盖了基础、并发、JVM、MySQL、Redis、Spring、SpringMVC、SpringBoot、SpringCloud、消息队列等多个类型,感兴趣的可以点击下方试试。
2025年01月14日
5 阅读
0 评论
0 点赞
2024-12-24
JWT一文全解,禁止这样使用,被发现立即领盒饭!
在当今的网络开发领域,JSON Web Tokens(JWT)被广泛提及和应用,但对于其在存储 Session 方面的适用性,却存在诸多误解。本文旨在深入探讨为何 JWT 并不适合担当存储 Session 的重任,并揭示其可能引发的安全问题,以期帮助开发者们更全面、准确地理解和运用 JWT。一、术语明确在展开讨论之前,先对一些关键术语进行清晰的界定,这有助于避免后续的混淆和歧义:无状态 JWT(Stateless JWT):这类 JWT Token 直接将 Session 数据编码于其中,所有与 Session 相关的信息都被封装在 Token 内部。有状态 JWT(Stateful JWT):此类型的 JWT Token 仅包含对 Session 的引用或者 Session ID,实际的 Session 数据则存放在服务端。Session token(亦称 Session cookie):这是一种标准的、可被签名的 Session ID 形式,许多 Web 框架(如 Laravel)长期以来都采用这种成熟的 Session 机制,其 Session 数据同样是存储在服务端。需要着重强调的是,本文的目的并非倡导“绝对不使用 JWT”,而是要明确指出 JWT 在作为 Session 机制时存在的弊端和风险。实际上,JWT 在其他特定场景下仍具有其独特的价值和用途,在本文结尾部分,将会简要介绍一些 JWT 的合理应用场景。二、对比对象的澄清常常有人错误地将 Cookies 和 JWT 进行不恰当的比较,这就如同将内存和硬盘简单对比一样,缺乏实际意义。Cookies 本质上是一种存储机制,而 JWT Tokens 则是经过加密和签名处理的令牌,二者并非相互对立的关系,相反,它们既可以独立使用,也能够结合运用。正确的比较方式应该是:Session 与 JWT 相对比,以及 Cookies 与 Local Storage 相对比。在后续的讨论中,本文将主要围绕 JWT Tokens 和 Session 的对比展开,并在必要时涉及 Cookie 和 Local Storage 的比较,这样的对比方式更能准确地反映实际情况和问题本质。三、JWT 被广泛宣扬的优势剖析(一)易于水平扩展?在众多关于 JWT 的宣传中,易于水平扩展被列为其优势之一。从技术层面来看,这一说法在使用无状态 JWT Tokens 时确实存在一定的合理性,但前提是要有相应的应用场景需求。然而,在实际情况中,绝大多数项目几乎并不需要这种高度的横向扩展能力。在常见的系统扩展场景中,存在许多更为简便易行的解决方案。例如:当需要在单台服务器上运行多个后端进程时,只需在该服务器上部署 Redis 服务来存储 Session 即可,这种方式简单高效,能够满足大部分常规业务的需求。对于运行多台服务器的情况,配备一台专门的 Redis 服务器用于集中存储 Session 数据,就能轻松实现 Session 的共享和管理,确保系统的稳定运行。在更为复杂的多集群环境中,通过会话保持(即粘滞会话)技术,也能够很好地应对 Session 的管理问题,而且这些技术在现有的软件系统架构中都得到了良好的支持,通常不需要应用层进行特殊的复杂处理。或许有人会考虑为未来的业务发展预留更多的调整空间,担心以后可能会需要无状态的会话机制。但从实际经验来看,后期对 Session 机制进行替换并非难事,唯一可能产生的影响是在迁移过程中所有用户需要重新登录一次。因此,综合考虑 JWT 可能带来的负面影响,在项目前期并没有必要为了一个不太确定的未来需求而强行采用 JWT 来实现 Session 管理。另外,对于近期有面试跳槽计划的开发者,建议在[Java面试库]()小程序上进行在线刷题,该小程序涵盖了 3000 余道 Java 面试题,几乎囊括了所有主流技术面试题,能够帮助开发者更好地准备面试,提升自身竞争力。(二)易于使用?实际上,JWT 在使用便利性方面并没有优势。使用 JWT 时,无论是在客户端还是服务端,都需要开发者自行处理 Session 的管理逻辑。相比之下,标准的 Session cookies 则具有开箱即用的特点,许多 Web 框架都对其进行了良好的封装和支持,开发者无需过多关注底层的实现细节,即可轻松使用 Session 功能。而 JWT 目前尚未被广泛集成到各种开箱即用的框架中,这就意味着研发人员需要投入更多的精力来处理与 JWT 相关的复杂逻辑,从这个角度来看,JWT 并不比传统的 Session 机制更易于使用。(三)更加灵活?关于 JWT 更加灵活这一说法,目前并没有确凿的证据和清晰的阐述来支撑。在实际应用中,几乎每个主流的 Session 实现都允许开发者直接将数据存储到 Session 中,这种数据存储和管理方式与 JWT 的机制并没有本质上的区别。因此,所谓的“JWT 更加灵活”更多的可能只是一种流行但缺乏实际依据的说法,在实际的开发过程中,开发者很难真切地感受到这种灵活性带来的实际价值和优势。(四)更加安全?有不少人认为 JWT Tokens 更加安全,其主要理由是 JWT 使用了加密技术。然而,这种观点是不准确的。实际上,签名后的 Cookies 相较于未签名的 Cookies 同样具有更高的安全性,而且这并不是 JWT 所独有的特性,许多优秀的 Session 实现都采用了签名后的 Cookies 来保障数据的安全传输和完整性(例如 Laravel 框架)。需要明确的是,单纯的“使用加密技术”并不能自动使某个事物变得更加安全,加密技术必须服务于特定的安全目标,并且要确保其应用方式是针对该目标的有效解决方案。如果加密技术使用不当,反而可能会引入新的安全风险,导致安全性降低。此外,还有一种关于“JWT 更加安全”的常见论述,即“JWT 不使用 Cookies 传输 Tokens”,这种说法是荒谬的。Cookie 仅仅是一条 HTTP 头信息,使用 Cookies 本身并不会导致不安全的情况发生。事实上,Cookies 在设计和实现上受到了特别良好的保护,能够有效地防止恶意的客户端代码对其进行篡改和窃取。如果担心 Session cookies 被他人拦截,正确的做法是采用 TLS(Transport Layer Security)加密协议来保障数据传输的安全。如果不使用 TLS,那么无论是哪种类型的 Session 机制,包括 JWT,都存在被拦截和窃取的风险,因为在未加密的网络环境中,数据的安全性无法得到有效的保障。(五)内置过期时间功能?JWT 的内置过期时间功能实际上并没有太大的实际价值和优势。虽然 JWT 可以在 Token 中设置过期时间,但在服务端同样能够轻松实现对 Session 过期时间的控制,而且许多现有的 Session 实现正是采用这种方式。相比之下,服务端的过期控制更加合理和灵活,因为这样可以让应用程序及时清除那些不再需要的 Session 数据,从而优化系统资源的利用和管理。而如果依赖无状态 JWT Tokens 的过期机制,由于其无法在服务端直接对过期的 Token 进行有效的管理和清理,可能会导致一些潜在的问题,例如 Session 数据的残留和浪费,以及在某些情况下可能会增加系统的复杂度和管理难度。(六)无需询问用户“本网站使用 Cookies”?这种说法是完全错误的。并不存在所谓的“Cookies 法律”专门针对 Cookies 的使用进行限制,实际上,有关 Cookies 的各种法律法规涵盖的范围更广,包括任何类型的“对某项服务的正常运行非严格必须的持久性 ID”,这意味着几乎所有可以想到的 Session 机制都在其监管范围内。具体来说:如果 Session 或 Token 是出于系统功能目的(例如保持用户的登录状态)而使用的,那么无论采用何种方式存储 Session,都无需征得用户的同意,因为这是保障系统正常运行所必需的功能。然而,如果 Session 或 Token 是用于其他目的(例如数据分析、用户追踪等),那么无论选择怎样的 Session 存储机制,都需要明确询问用户是否允许使用,以确保符合相关法律法规的要求和保护用户的隐私权益。(七)防止 CSRF 攻击?JWT 并不能有效地防止 CSRF(Cross-Site Request Forgery)攻击。目前,存储 JWT Tokens 主要有两种常见方式:一种是将其存入 Cookie 中,但这样做仍然容易受到 CSRF 攻击,与传统的 Session 机制类似,仍然需要采取特殊的防护措施来保护 Token 不被恶意利用,例如使用 CSRF Tokens 等技术来增强安全性。另一种方式是将 JWT 存储在其他地方,如 Local Storage 中。虽然这种方式在一定程度上可以避免 CSRF 攻击,但却带来了新的问题。因为使用 Local Storage 需要依赖 JavaScript 来获取和使用 Token,这就要求网站必须支持 JavaScript 才能正常访问,而且这还可能引发另一个完全不同且可能更加严重的漏洞,后续将会对此进行详细说明。需要明确的是,预防 CSRF 攻击的唯一正确方法是使用 CSRF Tokens,而 Session 机制本身与防止 CSRF 攻击并没有直接的关联,不能将防止 CSRF 攻击的期望寄托于 JWT 或其他 Session 机制上,而应该采用专门的 CSRF 防护技术来保障系统的安全。(八)更适用于移动端?这种说法是毫无根据的。目前市面上几乎所有可用的浏览器都对 Cookies 提供了良好的支持,因此也能够很好地支持基于 Cookies 的 Session 机制。同样,主流的移动端开发框架以及严谨的 HTTP 客户端库也都对 Session 机制有着完善的支持,无论是在移动端还是桌面端,Session 机制都能够稳定、高效地运行,不存在 JWT 在移动端具有独特优势的情况,所以这并不是一个选择 JWT 作为 Session 机制的合理理由。(九)适用于阻止 Cookies 的用户?这种情况发生的可能性非常低。通常情况下,用户如果选择阻止 Cookies,往往也会阻止其他任何形式的持久化数据存储,而不仅仅是针对 Cookies 本身。例如,Local Storage 以及其他能够实现 Session 持久化存储的机制(无论是否使用 JWT)都会受到影响。因此,即使采用 JWT 来试图解决用户阻止 Cookies 的问题,也很难达到预期的效果,因为这是一个更为复杂和独立的用户行为和隐私设置问题,与具体的 Session 存储技术关系不大。而且,对于大多数禁用 Cookies 的用户来说,他们往往清楚这会导致网站的身份认证功能无法正常使用,因此在实际使用中,他们通常会针对自己关心的特定站点单独解锁 Cookies 功能,以确保能够正常访问和使用这些网站的服务。所以,从开发者的角度来看,这并不是一个需要通过采用特殊技术(如 JWT)来解决的关键问题,更好的做法是向用户清晰、详细地解释为什么网站需要使用 Cookies 才能正常运行,以提高用户对 Cookies 使用的理解和接受程度。四、JWT 的劣势揭示通过对上述常见误解的分析和澄清,我们已经了解到 JWT 在作为 Session 机制时,所谓的优势大多并不成立。然而,问题远不止于此,使用 JWT 作为 Session 机制还存在许多严重的缺点,其中一些甚至会引发重大的安全问题,以下将对这些劣势进行详细阐述:(一)更费空间JWT Tokens 的实际数据量并不小,尤其是在使用无状态 JWT 时,由于所有的 Session 数据都需要直接编码到 Tokens 内部,这很容易导致 Token 的长度迅速增加,从而可能超过 Cookies 或 URL 的长度限制。在这种情况下,可能会有人考虑将 JWT Tokens 存储到 Local Storage 中,但这又会引发其他一系列问题,后续将会详细说明。(二)更不安全当将 JWT Tokens 存储到 Cookies 中时,其安全性与其他传统的 Session 机制相比并没有本质上的提升或差异。然而,如果将 JWT 存储在其他位置,如 Local Storage,就会引入一个新的安全漏洞。具体来说,Local Storage 是 HTML5 提供的一项功能,它允许浏览器进行 Key/Value 形式的存储操作,对于可能较大的 JWT Tokens 来说,由于 Cookies 的存储容量通常在 4k 左右,对于较大的 Tokens 可能无法满足存储需求,而 Local Storage 似乎成为了一个可行的解决方案。但需要注意的是,Local Storage 并没有像 Cookies 那样提供完善的安全保护措施。与 Cookies 不同,Local Storage 中的数据不会在每次 HTTP 请求时自动发送到服务器端,获取这些数据的唯一途径是通过 JavaScript 代码。这就意味着,如果攻击者能够成功注入恶意的 JavaScript 脚本,并且该脚本能够通过网站的内容安全策略检查,那么攻击者就可以轻易地访问和泄露 Local Storage 中存储的 JWT Tokens,从而导致严重的安全事故。此外,JavaScript 在处理数据时并不关心或追踪数据是否通过安全的 HTTPS 连接进行传输,对于 JavaScript 而言,这些数据仅仅是普通的数据,浏览器会按照常规的数据处理方式来对待它们。这与我们长期以来通过各种技术手段确保 Cookies 安全的努力背道而驰,因为我们花费了大量的精力来防止 Cookies 被恶意接触和窃取,而现在使用 Local Storage 存储 JWT Tokens 却忽视了这些宝贵的经验教训,这无疑是一种安全意识上的倒退。综上所述,无论是否采用 JWT,“使用 Cookies 仍然是保障数据安全传输和存储的重要手段”,不应该轻易地放弃 Cookies 而选择其他可能存在安全隐患的存储方式。(三)无法单独销毁使用 JWT 作为 Session 机制还存在一个严重的安全问题,那就是无法单独销毁 Session。与传统的 Sessions 不同,传统 Sessions 可以在服务端随时根据需要进行单独的销毁操作,例如当检测到用户的异常行为或安全威胁时,能够及时销毁相应的 Session,以防止潜在的风险。然而,无状态 JWT Tokens 由于其设计特点,在 Token 过期之前无法被单独地销毁。例如,在检测到攻击行为时,由于无法立即销毁攻击者的 Session,这就使得攻击者仍然可以利用有效的 Token 继续访问系统,从而给系统带来持续的安全威胁。同样,在用户修改密码等关键操作后,也无法及时销毁旧的 Sessions,这可能会导致旧的 Token 仍然被泄露或滥用,进一步增加了系统的安全风险。针对这一问题,目前几乎没有有效的解决方案,除非重新构建一套复杂的、有状态(Stateful)的基础设施,专门用于明确地检测和拒绝特定的 Session,但这样做不仅成本高昂,而且完全违背了使用无状态 JWT Tokens 的初衷和优势,使得系统的架构变得更加复杂和难以维护。(四)数据延迟与上述安全问题相关联的是,使用无状态 JWT Tokens 还可能存在数据延迟的问题,这也构成了一个潜在的安全隐患。由于无状态 Tokens 中存储的数据是在某个特定时间点生成的,随着时间的推移,这些数据可能会逐渐“过时”,无法反映数据库中最新的信息。例如,用户在个人信息页面修改了某些数据,如个人资料图片的 URL,但由于 JWT Token 中仍然保留着旧的 URL,这就可能导致在某些情况下,系统仍然使用旧的、不准确的信息进行操作,从而出现数据不一致的情况。更为严重的是,如果涉及到权限相关的信息,例如用户原本具有管理员权限,但后来该权限被撤销,然而由于无法销毁之前颁发的具有管理员权限的 Token,系统可能仍然会认为该用户具有管理员权限,这就可能导致严重的安全漏洞,除非关闭整个系统,否则很难完全避免这种情况的发生。(五)实现库缺乏生产环境验证或压根不存在在考虑使用 JWT 作为 Session 机制时,还需要关注其实现库的成熟度和稳定性问题。许多人可能认为上述提到的问题主要是针对无状态 JWT,但实际上,即使使用有状态 Tokens,虽然在功能上与传统的 Session cookies 大致相当,但目前市场上缺乏经过大量生产环境验证的成熟实现库。相比之下,现有的许多传统 Session 实现(例如适用于 Express 的 express-sessionhttps://github.com/expressjs/sessio)已经在生产环境中使用了多年,其安全性和稳定性经过了大量实际项目的检验和改良,开发者可以放心地使用这些成熟的实现,并且能够受益于社区的持续支持和更新。而如果选择使用 JWT 作为 Session cookies 的临时替代品,不仅无法享受到这些成熟实现带来的好处,而且还需要自行不断改进和完善其实现细节,这在过程中很容易引入新的漏洞和问题。即使选择使用第三方的 JWT 实现库,由于这些库尚未在真实世界中得到广泛的应用和验证,仍然存在一定的风险和不确定性。例如,Laravel Passport 虽然采用了类似“有状态 JWT”的方式来存储 OAuth Access Token,但幸运的是,Passport 已经在一些实际应用中得到了验证,并且不完全依赖于 JWT 的原生特性,而是结合了其他成熟的技术和机制来保障安全性和稳定性。五、结论综上所述,无状态 JWT Tokens 在作为 Session 机制时存在诸多无法忽视的问题,如无法单独销毁或更新、可能导致长度限制问题以及潜在的安全隐患等。而有状态 JWT Tokens 虽然在功能上与传统的 Session cookies 相似,但由于缺乏广泛的生产环境验证、经过大量 Review 的成熟实现以及良好的客户端支持,其在实际应用中的可靠性和稳定性仍然存在一定的疑虑。因此,除非是在像 BAT 这样规模庞大、具有特殊技术需求和挑战的公司,对于大多数普通的项目和开发者来说,并没有充分的理由选择使用 JWT 作为 Session 机制。在实际开发中,建议仍然优先选择传统的、经过长期实践验证的 Session 机制,这样能够更好地保障系统的安全性、稳定性和可维护另外,近期才哥整理出了一个可用于快速刷面试题的小程序,其中收录了常见面试题及其答案,涵盖了基础、并发、JVM、MySQL、Redis、Spring、SpringMVC、SpringBoot、SpringCloud、消息队列等多个类型,感兴趣的可以点击下方试试
2024年12月24日
10 阅读
0 评论
0 点赞
2024-12-11
Java基础面试题—— 包装类型和基本类型的区别是什么?
面试回答重点基本类型 :Java 中有 8 种基本数据类型(int、long、float、double、char、byte、boolean、short),它们是直接存储数值的变量,位于栈上(局部变量),性能较高,且不支持 null。包装类型 :每个基本类型都有一个对应的包装类型(Integer、Long、Float、Double、Character、Byte、Boolean、Short)。包装类型是类,存储在堆中,可以用于面向对象编程,并且支持 null。区别总结:(1)性能区别:基本类型:占用内存小,效率高,适合频繁使用的简单操作。包装类型:因为是对象,涉及内存分配和垃圾回收,性能相对较低。(2)比较方式不同:基本类型 :比较用 ==,直接比较数值。包装类型 :比较时,== 比较的是对象的内存地址,而 equals() 比较的是对象的值。(3)默认值不同:基本类型:默认值是 0,false 等。包装类型:默认为 null。(4)初始化的方式不同:基本类型:直接赋值。包装类型:需要采用 new 的方式创建。(5)存储方式不同:基本类型:如果是局部变量则保存在栈上面,如果是成员变量则在堆中。包装类型:保存在堆上(成员变量,在不考虑 JIT 优化的栈上分配时,都是随着对象一起保存在堆上的)。扩展知识自动装箱与拆箱Java 是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将int 、double等类型放进去的。因为集合的容器要求元素是 Object类型。为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型"包装起来",使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。装箱:基本类型自动转换为包装类型对象。拆箱:包装类型对象自动转换为基本类型的值。缓存机制包装类型中的 Byte、Short、Integer 和 Long 对某些范围内的值(例如 Integer 缓存 -128 到 127)会使用对象缓存来提升性能。因此,同一数值的包装类型对象可能是同一个实例。例如: Integer a = 100; Integer b = 100; System.out.println(a == b); // true Integer c = 200; Integer d = 200; System.out.println(c == d); // false 基础类型与包装类长度和范围分类基本数据类型包装类长度表示范围布尔型booleanBoolean// byteByte1 字节-128 ~ 127 shortShort2 字节-32768 ~ 32767整型intInteger4 字节-2,147,483,648 ~ 2,147,483,647 longLong8 字节-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807字符型charCharacter2 字节Unicode 字符集中的任何字符浮点型floatFloat4 字节约 -3.4E38 ~ 3.4E38 doubleDouble8 字节约 -1.7E308 ~ 1.7E308另外,近期才哥整理出了一个可用于快速刷面试题的小程序,其中收录了常见面试题及其答案,涵盖了基础、并发、JVM、MySQL、Redis、Spring、SpringMVC、SpringBoot、SpringCloud、消息队列等多个类型。可以看下方小程序试试
2024年12月11日
17 阅读
0 评论
0 点赞
2024-12-10
Java基础面试题 ——参数传递是按值还是按引用?
文章着重介绍 Java 里参数传递按值传递的情况,针对基本类型和引用类型详细说明了其传递的实质以及操作对原始变量的影响,通过具体的示例代码加以分析说明不同操作下的结果差异
2024年12月10日
7 阅读
0 评论
0 点赞
1
2
...
5