前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

一、Http 协议

1、http 连接

HTTPHyperText Transfer Protocol,超文本传输协议)是 Web 应用的基础通信协议,也是移动端常用的网络协议之一。HTTP 是一种应用层协议,建立在可靠的 TCP 协议之上。

1) 短连接、长连接

  • HTTP/1.0 默认使用短连接
    • 每次请求都需要单独建立一次 TCP 连接,服务器处理完请求后立即关闭连接。这种方式开销大、效率低。
  • HTTP/1.1 默认使用长连接(持久连接)
    • 同一个 TCP 连接上可以连续发送多个 HTTP 请求和响应,无需为每个请求重新建立连接,显著减少了连接建立开销,提升性能
    • 通过 Connection: keep-alive 头部实现(HTTP/1.1 中默认启用)。
    • HTTP 协议本身是无状态的,它并不负责维护客户端的“在线状态”。
      • 某些应用场景(如即时通讯、实时推送)中,客户端会定期向服务器发送“心跳请求”以表明自身在线,但这属于业务层的设计,并非 HTTP 协议的固有机制。
      • 真正的连接保活也可依赖 TCP 层的 SO_KEEPALIVE 选项,但其默认超时时间较长(通常数小时),不适合实时性要求高的场景。
场景 需要“心跳” 说明
普通网页浏览(HTTP/1.1 不需要 浏览器自动复用连接,用完可保持空闲一段时间( Keep-Alive: timeout=5
WebSocket /MQTT / 自定义协议 需要 应用层需实现心跳包检测连接有效性
移动 App 后台保活 可能需要 因系统限制(如 Android 省电策略),需主动发包防止连接被回收

2)长连接、长轮询

  • 介绍
    • 长连接(如 WebSocketTCP Keep-Alive
      • 连接建立后长期保持,服务端需为每个连接维护状态(内存、文件描述符等)百万级连接对服务器压力极大
    • 长轮询(Long Polling:长轮询的唯一优势是:无需心跳、无状态、兼容 HTTP 生态(配置中心)
      • 基于 HTTP 长连接Keep-Alive),客户端发起一次请求后,服务端 hold 住连接直到有数据或超时,响应后连接可复用或关闭
      • 虽然每次交互是“请求-响应”,但单次连接寿命较长,服务端仍需为每个 pending 请求维护状态(如 event loop 中的等待队列),高并发下同样有压力,但通常低于真长连接
  • 一句话总结:

    • 想“用 HTTP 伪装成推送”且实时性要求不是极端高,选长轮询

    • 想“真正持久、双向、低延迟”,直接上长连接(如 WebSocket)。

a、原理比较

维度 长轮询 长连接
核心机制 客户端发请求 → 服务端挂起 → 有数据/超时返回 → 客户端立即重发 TCP 连接长期保持,可多次双向通信
实时性 比短轮询好,数据一产生就能立刻返回;但仍有“请求-等待”周期,延迟在毫秒到秒级。 数据随时可推送,延迟最低,能做到“即时到达”。
连接开销 每次“挂起”都会占用服务器线程/进程资源;并发高时容易把连接数耗尽。 连接复用,省去了反复握手、挥手的消耗;但保持空闲连接本身也有内存和句柄开销。
实现复杂度 服务器要能“hold”住请求,需要异步I/O 或线程池,代码比短轮询复杂。 客户端和服务端都要处理心跳、超时、断线重连,协议层或应用层都要额外逻辑。
内存开销 中(每个 pending 请求需状态) 高(每个连接长期占内存)
典型场景 WebIM、在线聊天、实时通知——既要实时又不想立即上 WebSocket 的场景。配置中心 WebSocket、数据库连接池、需要双向持续通信的后台服务。

b、性能状态

场景 长轮询 vs 长连接
理想情况(客户端偶尔拉取) 长轮询压力 « 长连接(大部分时间无连接)
现实情况(客户端持续监听) 长轮询压力 ≈ 长连接(始终有 pending)
极端情况(短 timeout + 高频重试) 长轮询压力 > 长连接(频繁建连 + HTTP 开销

2)Http 状态码

HTTP 状态码是服务器对客户端请求的响应状态,用以快速了解请求处理的情况。这些状态码分为五大类,每类以不同的百位数字开头

  • 1xx(信息性响应):表示请求已被接收,继续处理。
  • 2xx(成功响应):表示请求已成功被服务器接收、理解和处理。
  • 3xx(重定向):为了完成请求,需要进一步操作。通常,这些状态码用于将请求重定向到另一个位置。
  • 4xx(客户端错误):请求包含语法错误或无法完成请求。除非进行修改,否则客户端不应重复提交这个请求。
  • 5xx(服务器错误):服务器在尝试处理请求时发生内部错误。这类错误通常由服务器端的问题引起。

a、1xx 状态码(已收到请求)

  • 解释:信息响应,表示服务器已收到请求,但是需要请求者继续操作。

  • 说明:主要涉及的状态码有101、102和103,主要用于协议切换、指示服务器正在处理请求以及预加载提示等。

状态码 作用
101 这只是个临时的响应状态,它表示到目前为止,客户端请求的内容都没有问题。但是客户端需要继续发送请求,才能完成本次请求过程。
102 该代码是响应客户端的 Upgrade 标头发送的,并且指示服务器也正在切换的协议。比如,我们在使用ws协议时常会见到这个状态码
103 预加载提示。此状态代码主要用于与Link 链接头一起使用,以允许用户代理在服务器仍在准备响应时开始预加载资源。

b、2xx 状态码(响应成功)

  • 解释:响应成功,表示服务器已接收到请求并正确处理

  • 说明:包括200(成功)、201(创建资源)、202(接受但未处理)、204(无内容)、206(部分内容)等,代表各种形式的成功响应

状态码 作用
200 服务器已成功处理了请求。通常,这表示服务器供了请求的网页。
201 请求成功并且服务器创建了新的资源。
202 服务器已接受请求,但尚未处理。
203 服务器已成功处理了请求,但返回的信息可能来自另一来源
204 服务器已成功处理了请求,但没有返回任何实体内容,并且浏览器不需要刷新或者重定向
205 服务器已成功处理了请求,但没有返回任何实体内容,不过浏览器应该立即刷新当前页面
206 服务器成功处理了部分 GET请求

c、3xx 状态码(重定向)

  • 解释:重定向,表示服务器已接收到请求,但是没有直接处理,而是进行了重定向

  • 说明:涉及重定向场景,包括301(永久移动)、302(临时重定向)、304(未修改,使用缓存)等。

状态码 作用
300 针对请求,服务器可执行多种操作。服务器可根据请求者(user_agent)选择一项操作,或提供操作列表供请求者选择。
301 请求的网页已永久移动到新位置。服务器返回此响应对GET或HEAD请求的响应时,会自动将请求者转到新位置
302 请求的资源被临时重定向到了别的URI。但这个重定向只是临时的,下次请求当前资源时仍然应该使用当前的地址
303 服务端已经收到请求,但是不会进行处理。客户端需要向响应中携带的新的URI发送GET请求,服务端才会进行处理
304 表示当前资源已被下载过,并且没有改变,因此客户端应该从缓存中获取该资源
305 当前资源需要使用指定的代理才能访问
306 最新的规范中,306不再被使用
307 请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的

d、4xx 状态码(客户端错误)

  • 解释:客户端错误,表示客户端发出的请求中存在错误,无法完成请求

  • 说明:如400(错误请求)、401(未授权)、403(禁止访问)、404(找不到页面)、413(请求实体太大)等。

状态码 作用
400 请求有误。它包含两种情况:1.语义有误。服务端无法理解当前请求的含义,因此客户端必须对请求进行修改才可以重新发送。2.参数有误。客户端请求中携带的参数不符合服务端的要求,如数量不一致,类型错误,体积过大(如上传文件时,文件大小超出了服务端配置)等。
401 未授权。即当前请求需要进行用户验证
402 该状态码暂未使用,将来可能用于数字支付系统
403 服务器已经接受并理解了请求,但是拒绝执行
404 请求的资源不存在
405 请求方法不允许。比如服务端设置某个资源只能用POST方法进行访问,而客户端发送的是一个GET请求,服务端就会返回状态码405
406 请求的资源的内容特性无法满足请求头中的条件,因此无法生成响应实体。比如客户端设置返回的数据类型必须是JSON,而服务端没有配置转换JSON所依赖的包,或者所请求的资源本身就无法转化为JSON,这时服务端就无法生成符合客户端要求的响应实体,因此就会返回状态码406。
407 需要在代理服务器上进行身份验证。该状态码与401类似,但是它要求客户端必须在代理服务器上进行身份验证
408 请求超时。
409 服务器在完成请求时发生冲突。服务器必须在响应中包含有关冲突的信息
410 如果请求的资源已永久删除,服务器就会返回此响应。
411 服务器不接受不含有效内容长度标头字段的请求。
412 服务器未满足请求者在请求中设置的其中一个前提条件。
413 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力
414 请求的URI通常为网址过长,服务器无法处理
415 请求的格式不受请求页面的支持
416 如果页面无法提供请求的范围,则服务器会返回此状态代码
417 服务器未满足”期望”请求标头字段的要求

e、5xx 状态码(服务端错误)

  • 解释:服务端错误,表示服务器在处理请求的过程中出现错误

  • 说明:服务器问题,不能完成请求,如500(内部服务器错误)、502(无效网关)、503(服务不可用)、504(网关超时)等。

状态码 作用
500 服务器遇到错误,无法完成请求
501 服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码
502 服务器作为网关或代理,从上游服务器收到无效响应
503 服务器目前无法使用,由于超载或停机维护。通常,这只是暂时状态
504 服务器作为网关或代理,但是没有及时从上游服务器收到请求
505 服务器不支持请求中所用的HTTP协议版本

2、浏览器中输入网址发生了什么

若使用 HTTPS,还会在 TCP 连接建立后进行 TLSTransport Layer Security,传输层安全协议)握手,完成加密通道的建立。

1)基本流程

  1. URL 解析: 浏览器解析 URL,提取协议(如 http/https)、域名(如 www.example.com)、端口、路径等信息。
  2. DNS 查询
    • 首先检查本地 Hosts 文件
    • 然后查询操作系统或浏览器的 DNS 缓存
    • 若未命中,则向配置的 DNS 服务器发起递归查询,最终获得目标域名对应的 IP 地址
  3. 建立 TCP 连接: 浏览器通过获得的 IP 地址,与 Web 服务器进行 TCP 三次握手,建立可靠连接。
  4. 发送 HTTP 请求:在已建立的 TCP 连接上,浏览器发送 HTTP 请求报文(如 GET /index.html HTTP/1.1)。
  5. 接收 HTTP 响应:服务器处理请求后返回 HTTP 响应(包含状态码、响应头和 HTML 内容等)。
  6. 渲染页面: 浏览器解析 HTML、加载 CSS/JS、执行脚本,并最终将页面渲染到屏幕上。

2)VIP 参与

用户浏览器
     ↓ 输入域名(如 www.example.com)
DNS 服务器
     ↓ 返回 VIP(如 203.0.113.10)
VIP(虚拟IP)
     ↓ 由负载均衡器或高可用集群持有
真实服务器(Server A / Server B / ...)
     ↓ 处理实际请求

image-20251112181827201

优势 说明
高可用 一台服务器宕机,流量自动切到其他机器
弹性扩展 加机器只需在负载均衡加节点,无需改 DNS
维护透明 升级/重启服务器不影响用户访问
安全隔离 真实服务器 IP 不暴露,减少攻击面

3、TCP 三次握手

TCP 三次握手(Three-way Handshake) 是建立可靠 TCP 连接的核心机制,确保通信双方都能正常收发数据。

1)关键字段说明

  • SYNSynchronize Sequence Numbers):同步序列号,用于发起连接。
  • ACKAcknowledgement):确认号,表示期望收到的下一个字节序号。
    • ACK(大写)控制标志位,是一个 1-bit 的标志。当 ACK = 1 时,表示本报文段中的 确认号(ack)字段有效。
    • ack(小写)确认号字段,是一个 32 位的数值,表示“期望收到的下一个字节的序号”,即对对方发送数据的确认
  • seq:当前报文的序列号。
  • ack:确认号(即对方 seq + 1)。

2)握手过程

image-20251112103629284

初始状态:客户端 & 服务器:CLOSED

服务器准备就绪:服务器调用 listen() 后进入:LISTEN

第一次握手(客户端 → 服务器)

  • 客户端发送:SYN=1, seq=x
  • 客户端状态变为:SYN_SENT
    • SYN:置为 1, 表示需要建立 TCP连接
      seq:序列号,是由发送端随机生成的

第二次握手(服务器 → 客户端)

  • 服务器回复:SYN=1, ACK=1, seq=y, ack=x+1
  • 服务器状态变为:SYN_RECEIVED(常简写为 SYN_RCVD

第三次握手(客户端 → 服务器)

  • 客户端发送:ACK=1, seq=x+1, ack=y+1
  • 客户端状态变为:ESTABLISHED
  • 服务器收到后也变为:ESTABLISHED

3)状态说明

状态 含义
CLOSED 初始状态:客户端 & 服务器
LISTEN 服务器等待来自任意远程主机的连接请求。
SYN_SENT 客户端已发送 SYN,等待服务器的确认。
SYN_RECEIVED 服务器已收到 SYN 并发送了 SYN+ACK,等待客户端最终确认。
ESTABLISHED 连接已成功建立,可进行数据传输。

4)为什么是“三次握手”?二次握手有什么问题

核心目的:防止已失效的连接请求报文段突然传到服务端,导致错误地建立连接,浪费服务器资源。

a、问题出现-两次握手

无法确认客户端是否真的希望建立连接

  1. 客户端(Client曾向服务器Server)发送一个连接请求(SYN 报文),但该报文并未丢失,而是在网络中长时间滞留
  2. 此时客户端早已放弃该连接(可能已正常关闭),但滞留的 SYN 报文在连接释放后很久才到达服务器
  3. 服务器收到这个“过期”的 SYN 后,误以为是客户端发起的新连接请求,于是回复 SYN+ACK立即进入连接建立状态(ESTABLISHED
  4. 然而,客户端当前并未发起任何连接请求,因此会忽略服务器发来的 SYN+ACK
  5. 结果:服务器单方面认为连接已建立,持续等待客户端发送数据,导致内存、端口等资源被无效占用,严重时可能引发资源耗尽。

b、问题解决:三次握手

第三次握手本质上是对“客户端当前意愿”的最终确认,确保连接请求是“新鲜且有效”的。

  • 服务器在收到 SYN 后,仅进入 SYN_RECV 状态,不会立即分配完整连接资源
  • 只有当客户端主动回传 ACK(第三次握手)后,双方才真正进入 ESTABLISHED 状态;
  • 在上述过期 SYN 场景中:
    • 服务器因收不到第三次握手的 ACK,会在超时后丢弃该半连接;
    • 连接不会建立,资源不会浪费

c、四次握手?没必要

第三次握手已足够完成双向确认,更多握手只会增加延迟,无实际收益。三次握手是可靠性与效率之间的最佳平衡,既避免了资源浪费,又确保了连接的正确建立。

握手次数 是否能防止“过期 SYN”攻击 是否可靠建立双向连接
两次 不能 服务器单方面建立
三次 双方共同确认

4、SYN 攻击

1)攻击原理

SYN Flood 是一种典型的 拒绝服务攻击(DoS,利用 TCP 三次握手的机制缺陷进行攻击:

注意:这种攻击不依赖漏洞,而是滥用协议正常行为,属于资源耗尽型攻击。

  1. 攻击者伪造大量IP 地址(通常是不存在或不可达的地址),向目标服务器发送大量 SYN 请求;
  2. 服务器收到 SYN 后,会为每个请求分配资源(如内存、半连接队列条目),并回复 SYN+ACK,进入 SYN_RECEIVED状态,等待客户端的最终 ACK 确认;
  3. 由于源 IP 是伪造的,攻击者不会(也无法)发送 ACK,导致服务器长时间维持这些“半开连接”;
  4. 服务器在超时前会多次重传 SYN+ACK,进一步消耗资源;
  5. 最终,半连接队列被占满,合法用户的连接请求无法进入队列,导致服务不可用。

2)危害

  • 耗尽服务器的半连接队列(backlog queue
  • 占用内存和 CPU 资源(用于维护连接状态和重传);
  • 阻塞合法用户的新连接请求,造成服务拒绝(Denial of Service);
  • 在大规模攻击下,可能导致整个服务器或网络瘫痪。

3)如何监测

少量 SYN_RECV 是正常的(如网络延迟导致 ACK 未及时到达),但短时间内激增 + 源 IP 随机是典型攻击特征。

  • 使用命令查看处于 SYN_RECEIVED 状态的连接数量是否异常高:

    netstat -napt | grep SYN_RECV | wc -l
    
  • 观察这些连接的源 IP 是否高度随机、无规律(真实用户通常来自有限 IP 段);

  • 监控系统日志或使用 IDS/IPS(如 SnortSuricata)检测异常流量模式。

4)防御措施

防御方法 原理说明
SYN Cookies 服务器在收到 SYN 时不立即分配资源,而是通过加密算法生成一个“Cookie”作为初始序列号。只有收到合法 ACK 后,才重建连接上下文。无需维护半连接队列,可有效抵御 SYN Flood
缩短 SYN_RECV 超时时间 减少半开连接的等待时间(如从默认 60–120 秒降至 10–20 秒),加快资源回收。
增大半连接队列 临时缓解小规模攻击,但无法根本解决问题(攻击者可继续填满)。
启用防火墙/IDS 规则 限制单 IPSYN 速率(如 iptables + limit 模块),或直接丢弃可疑流量。
使用云防护/WAF Cloudflare、阿里云 DDoS 高防等,可在网络边缘清洗恶意流量。

5、Https

问题 HTTP HTTPS
数据是否加密? 明文 TLS 加密
能否防篡改? 无校验 数字签名 + Hash
能否验身份? 任意伪装 CA 证书认证
是否推荐使用? 仅用于非敏感场景 全站强制启用

1)HTTP 的三大安全问题

HTTP = 裸奔在网络上的信使,谁都能偷看、修改、冒充。

a、通信使用明文 → 内容可能被窃听

  • HTTP 报文以明文形式传输,任何中间节点(如路由器、运营商、公共 Wi-Fi)都可直接读取内容。
  • 攻击者可通过网络嗅探还原用户账号、密码、银行卡等敏感信息。
  • 运营商甚至可进行 HTTP 劫持:插入广告、强制跳转下载页(通过 302 重定向或篡改 HTML)。

b、无法验证完整性 → 报文可能被篡改

  • HTTP 没有机制校验数据是否被修改。
  • 即使内容未被解密,攻击者仍可篡改传输中的数据(如将“转账 100 元”改为“转账 10000 元”),而接收方无法察觉。

c、无法验证身份 → 通信方可能被伪装

  • HTTP 不验证服务器身份,任何人都可搭建钓鱼网站。
  • 用户访问 http://bank.com 时,实际可能连接到伪造的服务器,导致信息泄露。

2)HTTPS 是什么?

HTTPS = HTTP + SSL/TLS 加密层

  • 它不是新协议,而是 HTTPTCP 之间加入了一层安全协议(SSL/TLS)
  • 实际上,HTTPS 就是“披着 SSL/TLS 外壳的 HTTP”
  • SSLSecure Socket Layer 安全套接层)) 是早期协议,已废弃。
  • TLS**Transport Layer Security,传输层安全协议 是其继任者,现代 HTTPS 实际使用的是 TLS 1.2 或 TLS 1.3

3)HTTPS 如何解决 HTTP 的问题

HTTPS 通过 加密 + 身份认证 + 完整性校验 三位一体机制,全面保障通信安全。

a、防窃听:混合加密机制

HTTPS = 非对称加密(安全握手) + 对称加密(高效通信)

加密方式 优点 缺点 作用
对称加密(如 AES 加解密速度快 密钥分发困难(如何安全传密钥?) 关键点:非对称加密只用于安全地传递这个对称密钥不用于加密实际的 HTTP 数据
非对称加密(如 RSA 安全分发密钥 计算开销大,不适合大量数据 用于“实际数据传输
  • 非对称加密(如 RSAECDHE)——只用于“密钥协商”

    • 客户端用服务器的公钥加密一个随机生成的对称密钥

    • 客户端用服务器的公钥(来自证书)加密这个对称密钥,发送给服务器。

    • 服务器用自己的私钥解密,得到这个对称密钥。

  • 对称加密(如 AES-128-GCMChaCha20–用于“实际数据传输

    • 一旦双方都拥有了相同的对称密钥,后续所有的 HTTP 请求/响应数据(HTML、JSON、图片等)都用这个对称密钥加密

    • 对称加密速度快、开销小,适合大量数据传输。

b、防篡改:数字签名 + 哈希校验

  • 发送方对消息计算 Hash 值(摘要)
  • 私钥对该摘要加密,生成 数字签名
  • 接收方用公钥解密签名,得到摘要 A
  • 自己对收到的消息再算一次 Hash,得到摘要 B;
  • A == B,则说明消息未被篡改

c、防伪装:数字证书(由 CA 颁发)

引入 可信第三方 —— 证书颁发机构(CA, Certificate Authority,浏览器地址栏出现 🔒 锁形图标,即表示 HTTPS 有效且证书可信。

  1. 服务器向 CA 提交公钥、域名等信息;
  2. CA 验证身份(如域名所有权、企业资质);
  3. CA 用自己的私钥对服务器信息+公钥进行签名,生成 数字证书
  4. 客户端内置了受信任 CA 的根证书(含公钥)
  5. 浏览器收到服务器证书后:
    • CA 公钥验证证书签名;
    • 检查域名、有效期等;
    • 验证通过 → 信任该服务器公钥。

二、Request

HttpServletRequestJava Servlet API 中用于封装 HTTP 请求的核心对象。它提供了丰富的接口,用于获取客户端请求的各类信息。

1、路径相关

Spring Boot 内嵌容器中,默认没有 contextPath,因此 getRequestURI()getServletPath()

方法 说明 示例(Spring Boot 无上下文路径)
getRequestURL() 完整 URL(协议 + 主机 + 端口 + URI),不含查询参数 http://localhost:8081/healerjean/youhui/web/authorize
getRequestURI() URI 部分(不含协议、主机、端口、参数) /healerjean/youhui/web/authorize
getContextPath() Web 应用上下文路径(传统 WAR 部署时为项目名) /news(传统项目);Spring Boot 默认为空字符串 ""
getServletPath() Servlet 映射路径(通常与 getRequestURI() 在无上下文路径时相同) /healerjean/youhui/web/authorize

2、服务器与网络信息

  • getServerPort() 受反向代理影响(如 Nginx 转发到 8080,但 Host80,则 getServerPort()=80);
  • getLocalPort()Tomcat 实际绑定的端口(如 8080)。
方法 说明 示例
getServerName() 服务器主机名(来自 Host 头或本地配置) localhosttest.healerjean.cn
getServerPort() 获取 Http端口 808180
getLocalPort() 获取 本机 端口 80818086
getHeader("Host") 原始 Host 请求头(格式:host[:port]80/443 端口不显示 localhost:8081test.healerjean.cn

3、请求元数据

方法 说明
getMethod() HTTP 方法(GETPOSTPUT 等)
getHeader("Referer") 上一页面 URL(用于防盗链、来源分析),直接访问时为 null
getContentType() 请求体的 MIME 类型(如 application/json),GET 请求通常为 null

a、常见 Content-Type 类型

  • getParameter() 方法仅支持 application/x-www-form-urlencodedmultipart/form-data(需额外解析)
类型 使用场景
application/x-www-form-urlencoded 普通表单提交(默认)
multipart/form-data 文件上传表单(必须设置)
application/json AJAX 发送 JSON 数据
text/plain, text/html, image/* 静态资源或 API 返回类型(较少用于请求体)

4、请求头(Headers

a、获取所有请求头

Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
    String name = headerNames.nextElement();
    System.out.println(name + ": " + request.getHeader(name));
}

b、GET vs POST 典型请求头差异

特征 GET POST
是否含 Content-Type 通常无 必有(如 application/json
是否含 Content-Length 有(除非 chunked)
参数位置 URL 查询字符串 请求体
典型 Header Accept, Cookie Content-Type, Origin, Token

5、参数获取

1)、获取请求参数

a、GET 参数:getQueryString()

// 仅适用于 GET,返回原始查询字符串(未解码)
request.getQueryString(); // age=1&name=healerjean

b、表单参数(非文件):getParameter()

  • 支持:application/x-www-form-urlencoded
  • 不支持multipart/form-data(需用 MultipartResolverApache FileUpload
request.getParameter("name");        // 单值
request.getParameterValues("hobby"); // 多值(如 checkbox)
request.getParameterMap();           // 所有参数 Map<String, String[]>

2)获取请求体(Raw Body

当需要处理 JSONXML 或自定义格式时,需直接读取输入流:

// 方式1:字节流(适合二进制、JSON 等)
InputStream inputStream = request.getInputStream();

// 方式2:字符流(适合文本)
BufferedReader reader = request.getReader();

3)关键限制:流只能读一次!

a、限制逻辑

getParameter()getInputStream()getReader() 三者互斥,原因如下:

场景 行为
application/x-www-form-urlencoded 调用 getParameter()提前消费输入流,后续 getInputStream() 返回空
multipart/form-data getParameter() 无法解析,必须用流或专用解析器(如 SpringMultipartFile
任意情况 getInputStream()getReader() 不能同时调用,否则抛 IllegalStateException

b、解决方案:缓存请求体(可重复读)

  • 方案:通过 HttpServletRequestWrapper 包装原始请求,预先读取并缓存 body

  • 适用场景:日志记录、验签、重复读取 body(如 API 网关、安全审计)。

通过 HttpServletRequestWrapper 包装原始请求,预先读取并缓存 body:

public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
    private final byte[] cachedBody;

    public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        this.cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
    }

    @Override
    public ServletInputStream getInputStream() {
        return new ByteArrayServletInputStream(cachedBody);
    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
}

配合 Filter 使用:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
    HttpServletRequest request = (HttpServletRequest) req;
    CachedBodyHttpServletRequest wrappedRequest = new CachedBodyHttpServletRequest(request);
    chain.doFilter(wrappedRequest, res);
}

三、CookieSession

1、Session 详解

  • Session 是服务端对象
    • 由服务器创建并存储(内存/Redis/DB 等)
    • 每个 Session 有唯一 ID(JSESSIONID)。
  • Session ID 通过 Cookie 传递
    • 默认情况下,服务器通过 Set-Cookie: JSESSIONID=xxx 告知浏览器;
    • 浏览器后续请求自动携带该 Cookie
  • Cookie 的作用域(Domain + Path)决定了哪些请求会携带它

1)Session 生命周期

行为 是否销毁 Session
调用 session.invalidate() 立即销毁
超过 maxInactiveInterval(默认 30 分钟) 自动销毁
关闭浏览器 不销毁
服务器重启(内存存储) 丢失(除非持久化到 Redis/File)

2) Session 数据操作

HttpSession session = request.getSession(); // true: 无则创建

// 存
session.setAttribute("USER", userDTO);

// 取
UserDTO user = (UserDTO) session.getAttribute("USER");

// 遍历
Enumeration<String> names = session.getAttributeNames();
while (names.hasMoreElements()) {
    String key = names.nextElement();
    Object value = session.getAttribute(key);
    log.info("{}: {}", key, value);
}

2、Cookie 详解

request.getCookies() 只返回当前请求携带的 Cookie,受 Domain/Path/Secure 等属性限制。

Cookie[] cookies = request.getCookies();
if (cookies != null) {
    for (Cookie cookie : cookies) {
        System.out.println(cookie.getName() + "=" + cookie.getValue());
    }
}

2)CookieSession 的关系

项目 Cookie Session
存储位置 客户端(浏览器) 服务端
安全性 低(可被窃取) 高(数据在服务端)
用途 传递 Session ID、记住偏好等 存储用户状态、权限等敏感信息
关联方式 JSESSIONID Cookie 指向 Session Session ID 通常通过 Cookie 传递

不需要写 response.addCookie(...),容器(如 Tomcat)会自动处理。

HttpSession session = request.getSession(); // 或 getSession(true)

如果这是用户第一次访问(还没有 Session),服务器会:

  • 创建一个新的 HttpSession 对象
  • 生成一个唯一的 Session ID(如 JSESSIONID=abc123xyz
  • 自动通过 Set-Cookie 响应头将 JSESSIONID 发送给浏览器

浏览器收到 Set-Cookie: JSESSIONID=... 后,会在同域下的后续请求中自动带上:

Cookie: JSESSIONID=abc123xyz

c、服务器自动识别 Session

Servlet 容器(如 Tomcat)会自动从请求的 Cookie 中提取 JSESSIONID,并根据它查找对应的 HttpSession 对象。你在代码中直接调用:

HttpSession session = request.getSession();
UserDTO user = (UserDTO) session.getAttribute("USER");

2、问题

1)不同端口,Session 是否共享

条件 浏览器是否共享 JSESSIONID Cookie?
同域名 + 同端口 共享(同一应用)
同域名 + 不同端口 不共享(浏览器将端口视为不同源
  • Domain=localhost(或未指定)时,Cookie 默认绑定到完整的 host:port 组合
  • 即使域名相同,http://localhost:8080http://localhost:8081 被视为两个独立站点
  • 因此,它们各自设置的 JSESSIONID 互不影响,不会“覆盖”。

3)如何实现跨端口/跨应用 Session 共享

若需多个应用(如 SSO 客户端)共享登录状态,不能依赖默认 Session 机制,而应采用:

  • Session 存入 Redis(而非 Tomcat 内存);

  • 所有应用配置相同的:这样,app1.healerjean.cn:8080app2.healerjean.cn:8081 可共享 JSESSIONID

    // Spring Boot 示例
    server.servlet.session.cookie.domain = .healerjean.cn   // 注意前缀点
    server.servlet.session.cookie.path = /
    

方案 2:SSO(单点登录)架构(推荐)

  • 用户只在 认证中心(CAS/OAuth2 Server) 登录;
  • 各业务系统(Client)通过 Ticket / Token 验证身份;
  • 各 Client 有自己的 Session,但通过 SSO 协议同步登录状态;
  • 不要求 JSESSIONID 相同,因此不存在“覆盖”问题。

4)关键结论

问题 正确认知
同域名不同端口是否共享 Session 默认不共享(浏览器隔离 Cookie
如何实现多应用登录状态同步? SSO 或统一 Session 存储 + Cookie Domain 配置
关闭浏览器会销毁 Session 吗? 不会(除非 Session Cookie 未持久化)

四、跨域解决

1、什么是跨域?

1)同源策略(Same-Origin Policy

浏览器出于安全考虑,限制 不同源 的网页脚本访问资源。同源 = 协议 + 域名 + 端口 完全相同

URL A URL B 是否同源 原因
http://localhost:8080 http://localhost:9000 端口不同
https://api.example.com http://api.example.com 协议不同
http://example.com http://www.example.com 域名不同

2)跨域请求的表现

  • 浏览器控制台报错:

    Access to XMLHttpRequest at 'http://localhost:8080/api' from origin 'http://localhost:3000' has been blocked by CORS policy.
    
  • 请求可能发出(简单请求),但响应被浏览器拦截

  • 非简单请求(如带自定义 HeaderJSON body)会先发 预检请求(Preflight,OPTIONS

2、解决跨域

方案 适用场景 优点 缺点
拦截器 全局统一 CORS 一次配置,处处生效;自动处理 OPTIONS 所有请求都经过拦截器(轻微性能开销)
工具类 单接口精细控制 灵活,可选是否启用 CORS 需在每个接口手动调用

1)工具类

package com.healerjean.proj.utils.http;

/**
 * CorsUtils
 *
 * @author zhangyujin
 * @date 2025/11/12
 */

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;

public class CorsUtils {


    /**
     * 校验 Origin 是否在允许的域名白名单中
     * - 以 "." 开头:表示匹配该域名及所有子域名(如 ".bail.com" 匹配 bail.com, app.bail.com, x.y.bail.com)
     * - 不以 "." 开头:仅精确匹配该主域名(如 "bail.com" 只匹配 bail.com,不匹配 app.bail.com)
     */
    public static boolean isValidOrigin(String origin, List<String> allowedDomains) {
        if (origin == null || origin.trim().isEmpty()) {
            return false;
        }

        String host;
        try {
            host = new URI(origin).getHost();
        } catch (URISyntaxException e) {
            return false;
        }

        if (host == null || host.isEmpty()) {
            return false;
        }

        host = host.toLowerCase();

        for (String pattern : allowedDomains) {
            if (pattern == null || pattern.trim().isEmpty()) {
                continue;
            }
            pattern = pattern.trim().toLowerCase();

            if (pattern.startsWith(".")) {
                // 模式是 .example.com → 匹配 example.com 及 *.example.com
                String domain = pattern.substring(1);
                if (domain.isEmpty()) {
                    continue;
                }

                // 匹配规则:
                // 1. host == domain (主域)
                // 2. host 以 ".domain" 结尾(子域)
                if (host.equals(domain) || host.endsWith("." + domain)) {
                    return true;
                }
            } else {
                // 模式是 example.com → 仅精确匹配
                if (host.equals(pattern)) {
                    return true;
                }
            }
        }

        return false;
    }
}
白名单配置 Origin 是否通过 说明
.bail.com https://bail.com 主域匹配
.bail.com https://app.bail.com 子域匹配
.bail.com https://x.y.bail.com 多级子域
.bail.com https://evilbail.com 不匹配(不是子域)
.bail.com https://bail.com.cn 不匹配
bail.com(无点) https://bail.com 精确匹配
bail.com(无点) https://app.bail.com 不匹配子域
  • 统一使用带点写法(推荐):如果你想支持子域名,全部写成 ".xxx.com",语义清晰,避免混淆。
  • 不要混用敏感域名: 避免像 ".com" 这种过于宽泛的配置,会带来安全风险。
  • 配合 HTTPS 使用: 在生产环境,建议只允许 HTTPSOrigin(可在 isValidOrigin 中加协议判断):

2)Spring 拦截器解决

  • 全局生效,无需每个接口处理
  • 自动处理 OPTIONS 预检
  • 仅对白名单域名放行,安全可靠
// src/main/java/com/yourpackage/interceptor/CorsInterceptor.java
package com.yourpackage.interceptor;

import com.yourpackage.util.CorsUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CorsInterceptor implements HandlerInterceptor {

   // 允许的主域名(支持任意子域名)
    private static final List<String> ALLOWED_DOMAINS = Arrays.asList(
        "a.com",
        "b.com"
        // 可按需添加,如 "example.org"
    );
  
  
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws IOException {
        String origin = request.getHeader("Origin");
        String method = request.getMethod();

        // 处理预检请求(OPTIONS)
        if ("OPTIONS".equalsIgnoreCase(method)) {
            if (origin != null && CorsUtils.isValidOrigin(origin, ALLOWED_DOMAINS)) {
                setCorsHeaders(response, origin);
            }
            response.setStatus(HttpServletResponse.SC_OK);
            return false; // 终止后续处理
        }

        // 处理实际请求
        if (origin != null && CorsUtils.isValidOrigin(origin, ALLOWED_DOMAINS)) {
            setCorsHeaders(response, origin);
        }

        return true; // 继续执行 Controller
    }

    private void setCorsHeaders(HttpServletResponse response, String origin) {
        response.setHeader("Access-Control-Allow-Origin", origin);
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers",
            "Content-Type, Authorization, X-Requested-With, Accept, Origin");
        response.setHeader("Access-Control-Max-Age", "3600");
    }
}
import com.yourpackage.interceptor.CorsInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private CorsInterceptor corsInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(corsInterceptor)
                .addPathPatterns("/**"); // 拦截所有路径
    }
}

3)单个方法中手动处理 CORS

// src/main/java/com/yourpackage/util/CorsHelper.java
package com.yourpackage.util;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CorsHelper {

     // 允许的主域名(支持任意子域名)
    private static final List<String> ALLOWED_DOMAINS = Arrays.asList(
        "a.com",
        "b.com"
        // 可按需添加,如 "example.org"
    );
  
    /**
     * 处理 CORS 请求
     *
     * @return true 表示是 OPTIONS 请求且已处理完毕,调用方应直接返回;
     *         false 表示是非 OPTIONS 请求,可继续业务逻辑。
     */
    public static boolean handleCors(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String origin = request.getHeader("Origin");
        if (origin == null || !CorsUtils.isValidOrigin(origin, ALLOWED_DOMAINS)) {
            // 非法 Origin 不设置 CORS 头(同源请求不受影响)
            return !"OPTIONS".equalsIgnoreCase(request.getMethod());
        }

        // 设置 CORS 头
        response.setHeader("Access-Control-Allow-Origin", origin);
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers",
            "Content-Type, Authorization, X-Requested-With, Accept, Origin");
        response.setHeader("Access-Control-Max-Age", "3600");

        // 如果是 OPTIONS,直接返回
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return true;
        }

        return false; // 继续业务
    }
}

3、问题

1)OPTIONS 请求

a、什么时候浏览器会发 OPTIONS 请求

当请求满足 “非简单请求” 条件时,浏览器会自动先发一个 OPTIONS 预检请求(Preflight Request),例如:

现代前端(AxiosFetch)几乎都会触发 OPTIONS,因为:

  • 默认发 JSONContent-Type: application/json
  • 带认证头:Authorization: Bearer xxx
情况 是否触发 OPTIONS
GET / POST + Content-Type: application/x-www-form-urlencoded ❌ 不触发
POST + Content-Type: application/json ✅ 触发
任意请求 + 自定义 Header(如 Authorization, X-Token ✅ 触发
使用 PUT / DELETE 等方法 ✅ 触发

b、如果不处理 OPTIONS,会发生什么?

如果你的后端 没有返回 200 + 正确 CORS 头 → 浏览器直接 拦截后续 POST 请求

  • 接口调用失败
  • 错误信息误导性强(看起来像“没配 CORS”,其实是“没处理 OPTIONS”)
  • 开发环境可能“偶然能用”(比如只测了 GET),但上线后 POST 全挂

c、为什么“历史代码没加也能跑”?

不处理 OPTIONS 是“侥幸成功”,不是“正确实现”

原因 说明
只测试了简单请求 比如纯 GET、无自定义 Header,不会触发 OPTIONS
前端用了代理 开发时通过 webpack devServer 代理,绕过了 CORS
后端框架自动处理了 Spring Boot 默认对未映射的 OPTIONS 返回 405,但某些旧版本或配置可能“巧合通过”(不可靠)
浏览器缓存了预检结果 第一次失败后,短时间内不再发 OPTIONS,造成“能用”的假象

2)request.getHeader("Origin")

Origin 类型 来源 是否应处理 CORS
null null 同源请求、Postman、curl 不需要
"null"(字符串) "null" file:// 本地页面 禁止响应(安全风险)
"https://app.bail.com" 合法 URL 正常跨域页面 白名单校验后响应

a、什么时候 Originnull

  • 同源请求(Same-Origin

    • 当前端页面和后端 API 同协议 + 同域名 + 同端口 时,浏览器 不会发送 Origin 请求头

    • 此时 request.getHeader("Origin") 返回 null(Java 对象为 null)。

  • 非浏览器客户端发起的请求
    • PostmancurlJava HttpClientPython requests 等工具默认 不带 Origin
    • 结果:getHeader("Origin") == null
  • 某些特殊浏览器行为或隐私模式:极少数情况下(如隐私浏览、扩展干扰),浏览器可能省略 Origin

b、什么时候 Origin 是字符串 "null"

  • 通过 file:// 协议打开的本地 HTML 页面
    • 浏览器出于安全考虑,Origin 设为字符串 "null"(注意:不是 Java 的 null,而是字面量 "null"
    • 所以 request.getHeader("Origin") 返回的是 "null"(String 类型)

c、是否要对 "null" 做响应?

  • 强烈建议:不要为 "null" 设置 Access-Control-Allow-Origin

  • 原因:

    • 来自 file:// 的请求不可信,无法验证来源
      • 如果你返回 Access-Control-Allow-Origin: null任何本地 HTML 文件都能调用你的 API,造成严重安全风险!

3)为什么要加 CORS 逻辑

让浏览器知道:“这个跨域请求是被允许的,请把响应内容暴露给前端 JavaScript

是否需要 CORS  
前端(React/Vue)调用自己后端 API(不同域) 必须加,否则前端拿不到数据
提供开放 API 给第三方网页使用 必须加(或用 JSONP / 代理)
App / 小程序 / Postman 调用 API 不需要(它们不受浏览器 CORS 限制)
同域部署(前端和后端同一个域名) 不需要

a、进得去” ≠ “拿得到

阶段 谁在操作 是否受 CORS 影响
1. 请求发送 浏览器 → 你的服务器 不受影响(请求照发)
2. 后端处理 你的 Spring Controller 不受影响(照常执行)
3. 响应返回 你的服务器 → 浏览器 网络层照常传输
4. 浏览器交付 浏览器 → 前端 JavaScript CORS 控制!

b、结论

说法 是否正确 解释
跨域请求能进入 Controller 正确 服务器无条件处理所有 HTTP 请求
不配 CORS 前端拿不到数据” 正确 浏览器拦截了响应的“JS 可见性
不配 CORS 请求会被服务器拒绝 错误 服务器根本不知道“跨域”这回事

五、工具类

1、Ip 工具类

package com.hlj.utils.ip;

import com.hlj.utils.ExceptionLogUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * @Desc:
 * @Author HealerJean
 * @Date 2018/7/4  下午2:36.
 */
public class IpUtil {

    public static String getIp(){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        if(request==null)return null;
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        if(ip.toLowerCase().contains("x-forwarded-for")){
            String ip_temp = "";
            if(ip.contains(":") && ip.split(":").length>0 && !ip.endsWith(":")){
                ip_temp = ip.split(":")[1];
                if(StringUtils.isBlank(ip_temp)) ip_temp = ip.substring(0,ip.lastIndexOf("X-Forwarded-For"));
            }else{
                ip_temp = ip.substring(0,ip.lastIndexOf("X-Forwarded-For"));
            }
            ip = ip_temp.replaceAll(" ","");
        }
        if (ip.indexOf(",") > -1) {
            String ip_temp = ip.split(",")[0];
            ip_temp = ip_temp.replaceAll(" ", "");
            if(ip_temp.startsWith("10.") && ip.split(",").length>1){
                ip = ip.split(",")[1];
                ip = ip.replaceAll(" ", "");
            }else ip = ip_temp;
        }
        return ip;
    }


      /**
     * 获取调用的http/https+ 域名 + 端口
     * @param urlTarget
     * @return
     */
    public static String getDomainAndPort(String urlTarget)
    {
        //跳转到对应的回调地址
        String domain = "";
        try {
            URL url = new URL(urlTarget);
            String host = url.getHost();
            int port = url.getPort();
            String s = url.toString();
            domain = s.substring(0,s.indexOf(host)+host.length());
            if(port != -1) {
                domain = domain + ":" + port;
            }
        } catch (MalformedURLException e) {
            log.info("获取域名失败");
        }
        return domain ;
    }



    /**
     * 获取服务器ip
     * @return
     */
    public static  String getHostIp(){
        try {
            InetAddress ia2 = InetAddress.getLocalHost();
            return  ia2.getHostAddress();
        } catch (UnknownHostException e) {
            ExceptionLogUtils.log(e,IpUtil.class);
            return  null ;
        }
    }

    /**
     * 根据域名获取ip
     * www.baidu.com
     * @param url
     * @return
     */
    public  static String getIpByUrl(String url) {
        try {
            InetAddress ia2 = InetAddress.getByName(url);
            return ia2.getHostAddress();
        } catch (UnknownHostException e) {
            ExceptionLogUtils.log(e,IpUtil.class );
            return  null;
        }
    }

    public static IPEntry getAddress(String ip){
        return  IPSeeker.getInstance().getAddress(ip);
    }

    public static void main(String[] args) {
        System.out.println(getAddress("106.39.75.134"));
    }

}

2、Request 工具类


import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * 静态获取HttpServletRequest 和 session的方法
 * 要使用此类需要在web.xml注册org.springframework.web.context.request.RequestContextListener
 */
public class RequestHolder {

    public static HttpSession getSession() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return request.getSession();
    }

    public static HttpServletRequest getRequest() {
        return ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
    }

    public static HttpServletResponse getResponse() {
        return ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();
    }

}

3、Cookie 工具类

package com.duodian.youhui.admin.utils.cookie;

import com.duodian.youhui.admin.utils.RequestHolder;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Desc: Cookie 工具类
 * @Date:  2018/9/12 下午1:02.
 */

public class CookieHelper {


    public static void setCookie(String key,String value,Integer seconds){
        HttpServletResponse response = RequestHolder.getResponse();
        Cookie add = new Cookie(key,value);
        add.setMaxAge(seconds);
        add.setPath("/");
        response.addCookie(add);
    }

    public static String getCookieValue(String key){
        HttpServletRequest request = RequestHolder.getRequest();
        Cookie[] cookies = request.getCookies();
        if (cookies == null){
            return null;
        }
        for (Cookie cookie : cookies){
            if (StringUtils.equals(cookie.getName(), key)){
                return cookie.getValue();
            }
        }
        return null;
    }

    public static void clearCookie(String key){
        HttpServletResponse response = RequestHolder.getResponse();
        Cookie clear = new Cookie(key,"");
        clear.setMaxAge(0);
        clear.setComment("清除cookie");
        clear.setPath("/");
        response.addCookie(clear);
    }


}

4、Session 工具

package com.duodian.admore.home.utils;

import com.duodian.admore.constants.AppConstants;
import com.duodian.admore.core.spring.RequestHolder;
import com.duodian.admore.entity.db.user.User;

import javax.servlet.http.HttpSession;

public class SessionUtils {

   
    public static final String SESSION_USER = "user";


    public static void initSession(User user){
        HttpSession session = RequestHolder.getSession();
        session.setAttribute(SESSION_USER,user);
    }

    public static void clearSession(){
        HttpSession session = RequestHolder.getSession();
        session.invalidate();
    }

    public static User getSessionUser(){
        HttpSession session = RequestHolder.getSession();
        User user = (User)session.getAttribute(SESSION_USER);
        return user;
    }


}

ContactAuthor