goroutine内存泄漏的情况?如何避免?
goroutine内存泄漏基本上是因为异常导致阻塞, 可以导致阻塞的情况
1 死锁, goroutine 等待的锁发生了死锁情况
2 chan没有正常被关闭,导致读取读chan的goroutine阻塞
如何避免
1 避免死锁
2 正常关闭
3 使用context管理goroutine, 超时结束goroutine
在Go语言中,Goroutine内存泄漏通常是由于Goroutine未能正确退出,导致其引用的对象无法被垃圾回收(GC)。以下是常见的内存泄漏场景及避免方法:
常见Goroutine内存泄漏场景
-
无限循环未设退出条件
go func() {for { // 无退出条件// 持续执行任务...} }()
-
阻塞于未处理的Channel操作
ch := make(chan int) go func() {<-ch // 永久阻塞,无数据发送 }()
-
生产者提前退出,消费者未关闭
func consumer(ch <-chan int) {for v := range ch { // 若生产者未关闭ch,消费者永久阻塞// 处理v} }
-
未释放资源(如time.Ticker)
go func() {ticker := time.NewTicker(time.Second)for {<-ticker.C // 未调用ticker.Stop()} }()
-
WaitGroup使用不当
var wg sync.WaitGroup wg.Add(1) go func() {// 忘记调用wg.Done() }() wg.Wait() // 永久阻塞
避免内存泄漏的方法
1. 使用context.Context
或done
通道通知退出
- 通过
context
的取消机制或自定义done
通道,明确控制Goroutine生命周期。
ctx, cancel := context.WithCancel(context.Background())
defer cancel()go func(ctx context.Context) {for {select {case <-ctx.Done(): // 收到退出信号returndefault:// 执行任务}}
}(ctx)
2. 避免永久阻塞:超时与select
结合
- 为Channel操作添加超时,防止永久阻塞。
select {
case <-ch:// 正常处理
case <-time.After(1 * time.Second):// 超时处理
}
3. 正确关闭Channel
- 确保生产者完成后关闭Channel,或在退出时通过
done
通道通知消费者。
done := make(chan struct{})
ch := make(chan int)go func() {defer close(ch) // 确保关闭Channel// 生产数据
}()go func() {for {select {case v, ok := <-ch:if !ok {return}// 处理vcase <-done: // 外部终止信号return}}
}()
4. 释放资源(如Ticker、锁、文件句柄)
- 使用
defer
确保资源释放。
go func() {ticker := time.NewTicker(time.Second)defer ticker.Stop() // 确保停止Tickerfor {select {case <-ticker.C:// 执行任务case <-done:return}}
}()
5. 正确使用sync.WaitGroup
- 使用
defer wg.Done()
避免遗漏。
var wg sync.WaitGroup
wg.Add(1)
go func() {defer wg.Done() // 确保调用Done()// 执行任务
}()
wg.Wait()
6. 监控Goroutine数量
- 使用
pprof
或runtime.NumGoroutine()
诊断泄漏。
import (_ "net/http/pprof""net/http"
)func main() {go http.ListenAndServe(":6060", nil) // 暴露pprof接口// ...
}
访问http://localhost:6060/debug/pprof/goroutine?debug=2
查看Goroutine堆栈。
总结
- 设计原则:确保每个Goroutine都有明确的退出路径。
- 资源管理:通过
defer
、context
和done
通道管理生命周期。 - 工具辅助:利用
pprof
等工具监控和诊断泄漏。
遵循以上实践,可有效避免Goroutine内存泄漏问题。
讲一下协程和线程的区别?从多个角度说明,讲一下golang协程是如何调度的,和java线程的区别
主要的区别在于协程是用户级, 不需要进入到内核态处理. 所以协程更加轻量级, 不占用内核资源。2k
协程(Goroutine)和线程(Thread)是并发编程中的核心概念,它们在设计目标、资源管理、调度方式等方面有显著差异。以下是两者的详细对比,并重点分析 Go 协程的调度机制及其与 Java 线程的区别。
一、协程与线程的核心区别
角度 | 协程(Goroutine) | 线程(Thread) |
---|---|---|
调度主体 | 由用户态调度(Go 运行时调度器) | 由操作系统内核调度 |
内存占用 | 初始栈约 2KB,可动态扩展(最大默认 1GB) | 固定栈(如 Linux 默认 8MB,Java 线程约 1MB) |
切换成本 | 极低(无需内核切换,仅用户态操作) | 高(需陷入内核态,上下文切换开销大) |
并发数量 | 轻松支持百万级并发(资源占用小) | 通常最多数千(受内核资源限制) |
同步机制 | 基于 Channel 通信(CSP 模型),避免共享内存 | 依赖锁、信号量等(共享内存,需显式同步) |
错误处理 | 通过 panic /recover 或返回值传递错误 | 通过异常捕获(如 try-catch ) |
创建与销毁 | 极快(由 Go 运行时管理,无系统调用) | 较慢(需系统调用,内核分配资源) |
二、Go 协程的调度机制(GMP 模型)
Go 的协程调度器采用 GMP 模型,由以下三部分组成:
- G(Goroutine):协程对象,存储执行栈和状态。
- M(Machine):操作系统线程(实际执行单元),由内核调度。
- P(Processor):逻辑处理器,管理本地协程队列(每个 P 绑定一个 M)。
调度流程
-
协程创建:
当启动一个 Goroutine 时,它会被放入当前 P 的本地队列(或全局队列,若本地队列满)。 -
线程绑定(M 与 P 绑定):
每个 P 会绑定一个 M(操作系统线程),M 从 P 的本地队列中获取 G 并执行。 -
协作式与抢占式调度:
- 协作式:协程主动让出 CPU(如
runtime.Gosched()
或系统调用)。 - 抢占式:Go 1.14+ 引入基于信号的抢占,防止协程长时间占用 CPU。
- 协作式:协程主动让出 CPU(如
-
Work Stealing:
若某个 P 的本地队列为空,会尝试从其他 P 的队列或全局队列“窃取” G,提高 CPU 利用率。 -
系统调用优化:
- 当 G 执行系统调用时,M 会解绑 P,P 转而绑定其他空闲 M,避免阻塞其他 G。
- 系统调用完成后,M 尝试获取 P 继续执行,若无可用 P,则 G 进入全局队列。
三、Go 协程与 Java 线程的对比
1. 调度方式
- Go 协程:
- 由 Go 运行时调度,用户态协作式+抢占式混合调度。
- 无需内核介入,切换成本极低(如保存/恢复少量寄存器)。
- Java 线程:
- 依赖操作系统内核调度(完全抢占式)。
- 线程切换需陷入内核态,上下文切换开销大(如保存线程状态、切换页表等)。
2. 资源占用
- Go 协程:
- 初始栈 2KB,按需动态扩展(避免内存浪费)。
- 协程复用线程(M),1 个线程可运行数千个协程。
- Java 线程:
- 固定栈大小(默认约 1MB),大量线程易导致内存耗尽。
- 线程与内核线程 1:1 绑定,创建过多会触发 OOM。
3. 并发能力
- Go 协程:
- 轻松支持百万级并发(如 Web 服务器处理海量连接)。
- 典型场景:IO 密集型任务(网络、磁盘操作)。
- Java 线程:
- 线程数量受限于内核(通常数千级别)。
- 需结合线程池避免资源耗尽,但高并发时仍有瓶颈。
4. 同步与通信
- Go 协程:
- 通过 Channel 通信,遵循 CSP 模型(“不要通过共享内存通信,而通过通信共享内存”)。
- 天然避免竞态条件,减少锁的使用。
- Java 线程:
- 依赖
synchronized
、Lock
、volatile
等同步机制。 - 共享内存模型易引发死锁、数据竞争等问题。
- 依赖
5. 错误处理
- Go 协程:
- 错误通过返回值或
panic
/recover
传递。 - 若协程发生未捕获的
panic
,整个程序会终止。
- 错误通过返回值或
- Java 线程:
- 异常需在线程内捕获,否则导致线程终止但进程不退出。
- 可通过
Future
或全局异常处理器(UncaughtExceptionHandler
)处理。
四、总结
特性 | Go 协程 | Java 线程 |
---|---|---|
调度开销 | 极低(用户态调度) | 高(内核态调度) |
适用场景 | 高并发、IO 密集型任务 | CPU 密集型任务、少量并发 |
资源占用 | 极小(动态栈,复用线程) | 大(固定栈,线程与内核线程 1:1 绑定) |
并发模型 | CSP 模型(Channel 通信) | 共享内存模型(锁同步) |
调试复杂度 | 较低(天然避免部分竞态条件) | 较高(需处理死锁、数据竞争) |
五、为什么 Go 协程更适合高并发?
- 轻量级:协程创建和切换成本极低,允许海量并发。
- 高效调度:GMP 模型减少线程阻塞,最大化 CPU 利用率。
- 简化同步:Channel 取代锁,降低并发编程复杂度。
- 资源友好:动态栈和用户态调度避免内存和内核资源浪费。
七、结论
- 协程是用户态的轻量级抽象,适合高并发、IO 密集型场景。
- 线程是操作系统资源,适合 CPU 密集型任务,但需谨慎管理。
- Go 的 GMP 调度器通过解耦协程与线程,结合协作式与抢占式调度,实现高效并发。
- Java 线程受限于内核调度和资源开销,需依赖线程池等技术优化,但并发能力仍弱于协程。
slice和array的区别,讲一下底层的结构
array本质是一个固定数组, 内存层面就是一块固定的内存区域,不会改变, 传递的时候是拷贝一份完整数据.
slice本质上是一个动态数组的封装,底层指向不是一个固定内存,可以重新指向新的内存,包含一个指向 Array 的指针、长度和容量。
channel的用途和使用上要注意的点,底层的结构是怎样的
channel是golang中协程之间的数据交互的重要工具,相当于与进程内的一个消息队列.
注意点: 最重要的是chan的close处理, 不然很容易出现异常, 1写数据goroutine中调用close, 2不要多次调用close, 3使用信号通知chan close了
底层结构: 环形队列(缓存数据, 无缓存的时候用不上), 读goroutine 队列(链表), 写goroutine 队列(链表), 锁
在 Go 语言中,Channel(通道) 是 Goroutine 之间通信和同步的核心机制,基于 CSP 模型(Communicating Sequential Processes)。以下是 Channel 的用途、使用注意事项及其底层结构的详细分析。
一、Channel 的用途
-
Goroutine 间通信
- 传递数据:通过
ch <- data
(发送)和data := <-ch
(接收)实现数据交换。 - 替代共享内存:遵循 Go 的哲学——“不要通过共享内存通信,而要通过通信共享内存”。
- 传递数据:通过
-
同步控制
- 阻塞等待:无缓冲 Channel 的发送和接收会阻塞,直到对方准备好(天然的同步机制)。
- 协调多个 Goroutine:例如通过
close(ch)
通知接收方数据结束。
-
工作池和任务分发
- 结合缓冲 Channel,可构建生产者-消费者模型,控制并发数量。
-
超时控制
- 通过
select
结合time.After
实现超时机制:select { case <-ch:// 正常处理 case <-time.After(1 * time.Second):// 超时处理 }
- 通过
二、使用 Channel 的注意事项
1. 初始化与零值
- Channel 的零值为
nil
,向nil
Channel 发送或接收会永久阻塞:var ch chan int // ch 是 nil ch <- 1 // 永久阻塞(无 panic) <-ch // 永久阻塞(无 panic)
- 正确做法:使用
make
初始化 Channel:ch := make(chan int) // 无缓冲 Channel bufferedCh := make(chan int, 10) // 缓冲容量为 10
2. 避免死锁
- 无缓冲 Channel 的同步性:发送和接收必须成对出现,否则死锁。
func main() {ch := make(chan int)ch <- 1 // 发送后阻塞,无接收者导致死锁fmt.Println(<-ch) // 永远不会执行 }
- 解决方式:确保发送和接收在不同的 Goroutine 中:
go func() { ch <- 1 }() fmt.Println(<-ch)
3. 关闭 Channel 的规则
- 关闭后不可发送:向已关闭的 Channel 发送数据会引发
panic
。 - 接收已关闭的 Channel:可继续接收剩余数据,返回零值时需通过
ok
判断是否关闭:v, ok := <-ch if !ok {// Channel 已关闭 }
- 关闭 Channel 的原则:
- 只由发送方关闭,避免多个 Goroutine 同时关闭。
- 无需关闭 Channel(除非必须通知接收方“数据结束”)。
4. 避免泄漏
- 未关闭的 Channel:若接收方持续等待已不再发送数据的 Channel,会导致 Goroutine 泄漏。
- 解决方式:使用
context
或done
通道通知退出:done := make(chan struct{}) go func() {select {case <-done:returncase v := <-ch:// 处理数据} }() // 退出时关闭 done close(done)
5. 性能优化
- 缓冲 Channel:减少阻塞频率,但需权衡内存占用。
- 避免过度缓冲:缓冲大小应根据实际场景调整,避免资源浪费。
三、Channel 的底层结构
Go 的 Channel 实现基于 runtime.hchan
结构体(源码见 runtime/chan.go
),核心字段如下:
1. hchan
结构体
type hchan struct {qcount uint // 当前队列中元素数量dataqsiz uint // 缓冲区的固定大小(容量)buf unsafe.Pointer // 指向环形缓冲区的指针elemsize uint16 // 元素大小closed uint32 // 是否已关闭(0-未关闭,1-已关闭)sendx uint // 发送索引(指向缓冲区下一个写入位置)recvx uint // 接收索引(指向缓冲区下一个读取位置)recvq waitq // 等待接收的 Goroutine 队列(sudog 链表)sendq waitq // 等待发送的 Goroutine 队列(sudog 链表)lock mutex // 互斥锁(保护 Channel 的并发操作)
}
2. 环形缓冲区(Buffered Channel)
- 缓冲机制:当
dataqsiz > 0
时,Channel 使用环形队列存储数据。 - 写入流程:
- 若缓冲区未满,数据写入
buf[sendx]
,sendx = (sendx + 1) % dataqsiz
。 - 若缓冲区已满,发送方 Goroutine 加入
sendq
队列并阻塞。
- 若缓冲区未满,数据写入
- 读取流程:
- 若缓冲区非空,读取
buf[recvx]
,recvx = (recvx + 1) % dataqsiz
。 - 若缓冲区为空,接收方 Goroutine 加入
recvq
队列并阻塞。
- 若缓冲区非空,读取
3. 直接发送与接收(Unbuffered Channel)
- 无缓冲 Channel:
dataqsiz = 0
,发送和接收必须同时就绪。 - 同步过程:
- 若接收方已阻塞(在
recvq
中),发送方直接将数据拷贝到接收方的栈。 - 若发送方已阻塞(在
sendq
中),接收方直接从发送方拷贝数据。
- 若接收方已阻塞(在
4. 等待队列(waitq
)
- 阻塞的 Goroutine:当发送或接收无法立即完成时,Goroutine 被封装为
sudog
结构体,加入sendq
或recvq
队列。 - 唤醒机制:当条件满足时(如缓冲区有空间或数据),调度器唤醒队列中的 Goroutine。
四、总结
特性 | 说明 |
---|---|
核心用途 | Goroutine 间通信与同步,替代共享内存 |
底层结构 | 基于环形缓冲区和等待队列(hchan ),通过锁保证并发安全 |
注意事项 | 避免死锁、正确关闭、处理零值、防止泄漏 |
性能优化 | 合理使用缓冲、减少锁竞争、避免过度阻塞 |
Channel 是 Go 并发编程的核心工具,深入理解其机制和陷阱,能够编写出高效且健壮的并发代码。
orm框架的优缺点
orm封装了对数据库的操作,并且自动转换内存数据结构和数据库字段, 优点是方便,开发效率高, 缺点是有些场景性能低, 复杂对像转换处理起来不方便.
操作系统内核态和用户态的区别,何时进入内核态or用户态
进入内核态: 中断(系统调用, 时间片到期等)
进入用户态: 内核态处理完成,调度用户线程处理程序
tcp和udp的区别,他们的报头结构,tcp的三次握手和四次握手的中间状态有哪些
https中的TLS/SSL层是用来干什么的,讲一下根证书和证书链和https握手的流程
TLS/SSL 层的作用
TLS(Transport Layer Security) 及其前身 SSL(Secure Sockets Layer) 是位于传输层(如 TCP)和应用层(如 HTTP)之间的安全协议,主要用于:
- 加密通信:防止数据在传输过程中被窃听。
- 身份认证:验证服务器(或客户端)的身份,防止中间人攻击。
- 数据完整性:确保数据未被篡改(通过哈希算法和消息认证码)。
HTTPS = HTTP + TLS/SSL,所有数据在传输前会被 TLS 层加密。
根证书与证书链
1. 根证书(Root Certificate)
- 颁发者:由受信任的根证书颁发机构(Root CA,如 DigiCert、Let’s Encrypt)签发。
- 存储位置:预装在操作系统或浏览器中(如 Windows 的受信任根证书存储)。
- 作用:作为信任链的起点,用于验证其他证书的合法性。
2. 证书链(Certificate Chain)
- 结构:
根证书 → 中间证书(Intermediate CA) → 服务器证书
。 - 示例:
- 根证书:
DigiCert Global Root CA
- 中间证书:
DigiCert TLS RSA SHA256 2020 CA1
- 服务器证书:
www.example.com
- 根证书:
- 作用:
- 分层信任:根证书不直接签发服务器证书,通过中间证书隔离风险(即使中间证书私钥泄露,只需吊销中间证书,无需替换根证书)。
- 链式验证:客户端从服务器证书逐级向上验证,直到找到信任的根证书。
3. 证书验证过程
- 客户端收到服务器的证书(如
www.example.com
)。 - 检查证书是否过期、域名是否匹配。
- 通过证书链向上追溯,验证每一级证书的签名是否合法,直到找到受信任的根证书。
- 若验证失败(如根证书不在信任库中),浏览器会提示“证书不受信任”。
HTTPS 握手流程(以 TLS 1.2 为例)
以下是客户端(如浏览器)与服务器建立 HTTPS 连接的详细步骤:
1. Client Hello
- 客户端发送:
- 支持的 TLS 版本(如 TLS 1.2)。
- 客户端随机数(Client Random,用于生成会话密钥)。
- 支持的密码套件(如
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
)。 - SNI(Server Name Indication):指定访问的域名(用于多域名服务器)。
2. Server Hello
- 服务器回应:
- 选择的 TLS 版本和密码套件。
- 服务器随机数(Server Random,用于生成会话密钥)。
- 服务器证书链(包含服务器证书和中间证书)。
3. 证书验证
- 客户端验证服务器证书链的合法性(如前文所述)。
4. 密钥交换(Key Exchange)
- RSA 密钥交换(传统方式):
- 客户端生成预主密钥(Pre-Master Secret),用服务器证书的公钥加密后发送。
- 服务器用私钥解密,得到预主密钥。
- ECDHE 密钥交换(现代方式,支持前向保密):
- 服务器生成临时椭圆曲线参数和公钥(Server Params),签名后发送。
- 客户端生成临时公钥(Client Params),发送给服务器。
- 双方通过椭圆曲线算法生成预主密钥。
5. 生成会话密钥
- 客户端和服务器使用以下三个随机数生成主密钥(Master Secret):
Client Random
Server Random
Pre-Master Secret
- 主密钥进一步派生出会话密钥(对称加密密钥,如 AES 密钥),用于后续通信加密。
6. Finished
- 双方发送加密的
Finished
消息,验证握手过程未被篡改。 - 握手完成,后续应用数据通过对称加密传输。
TLS 1.3 的优化
TLS 1.3 简化握手流程,提升安全性和速度:
- 合并步骤:将密钥交换与证书验证合并为单次往返(1-RTT)。
- 移除不安全算法:禁用 RSA 密钥交换(仅支持前向保密的 ECDHE)。
- 0-RTT 模式:对重复连接可跳过握手(需谨慎使用,存在重放攻击风险)。
总结
关键点 | 说明 |
---|---|
TLS/SSL 作用 | 加密数据、验证身份、保障完整性 |
根证书 | 信任链的起点,预装在系统中 |
证书链 | 分层签发,隔离风险,支持灵活吊销 |
握手流程 | 协商参数 → 交换密钥 → 生成会话密钥 → 加密通信 |
前向保密 | ECDHE 确保即使服务器私钥泄露,历史会话仍安全(TLS 1.3 强制要求) |
示例场景:
当访问 https://www.example.com
时:
- 浏览器通过 TLS 握手验证服务器的证书链,确认其属于
example.com
。 - 双方协商出会话密钥,后续所有 HTTP 数据(如登录密码、支付信息)均通过 AES 加密传输,确保安全。
常见的攻击手法有哪些,讲一下中间人攻击和跨域攻击的原理,跨域攻击主要是利用了浏览器的什么机制
一、常见攻击手法分类
以下是网络安全中常见的攻击类型:
攻击类型 | 描述 |
---|---|
中间人攻击(MITM) | 攻击者拦截并篡改通信双方的数据流。 |
跨站脚本(XSS) | 向网页注入恶意脚本,窃取用户数据或会话 Cookie。 |
SQL 注入 | 通过输入恶意 SQL 语句,操纵数据库查询。 |
跨站请求伪造(CSRF) | 诱使用户在已认证的站点上执行非预期的操作(如转账)。 |
拒绝服务(DoS/DDoS) | 通过大量请求耗尽目标资源,使其无法正常服务。 |
钓鱼攻击 | 伪造可信页面(如银行网站),诱导用户提交敏感信息。 |
目录遍历/文件包含 | 利用路径漏洞访问或执行服务器上的敏感文件。 |
社会工程学 | 通过心理欺骗获取敏感信息(如假冒客服骗取密码)。 |
二、中间人攻击(MITM)原理
1. 攻击场景
- 未加密的通信:如 HTTP 明文传输、公共 Wi-Fi 环境。
- 证书欺骗:伪造 SSL/TLS 证书(如自签名证书),诱导用户信任。
2. 攻击步骤
-
窃听(Eavesdrop):
攻击者通过 ARP 欺骗、DNS 劫持或 Wi-Fi 嗅探,成为通信链路的中间节点。- ARP 欺骗:伪造 IP 和 MAC 地址映射,劫持局域网流量。
- DNS 劫持:篡改 DNS 响应,将域名解析到攻击者控制的 IP。
-
篡改或伪造数据:
- 修改传输内容(如插入广告、重定向到钓鱼网站)。
- 窃取敏感信息(如登录凭证、Cookie)。
-
伪装身份:
- 在 HTTPS 场景下,攻击者可能伪造证书(需用户手动信任),解密 HTTPS 流量。
3. 防御措施
- 使用 HTTPS:加密通信内容,防止明文数据泄露。
- HSTS(HTTP Strict Transport Security):强制浏览器使用 HTTPS,防止降级攻击。
- 证书固定(Certificate Pinning):客户端固定合法证书,拒绝非预期证书。
- 网络层防护:使用 VPN、避免使用公共 Wi-Fi 进行敏感操作。
三、跨域攻击(Cross-Origin Attacks)原理
跨域攻击主要利用浏览器**同源策略(Same-Origin Policy)**的漏洞,绕过安全限制,窃取数据或执行恶意操作。
1. 同源策略(SOP)
- 定义:浏览器限制脚本只能访问与当前页面同源(协议、域名、端口相同)的资源。
- 目的:防止恶意网站读取其他站点的敏感数据(如 Cookie、DOM 内容)。
2. 跨域攻击类型
(1) 跨站脚本(XSS)
- 原理:攻击者注入恶意脚本到合法页面,脚本在用户浏览器执行。
<!-- 示例:通过未过滤的用户输入注入脚本 --> <input value="<script>stealCookie()</script>">
- 利用点:
- 窃取用户的 Cookie 或 Session ID。
- 伪造用户操作(如自动转账)。
(2) 跨站请求伪造(CSRF)
- 原理:诱导用户访问恶意页面,该页面自动向目标站点发送已认证的请求。
<!-- 恶意页面中隐藏的转账请求 --> <img src="https://bank.com/transfer?to=attacker&amount=1000000">
- 利用点:
- 用户已登录目标站点(如银行),浏览器自动携带 Cookie。
- 请求被执行(如转账、修改密码)。
(3) CORS 配置不当
- 原理:服务器错误设置
Access-Control-Allow-Origin: *
,允许任意域访问资源。HTTP/1.1 200 OK Access-Control-Allow-Origin: *
- 利用点:
- 攻击者通过恶意网站发起跨域 AJAX 请求,窃取数据。
(4) JSONP 劫持
- 原理:利用
<script>
标签跨域加载 JSONP 接口,通过回调函数窃取数据。<script src="https://api.com/user?callback=stealData"></script> <script> function stealData(data) {// 发送数据到攻击者服务器 } </script>
3. 防御措施
攻击类型 | 防御方法 |
---|---|
XSS | 输入输出过滤(如转义 < , > )、启用 CSP(Content Security Policy)。 |
CSRF | 使用 CSRF Token、检查 Referer 头、设置 SameSite Cookie 属性。 |
CORS | 严格限制 Access-Control-Allow-Origin (如白名单)、避免使用通配符 * 。 |
JSONP | 弃用 JSONP,改用 CORS;或校验回调函数名、添加随机 Token。 |
四、总结
攻击类型 | 核心原理 | 浏览器机制利用 |
---|---|---|
中间人攻击 | 拦截并篡改通信链路 | 无加密通信或证书信任漏洞 |
跨域攻击 | 绕过同源策略获取跨域数据或权限 | SOP 限制不严、CORS/JSONP 配置错误 |
关键点:
- 中间人攻击依赖网络层的漏洞,防御需加密通信(HTTPS)和身份验证。
- 跨域攻击利用浏览器同源策略的宽松配置,防御需严格限制跨域资源访问。
示例场景:
- MITM:公共 Wi-Fi 中,攻击者截获用户登录 HTTP 站点的密码。
- 跨域攻击:恶意网站利用银行站点的 CORS 配置错误,窃取用户账户数据。
通过理解攻击原理和浏览器机制,开发者可更有效地设计安全防护策略。