Java网络编程(UDP, TCP, HTTP)
1. OSI 七层网络模型
层级 | 名称 | 核心功能 | 协议示例 | 数据单元 |
---|---|---|---|---|
7 | 应用层 | 提供用户接口和网络服务 | HTTP, FTP, SMTP, DNS | 报文 |
6 | 表示层 | 数据格式转换、加密/解密、压缩/解压 | SSL, JPEG, MPEG | 数据流 |
5 | 会话层 | 建立、管理和终止会话连接 | NetBIOS, RPC | 会话数据 |
4 | 传输层 | 端到端可靠传输、流量控制、差错校验 | TCP, UDP | 数据段 |
3 | 网络层 | 路由选择、逻辑寻址、分组转发 | IP, ICMP, OSPF | 数据包 |
2 | 数据链路层 | 物理寻址、帧同步、差错控制 | Ethernet, PPP | 帧 |
1 | 物理层 | 比特流传输、物理接口定义 | RS-232, 100Base-T | 比特 |
2. 传输层协议
特性 | UDP | TCP |
---|---|---|
连接方式 | 无连接 | 面向连接(三次握手) |
可靠性 | 不保证送达 | 可靠传输(ACK+重传) |
数据边界 | 保留数据包边界 | 字节流(无边界) |
传输速度 | 更快(无连接开销) | 较慢(需维护连接状态) |
头部开销 | 8 字节 | 20-60 字节 |
适用场景 | 视频流、DNS、实时游戏、广播 | 文件传输、网页浏览 |
2.1 UDP
UDP(User Datagram Protocol)是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。
- 无连接传输
- 通信前无需建立连接,直接发送数据包
java.net.DatagramSocket
- 不可靠传输
- 不保证数据包顺序、不检测丢包、无重传机制
- 传输效率高于 TCP(头部仅 8 字节)
- 面向数据报
- 每次发送/接收都是完整数据包(有明确边界)
- 数据包最大长度: 64KB - 8 字节(头部)
- 支持广播/多播
- 可向同一网络内所有主机发送广播(地址:255.255.255.255)
- 支持多播(组播)地址范围:224.0.0.0 ~ 224.255.255.255
2.1.1 发送数据
import java.net.*;public class UDPSender {public static void main(String[] args) throws Exception {// 1. 创建Socket(随机端口)DatagramSocket socket = new DatagramSocket();// 2. 准备数据包(目标地址为localhost,端口8888)String message = "Hello UDP!";byte[] data = message.getBytes();InetAddress address = InetAddress.getByName("localhost");DatagramPacket packet = new DatagramPacket(data, data.length, address, 8888);// 3. 发送socket.send(packet);System.out.println("已发送: " + message);// 4. 关闭socket.close();}
}
2.1.2 接收数据
import java.net.*;public class UDPReceiver {public static void main(String[] args) throws Exception {// 1. 创建Socket并绑定端口8888DatagramSocket socket = new DatagramSocket(8888);// 2. 准备空数据包(缓冲区)byte[] buffer = new byte[1024];DatagramPacket packet = new DatagramPacket(buffer, buffer.length);// 3. 阻塞接收System.out.println("等待接收数据...");socket.receive(packet); // 阻塞直到收到数据// 4. 处理数据String message = new String(packet.getData(), 0, packet.getLength());System.out.println("收到来自" + packet.getAddress() + ":" + packet.getPort() + "的消息: " + message);// 5. 关闭socket.close();}
}
2.1.3 广播
// 发送广播示例
socket.setBroadcast(true); // 开启广播
InetAddress broadcastAddress = InetAddress.getByName("255.255.255.255");
DatagramPacket broadcastPacket = new DatagramPacket(data, data.length, broadcastAddress, 8888);
socket.send(broadcastPacket);
2.1.4 多播(组播)
发送
// 加入多播组(224.0.0.0~224.255.255.255)
InetAddress group = InetAddress.getByName("224.0.0.1");
MulticastSocket multicastSocket = new MulticastSocket();// 发送数据到组
DatagramPacket packet = new DatagramPacket(data, data.length, group, 8888);
multicastSocket.send(packet);
接收
MulticastSocket multicastSocket = new MulticastSocket(8888);
multicastSocket.joinGroup(InetAddress.getByName("224.0.0.1")); // 加入组// 接收数据(同普通UDP接收)
multicastSocket.receive(packet);
2.2 TCP
TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
- 面向连接:在数据传输之前,必须建立连接(三次握手),传输结束后要断开连接(四次挥手)。
- 可靠传输:通过确认机制、超时重传、流量控制、拥塞控制等机制保证数据正确到达。
- 字节流传输:数据被视为无结构的字节流,没有边界,但接收方收到的数据顺序与发送方发送的顺序一致。
2.2.1 三次握手
- 客户端发送 SYN(同步序列编号)包(SYN=1, seq=x)到服务器,进入 SYN_SENT 状态。
- 服务器收到 SYN 包,发送 SYN+ACK 包(SYN=1, ACK=1, seq=y, ack=x+1),进入 SYN_RECV 状态。
- 客户端收到 SYN+ACK 包,发送 ACK 包(ACK=1, seq=x+1, ack=y+1),进入 ESTABLISHED 状态。服务器收到 ACK 后也进入 ESTABLISHED 状态。
建立连接。
2.2.2 四次挥手
- 主动关闭方(假设为客户端)发送 FIN 包(FIN=1, seq=u),进入 FIN_WAIT_1 状态。
- 被动关闭方(服务器)收到 FIN,发送 ACK 包(ACK=1, seq=v, ack=u+1),进入 CLOSE_WAIT 状态。客户端收到 ACK 后进入 FIN_WAIT_2 状态。
- 服务器准备好关闭连接时,发送 FIN 包(FIN=1, seq=w, ack=u+1),进入 LAST_ACK 状态。
- 客户端收到 FIN,发送 ACK 包(ACK=1, seq=u+1, ack=w+1),进入 TIME_WAIT 状态,等待 2MSL(最大报文段生存时间)后关闭。服务器收到 ACK 后立即关闭。
断开连接。
2.2.3 实现
在 Java 中,我们可以使用 java.net.ServerSocket
和 java.net.Socket
类来实现 TCP 通信。
- 服务器端使用 ServerSocket 监听指定端口,等待客户端连接。当有客户端连接时,创建一个 Socket 对象,通过该对象进行通信。
- 客户端使用 Socket 连接到服务器,然后通过输出流向服务器发送数据,通过输入流读取服务器返回的数据。
服务器端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;public class TCPServer {public static void main(String[] args) throws IOException {// 创建服务器Socket,监听8888端口ServerSocket serverSocket = new ServerSocket(8888);System.out.println("服务器启动,等待客户端连接...");// 等待客户端连接Socket socket = serverSocket.accept();System.out.println("客户端连接成功!");// 获取输入流,读取客户端数据BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));String message = in.readLine();System.out.println("收到客户端消息: " + message);// 获取输出流,向客户端发送数据PrintWriter out = new PrintWriter(socket.getOutputStream(), true);out.println("你好,客户端!");// 关闭资源in.close();out.close();socket.close();serverSocket.close();}
}
客户端
import java.io.*;
import java.net.Socket;public class TCPClient {public static void main(String[] args) throws IOException {// 创建客户端Socket,连接服务器Socket socket = new Socket("localhost", 8888);System.out.println("已连接到服务器...");// 获取输出流,向服务器发送数据PrintWriter out = new PrintWriter(socket.getOutputStream(), true);out.println("你好,服务器!");// 获取输入流,读取服务器返回的数据BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));String response = in.readLine();System.out.println("收到服务器响应: " + response);// 关闭资源out.close();in.close();socket.close();}
}
3. 应用层协议
特性 | HTTP | HTTPS(HTTP+SSL/TLS) |
---|---|---|
安全性 | 明文传输(易被窃听) | 加密传输(防窃听/篡改) |
默认端口 | 80 | 443 |
性能 | 较高(无加密开销) | 较低(加密/解密消耗 CPU) |
证书 | 不需要 | 需要 CA 颁发的数字证书 |
适用场景 | 非敏感信息传输(如新闻网站) | 敏感信息传输(如支付/登录) |
3.1 HTTP
HTTP(HyperText Transfer Protocol)是应用层协议,基于 TCP/IP 协议族,用于在 Web 浏览器和服务器之间传输超文本(如 HTML)。
在 Java 中,我们可以使用 HttpURLConnection
或第三方库如 Apache HttpClient 来演示 HTTP 通信。
-
应用层协议
- 基于 TCP/IP 协议栈,用于 Web 浏览器和服务器之间的通信
- 默认端口:HTTP(80)/HTTPS(443)
- 遵循请求-响应模型:客户端发起请求,服务器返回响应
-
无状态协议
- 每个请求相互独立,服务器不保留客户端状态
- 通过 Cookie/Session 机制实现状态管理
-
报文结构
-
请求报文
GET /index.html HTTP/1.1 Host: www.example.com User-Agent: Java-HTTP-Client
-
响应报文
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1024<!DOCTYPE html>...
-
3.1.1 URL 结构
标准格式:协议://主机[:端口]/路径?查询字符串#片段标识符
示例:http://example.com:8080/api/data?category=books#section2
3.1.2 HTTP 方法
方法 | 作用 |
---|---|
GET | 获取资源 |
POST | 提交数据 |
PUT | 更新资源 |
DELETE | 删除资源 |
HEAD | 获取响应头(无响应体) |
3.1.3 状态码
状态码 | 类别 | 说明 |
---|---|---|
1xx | 信息响应 | 请求已被接收 |
2xx | 成功 | 请求处理成功 |
3xx | 重定向 | 需进一步操作 |
4xx | 客户端错误 | 请求包含错误语法 |
5xx | 服务器错误 | 服务器处理请求失败 |
3.1.4 发展
- HTTP/1.0:每个请求需单独建立连接
- HTTP/1.1:默认持久连接(可复用 TCP 连接)
- HTTP/2:二进制分帧、头部压缩、多路复用
- HTTP/3:基于 QUIC 协议(UDP 实现),解决队头阻塞
3.1.5 GET 请求
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;public class HttpClientExample {public static void main(String[] args) throws Exception {URL url = new URL("http://example.com/api/data");HttpURLConnection connection = (HttpURLConnection) url.openConnection();// 设置请求方法connection.setRequestMethod("GET");// 添加请求头connection.setRequestProperty("User-Agent", "Java HTTP Client");// 获取响应码int status = connection.getResponseCode();System.out.println("响应状态码: " + status);// 读取响应体try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {String line;StringBuilder response = new StringBuilder();while ((line = reader.readLine()) != null) {response.append(line);}System.out.println("响应内容: " + response.toString());}// 断开连接connection.disconnect();}
}
3.1.6 POST 请求
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;public class HttpPostExample {public static void main(String[] args) throws Exception {URL url = new URL("http://example.com/api/users");HttpURLConnection connection = (HttpURLConnection) url.openConnection();// 设置请求方法connection.setRequestMethod("POST");connection.setDoOutput(true); // 允许输出// 设置请求头(JSON类型)connection.setRequestProperty("Content-Type", "application/json");// 准备JSON数据String jsonInput = "{\"name\": \"Alice\", \"age\": 30}";// 发送请求体try (OutputStream os = connection.getOutputStream()) {byte[] input = jsonInput.getBytes("utf-8");os.write(input, 0, input.length);}// 处理响应,同GET请求int status = connection.getResponseCode();}
}
3.1.7 超时
connection.setConnectTimeout(5000); // 5秒连接超时
connection.setReadTimeout(10000); // 10秒读取超时
3.1.8 重定向
// 自动跟随重定向(默认true)
connection.setInstanceFollowRedirects(true);// 手动处理重定向
if (status == HttpURLConnection.HTTP_MOVED_PERM) {String newUrl = connection.getHeaderField("Location");// 重新发起请求...
}
3.2 HTTPS
// 创建HTTPS连接
URL url = new URL("https://example.com");
HttpsURLConnection httpsConn = (HttpsURLConnection) url.openConnection();// 配置SSL证书验证(生产环境需使用真实CA证书)
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{new X509TrustManager() {public void checkClientTrusted(X509Certificate[] chain, String authType) {}public void checkServerTrusted(X509Certificate[] chain, String authType) {}public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
}}, new SecureRandom());httpsConn.setSSLSocketFactory(sslContext.getSocketFactory());
httpsConn.setHostnameVerifier((hostname, session) -> true); // 跳过主机名验证// 后续操作与HTTP相同
3.3 WebSocket
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它允许服务端主动向客户端推送数据,非常适合实时应用。
特性 | HTTP | WebSocket |
---|---|---|
通信模式 | 半双工(请求-响应) | 全双工(双向通信) |
连接建立 | 每次请求都需要建立连接 | 一次握手,持久连接 |
头部开销 | 每次请求携带完整 HTTP 头(~800B) | 初始握手后,数据帧头仅 2~14 字节 |
实时性 | 依赖轮询(高延迟) | 实时推送(低延迟) |
适用场景 | 传统网页浏览 | 实时聊天、股票行情、游戏 |
- 全双工实时通信
- 基于 TCP 的持久化连接协议(与 HTTP 互补)
- 服务端和客户端可同时双向传输数据(突破 HTTP 请求-响应限制)
- 默认端口:WS(80)/WSS(443),与 HTTP/HTTPS 端口一致
- 低开销高效传输
- 连接建立后,数据帧头部仅 2-14 字节(远小于 HTTP 头部)
- 避免 HTTP 轮询的资源浪费,适合实时应用(聊天、游戏等)
- 协议升级机制
- 通过 HTTP 升级握手建立连接:
GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13
- 通过 HTTP 升级握手建立连接:
3.3.1 服务端实现
使用 JSR 356 标准 API。
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;@ServerEndpoint("/chat") // 声明 WebSocket 端点路径
public class ChatEndpoint {@OnOpenpublic void onOpen(Session session) {System.out.println("客户端连接: " + session.getId());}@OnMessagepublic void onMessage(String message, Session session) {System.out.println("收到消息: " + message);// 广播消息给所有客户端session.getOpenSessions().forEach(s -> {try {s.getBasicRemote().sendText("Echo: " + message);} catch (Exception e) { e.printStackTrace(); }});}@OnClosepublic void onClose(Session session) {System.out.println("连接关闭: " + session.getId());}@OnErrorpublic void onError(Session session, Throwable error) {error.printStackTrace();}
}
3.3.2 客户端实现
import javax.websocket.*;
import java.net.URI;@ClientEndpoint
public class WebSocketClient {@OnOpenpublic void onOpen(Session session) {System.out.println("连接服务器成功");try {session.getBasicRemote().sendText("Hello Server!");} catch (Exception e) { e.printStackTrace(); }}@OnMessagepublic void onMessage(String message) {System.out.println("收到服务端消息: " + message);}public static void main(String[] args) throws Exception {WebSocketContainer container = ContainerProvider.getWebSocketContainer();container.connectToServer(WebSocketClient.class,new URI("ws://localhost:8080/chat")); // WebSocket 地址}
}
3.3.3 二进制数据传输
@OnMessage
public void onMessage(ByteBuffer data, Session session) {byte[] bytes = new byte[data.remaining()];data.get(bytes);System.out.println("收到二进制数据长度: " + bytes.length);
}// 发送二进制数据
session.getBasicRemote().sendBinary(ByteBuffer.wrap(new byte[]{0x48,0x65,0x6C,0x6C,0x6F}));
3.3.4 连接管理
// 获取所有活动会话
Set<Session> sessions = session.getOpenSessions();// 异步发送(避免阻塞)
session.getAsyncRemote().sendText("异步消息");
3.3.5 安全连接 (WSS)
// 客户端连接使用 wss 协议
new URI("wss://example.com/chat");// 服务端配置 SSL(Tomcat 示例)
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"SSLEnabled="true" scheme="https" secure="true"><SSLHostConfig certificateVerification="none"/>
</Connector>
4. HTTP 报文
4.1 请求报文
纯文本格式传输。分为三个部分:请求行(Request Line)、请求头(Request Headers)、空行(CRLF)、请求体(Request Body)。
POST /login HTTP/1.1 -- 请求行
Host: www.example.com -- 请求头
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
User-Agent: Mozilla/5.0-- 空行
username=admin&password=123 -- 请求体
4.1.1 请求行(Request Line)
请求行是报文的第一行,包含三个关键元素,以空格分隔。
- 请求方法(Request Method):指定操作类型,如 GET(获取资源)、POST(提交数据)、PUT(更新资源)或 DELETE(删除资源)。
- 请求 URI(Request URI):标识目标资源路径,例如/index.html 或完整 URL 路径。
- 协议版本(Protocol Version):指定 HTTP 版本,如 HTTP/1.1 或 HTTP/2。
例如 GET /api/data HTTP/1.1:
- GET: 请求方法
- /api/data: 请求 URI
- HTTP/1.1: 协议版本
4.1.2 请求头(Request Headers)
请求头从第二行开始,到第一个空行为止。
- Host: www.example.com:指定服务器域名(HTTP/1.1必需)。
- User-Agent: Mozilla/5.0:标识客户端类型。
- Content-Type: application/json:定义请求体的媒体类型。
- Authorization: Bearer token:用于认证。
4.1.3 请求体(Request Body)
请求体在空行之后。
主要用于 POST、PUT 等方法,提交表单、JSON 或文件。
- 什么内容由 Content-Type 决定
- application/x-www-form-urlencoded:表单数据(如 username=admin&password=123)。
- application/json:JSON 数据(如{“name”: “John”})。
- multipart/form-data:文件上传。
4.2 响应报文
响应报文结构通常包括:状态行(Status Line)、响应头(Response Headers)、空行(CRLF)、响应体(Response Body)。
HTTP/1.1 200 OK -- 状态行
Content-Type: text/html; charset=UTF-8 -- 响应头
Content-Length: 1223
Last-Modified: Wed, 21 Oct 2015 14:26:38 GMT-- 空行
<html> -- 响应体
<body>
<h1>Hello, world!</h1>
</body>
</html>
4.2.1 状态行(Status Line)
位于报文首行。
- 协议版本:HTTP/1.1
- 定义 HTTP 协议版本(HTTP/1.0、HTTP/1.1、HTTP/2 等)
- 状态码: 200
- 三位数字代码,表示请求处理结果:
- 1xx:信息类(如 101 Switching Protocols)
- 2xx:成功(如 200 OK,201 Created)
- 3xx:重定向(如 301 Moved Permanently)
- 4xx:客户端错误(如 404 Not Found)
- 5xx:服务端错误(如 500 Internal Server Error)
- 原因短语:OK
- 状态码的文本描述(可自定义但通常遵循标准)
4.2.2 响应头(Response Headers)
键值对集合,每个头占一行,描述服务器信息和响应属性。
- Content-Type : 响应体数据类型(必需)
- text/html; charset=UTF-8
- Content-Length : 响应体字节数(精确匹配实际长度)
- 1223
- Last-Modified : 资源最后修改时间(用于缓存验证)
- Wed, 21 Oct 2015 14:26:38 GMT
- Cache-Control : 缓存控制指令
- max-age=3600, public
- Set-Cookie : 服务器设置客户端 Cookie
- sessionId=abc123; Path=/; Secure
- Location : 重定向目标 URL(配合 3xx 状态码)
- https://newdomain.com/resource
- Server : 服务器软件信息
- Nginx/1.18.0
- ETag : 资源版本标识符(用于缓存验证)
- “33a64df551425fcc55e4d42a148795d9”
4.2.3 响应体(Response Body)
包含实际返回的资源数据,格式由 Content-Type 决定:
- 文本类型:HTML、CSS、JSON 等可直接阅读
- 二进制类型:如图片(image/jpeg)、压缩文件(application/zip)等
4.2.4 性能优化
- 启用压缩:Content-Encoding: gzip
- 缓存控制:Cache-Control: public, max-age=31536000
4.2.5 安全规范
- 敏感 Cookie 需加 Secure; HttpOnly 属性
- 跨域资源需设置 Access-Control-Allow-Origin
5. HTTP 请求头
5.1 通用请求头(General Headers)
适用于所有请求类型的头部。
- Cache-Control
控制缓存行为:no-cache(禁用缓存)、max-age=3600(缓存有效期) - Connection
控制连接状态:keep-alive(保持连接)、close(关闭连接) - Upgrade
请求协议升级:Upgrade: websocket(升级到 WebSocket 协议) - Via
显示请求经过的代理路径:Via: 1.1 proxy1, 1.1 proxy2
5.2 实体头(Entity Headers)
描述请求体内容的头部。
- Content-Length
请求体字节数:Content-Length: 348(精确长度必须匹配) - Content-Type 请求体数据类型:
application/json(JSON 数据)
multipart/form-data(文件上传)
application/x-www-form-urlencoded(表单数据) - Content-Encoding
请求体压缩格式:gzip、deflate、br - Content-Language
请求体语言:zh-CN、en-US
5.3 请求控制头(Request Control Headers)
控制请求处理逻辑的头部。
- Host(必需)
目标服务器域名:Host: www.example.com(HTTP/1.1强制要求) - User-Agent
客户端标识:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
- Referer
请求来源 URL:Referer: https://www.google.com/(用于流量分析)
5.4 内容协商头(Content Negotiation)
客户端声明可接受的内容类型。
- Accept
响应内容类型偏好:Accept: text/html, application/xhtml+xml;q=0.9(q 值表示权重) - Accept-Encoding
可接受的压缩格式:gzip, deflate, br - Accept-Language
语言偏好:Accept-Language: zh-CN, en-US;q=0.7 - Accept-Charset
字符集偏好:Accept-Charset: utf-8, iso-8859-1(现代浏览器通常忽略)
5.5 认证头(Authentication Headers)
身份验证相关头部。
- Authorization
身份凭证:- Basic dXNlcjpwYXNz(Base64 编码)
- Bearer eyJhbGci…(JWT 令牌)
- Proxy-Authorization
代理服务器认证:Proxy-Authorization: Basic YWxhZGRpbjp…
5.6 条件请求头(Conditional Headers)
基于资源状态的条件请求。
- If-Match
ETag 匹配检查:If-Match: “737060cd8c284d8af7ad3082f209582d” - If-None-Match
ETag 不匹配时请求:用于缓存验证(返回 304 Not Modified)4 - If-Modified-Since
时间戳检查:If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT - If-Unmodified-Since
资源未修改时操作:用于并发控制
5.7 特殊用途头(Special Purpose Headers)
特定场景使用的头部。
- Range
请求部分内容:Range: bytes=0-499(断点续传) - Origin
跨域请求来源:Origin: https://www.domain.com(CORS必需) - Cookie
客户端存储数据:Cookie: sessionId=38afes7a8; userId=john - DNT (Do Not Track)
隐私请求:DNT: 1(请求不跟踪用户行为)
6. HTTP 响应头
6.1 基础控制头
- Content-Type
指定响应体的媒体类型(MIME 类型)和字符编码,例如:Content-Type: text/html; charset=utf-8
表示返回 HTML 文档,使用 UTF-8 编码。 - Content-Length
声明响应体的字节长度,例如:Content-Length: 1024
表示响应体大小为 1KB。 - Transfer-Encoding
指定传输编码方式,常见值:- chunked:响应体分块传输(动态内容常用)
- gzip:响应体压缩传输。
6.2 缓存控制头
- Cache-Control
控制缓存行为,常用指令:- no-cache:强制向服务器验证缓存
- max-age=3600:资源有效期 1 小时
- public:允许中间代理缓存。
- Expires
设定资源过期时间(GMT 格式),例如:Expires: Wed, 21 Oct 2025 07:28:00 GMT
。 - ETag
资源版本标识符(如文件哈希值),用于缓存验证:ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
。
6.3 重定向与刷新头
-
Location
配合 3xx 状态码实现重定向,指定新 URL:Location: https://example.com/new-page
。 -
Refresh
设置页面自动刷新/跳转:Refresh: 5; url=https://example.com
表示 5 秒后跳转到指定 URL。
6.4 安全控制头
-
Content-Security-Policy (CSP)
定义内容安全策略,防止 XSS 攻击:Content-Security-Policy: default-src 'self'
表示仅允许加载同源资源。 -
Strict-Transport-Security (HSTS)
强制 HTTPS 连接:Strict-Transport-Security: max-age=31536000
表示 1 年内自动转 HTTPS。
6.5 特殊功能头
- Content-Disposition
控制文件下载行为:Content-Disposition: attachment; filename="report.pdf"
触发文件下载并指定文件名。 - Set-Cookie
服务器向客户端设置 Cookie:Set-Cookie: sessionid=38afes7a8; HttpOnly; Secure HttpOnly
禁止 JS 访问,Secure 仅限 HTTPS 传输。 - Access-Control-Allow-Origin
跨域资源共享(CORS)关键头:Access-Control-Allow-Origin: *
允许所有域访问资源。
6.6 服务器信息头
- Server
暴露服务器类型和版本:Server: nginx/1.18.0
(建议隐藏以减少攻击面)。 - X-Powered-By
显示后端技术栈(如 PHP 版本):X-Powered-By: PHP/7.4.3
。
7. Cookie, Session
- Cookie:客户端存储机制(≤4KB),通过 Set-Cookie 头创建
- Session:服务器端存储机制,通过 Session ID 标识客户端
- 区别:
- Cookie 数据存储在浏览器,Session 数据存储在服务器
- Cookie 可长期保存,Session 随会话结束失效
- Cookie 有 4KB 限制,Session 无硬性限制
8. JWT
JWT(JSON Web Token)是目前最流行的跨域认证解决方案,特别适用于分布式站点的单点登录(SSO)场景。
JWT的最大优势是服务器不再需要存储Session状态,使得服务器认证鉴权业务可以方便扩展。
特性 | Session 机制 | JWT(JSON Web Token) |
---|---|---|
存储位置 | 服务器端(内存/数据库) | 客户端(localStorage/Cookie) |
数据结构 | 会话 ID(无状态标识) | 自包含 JSON(Header.Payload.Signature) |
通信方式 | 通过 Cookie 传递 Session ID | 通过 HTTP Header 或 URL 参数传递 |
状态管理 | 有状态(服务器存储会话数据) | 无状态(所有信息在 Token 中) |
扩展性 | 集群部署需 Session 共享方案 | 天然支持分布式系统 |
- 如果Token存储在localStorage中,可能面临XSS攻击的风险(因为JavaScript可以读取)。如果存储在HttpOnly的Cookie中,则相对安全,但也要注意CSRF。
- JWT的Token体积通常比Session ID大,每次请求都会在HTTP头部中携带,增加带宽消耗。
- 一旦签发,在有效期内无法撤销。
- Token中可以直接存储用户信息(如用户ID、角色等),服务器解析Token即可获取,无需额外查询。
8.1 前端实现
8.1.1 登录获取 JWT
登录获取JWT并存进Cookie中。
async function login() {const res = await fetch('http://localhost:8080/login', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ username: 'admin', password: 'password123' }),credentials: 'include' // 必须包含凭据});if (res.ok) alert('登录成功!Cookie已设置');
}
8.1.2 访问受保护资源
自动携带Cookie中的JWT发送请求。
async function fetchData() {const res = await fetch('/protected', {credentials: 'include' // 自动发送Cookie});if (res.ok) {const data = await res.text();alert('获取数据: ' + data);} else {alert('访问失败: ' + res.status);}
}
8.2 后端实现
需要添加jsonwebtoken包,以SpringBoot进行演示。
8.2.1 登录接口
后端签发设置HttpOnly、Secure和SameSite属性以增强安全性(如防止XSS和CSRF攻击)。
@RestController
public class AuthController {@PostMapping("/login")public ResponseEntity<String> login(@RequestBody LoginRequest request, HttpServletResponse response) {// 1. 验证用户名密码if ("admin".equals(request.username()) && "password123".equals(request.password())) {// 2. 生成JWTString jwt = JwtUtil.generateToken(request.username());// 3. 创建安全CookieCookie cookie = new Cookie("jwt", jwt);cookie.setHttpOnly(true); // 防止XSS攻击cookie.setSecure(true); // 仅HTTPS传输cookie.setPath("/"); // 全局路径cookie.setMaxAge(3600); // 1小时有效期cookie.setAttribute("SameSite", "Strict"); // 防止CSRF// 4. 添加到响应头response.addCookie(cookie);return ResponseEntity.ok("登录成功");}return ResponseEntity.status(401).body("认证失败");}
}
8.2.2 受保护资源接口
@GetMapping("/protected")
public ResponseEntity<String> protectedResource(@CookieValue("jwt") String token) { // 自动从Cookie提取if (JwtUtil.validateToken(token)) {return ResponseEntity.ok("访问成功!这是受保护资源");}return ResponseEntity.status(401).body("无效令牌");
}
8.2.3 JwtUtil
public class JwtUtil {// 生成带IP绑定的JWTpublic static String generateToken(String username, String ip) {return Jwts.builder().setSubject(username).claim("ip", ip) // 绑定客户端IP.setExpiration(new Date(System.currentTimeMillis() + 3600000)).signWith(Keys.hmacShaKeyFor(SECRET.getBytes())).compact();}// 验证时检查IPpublic static boolean validateToken(String token, String clientIp) {Claims claims = Jwts.parserBuilder().setSigningKey(SECRET.getBytes()).build().parseClaimsJws(token).getBody();return claims.get("ip").equals(clientIp);}
}
8.2.4 CORS配置
@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("https://yourdomain.com").allowedMethods("*").allowCredentials(true); // 必须允许凭据}
}