前后端分离的 session 困境
传统单体架构:
用户 → nginx → tomcat/php
↓
session 存储在内存(单机无问题)前后端分离架构:
用户 → nginx → 前端服务器 (vue/react 静态资源)
↓
api 网关 → 后端服务集群 (tomcat-a, tomcat-b, tomcat-c)
↓
session 存储在各自内存(用户请求被负载到不同节点,session 丢失!)
核心问题:负载均衡后,同一用户的请求可能落到不同后端节点,导致 session 不一致。
方案一:nginx 粘性会话(sticky session)
让同一用户的请求始终落到同一后端节点。
1. 基于 ip hash(简单但粗糙)
upstream backend {
# 根据客户端 ip 的 hash 值分配后端
ip_hash;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
server {
listen 80;
server_name api.example.com;
location /api/ {
proxy_pass http://backend;
proxy_set_header host $host;
proxy_set_header x-real-ip $remote_addr;
}
}缺点:
- 用户 ip 变化(移动网络、代理)会切换节点
- 某节点故障时,该节点用户 session 丢失
- 无法动态扩容(增减节点导致 hash 重新计算)
2. 基于 cookie 的粘性会话(推荐)
使用 sticky 模块或第三方模块:
# 需要编译安装 nginx-sticky-module
upstream backend {
sticky cookie srv_id expires=1h domain=.example.com path=/;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
server {
location /api/ {
proxy_pass http://backend;
}
}原理:nginx 设置 cookie srv_id=后端标识,后续请求根据 cookie 值路由。
方案二:session 集中存储(推荐)
将 session 从应用内存剥离,存储到共享中间件。
架构图
用户请求 → nginx → 后端服务a
↓ ↓
↓ 读写 session
↓ ↓
redis/mysql(session 存储中心)
↑ ↑
↑ 读写 session
↑ ↓
后端服务b(获取同一 session)
1. spring boot + redis 配置示例
// application.yml
spring:
session:
store-type: redis
redis:
namespace: myapp:session
flush-mode: on_save
cleanup-cron: 0 */30 * * * *
redis:
host: 192.168.1.50
port: 6379
password: secret
timeout: 2000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
server:
servlet:
session:
timeout: 30m # session 过期时间2. nginx 负载均衡配置(无状态)
upstream backend {
# 无需 ip_hash,纯轮询即可
least_conn; # 最少连接数优先,更智能
server 192.168.1.10:8080 weight=5;
server 192.168.1.11:8080 weight=5;
server 192.168.1.12:8080 backup; # 备用节点
}
server {
listen 80;
server_name api.example.com;
# 跨域配置(前后端分离必需)
location /api/ {
# 处理预检请求
if ($request_method = 'options') {
add_header 'access-control-allow-origin' 'https://frontend.example.com';
add_header 'access-control-allow-methods' 'get, post, put, delete, options';
add_header 'access-control-allow-headers' 'dnt,user-agent,x-requested-with,if-modified-since,cache-control,content-type,range,authorization';
add_header 'access-control-allow-credentials' 'true';
add_header 'access-control-max-age' 1728000;
add_header 'content-type' 'text/plain; charset=utf-8';
add_header 'content-length' 0;
return 204;
}
proxy_pass http://backend;
proxy_http_version 1.1;
# 关键:传递 cookie 和 session id
proxy_set_header host $host;
proxy_set_header x-real-ip $remote_addr;
proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
proxy_set_header x-forwarded-proto $scheme;
# 确保 cookie 透传
proxy_pass_header set-cookie;
proxy_cookie_path / /;
proxy_cookie_domain backend.example.com .example.com;
}
}3. 其他 session 存储方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| redis | 速度快,支持过期,分布式友好 | 需额外维护 redis | 高并发,大规模集群 |
| mysql/postgresql | 持久化好,已有数据库复用 | 性能较低,频繁读写压力大 | 小规模,已有数据库 |
| memcached | 纯内存,极快 | 无持久化,数据易失 | 纯缓存场景,可容忍丢失 |
| jwt(无 session) | 服务端无状态,天然分布式 | token 无法主动失效,体积大 | 微服务,api 网关 |
方案三:jwt token 替代 session(无状态架构)
彻底抛弃服务端 session,改用 token 认证。
认证流程
1. 用户登录 → 后端验证 → 生成 jwt(含用户id、权限、过期时间)
↓
2. 返回 token 给前端 → 前端存储(localstorage 或 cookie)
↓
3. 后续请求 → 前端在 header 携带 authorization: bearer <token>
↓
4. nginx 透传 → 后端验签 → 无需查 session,直接获取用户信息
nginx 配置(jwt 透传)
server {
listen 80;
server_name api.example.com;
location /api/ {
proxy_pass http://backend;
# 关键:透传 authorization header
proxy_set_header authorization $http_authorization;
proxy_pass_header authorization;
# 其他标准配置
proxy_set_header host $host;
proxy_set_header x-real-ip $remote_addr;
proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
# 可选:nginx 层 jwt 验签(减轻后端压力,需 ngx_http_auth_jwt_module)
# auth_jwt "api";
# auth_jwt_key_file /etc/nginx/jwt_key.pub;
}
}jwt vs session 对比
| 特性 | session + redis | jwt |
|---|---|---|
| 服务端状态 | 有状态(存 redis) | 无状态 |
| 横向扩展 | 需 redis 集群 | 天然支持 |
| 登出/踢人 | 立即删除 session | 需黑名单机制(redis) |
| 性能 | 每次查 redis | 本地验签,更快 |
| 安全性 | 可控制,服务端掌握 | token 泄露后无法撤销(除非短过期+刷新) |
| 适用 | 传统 web,需强控制 | 微服务,移动端,api |
方案四:nginx + lua 实现共享 session(openresty)
在 nginx 层统一处理 session,后端完全无感知。
# 使用 openresty + lua-resty-session
server {
listen 80;
server_name app.example.com;
location / {
access_by_lua_block {
local session = require "resty.session".start()
-- 检查登录状态
if not session.data.user_id then
-- 未登录,重定向到登录页
return ngx.redirect("/login")
end
-- 已登录,将用户信息注入 header 传给后端
ngx.req.set_header("x-user-id", session.data.user_id)
ngx.req.set_header("x-user-role", session.data.role)
}
proxy_pass http://backend;
}
location /login {
content_by_lua_block {
-- 处理登录逻辑,写 session 到 redis
local session = require "resty.session".start()
session.data.user_id = ngx.var.arg_username
session.data.role = "admin"
session:save()
ngx.say("login success")
}
}
}优势:后端完全无 session 逻辑,专注业务;nginx 层统一认证入口。
生产环境推荐配置
完整 nginx 配置(redis session + 负载均衡)
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式(记录 session id)
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'session=$cookie_session'; # 记录 session cookie
access_log /var/log/nginx/access.log main;
# 上游后端(无状态,纯轮询)
upstream api_backend {
zone backend 64k;
least_conn;
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
keepalive 32; # 长连接,提升性能
}
# 前端静态资源(cdn 或本地)
server {
listen 80;
server_name www.example.com;
root /var/www/frontend;
location / {
try_files $uri $uri/ /index.html;
expires 1h;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 30d;
add_header cache-control "public, immutable";
}
}
# api 网关
server {
listen 80;
server_name api.example.com;
# 安全头
add_header x-frame-options "sameorigin" always;
add_header x-content-type-options "nosniff" always;
add_header x-xss-protection "1; mode=block" always;
# 跨域(生产环境限制具体域名)
map $http_origin $cors_origin {
default "";
"~^https?://(.*\.)?example\.com$" $http_origin;
"~^https?://localhost:\d+$" $http_origin;
}
location /api/ {
# cors 处理
if ($request_method = 'options') {
add_header 'access-control-allow-origin' $cors_origin;
add_header 'access-control-allow-credentials' 'true';
add_header 'access-control-allow-methods' 'get, post, put, delete, options';
add_header 'access-control-allow-headers' 'accept,authorization,cache-control,content-type,dnt,if-modified-since,keep-alive,origin,user-agent,x-requested-with';
add_header 'access-control-max-age' 1728000;
add_header 'content-type' 'text/plain; charset=utf-8';
add_header 'content-length' 0;
return 204;
}
add_header 'access-control-allow-origin' $cors_origin always;
add_header 'access-control-allow-credentials' 'true' always;
proxy_pass http://api_backend;
proxy_http_version 1.1;
# 连接优化
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 缓冲区
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
# 关键:session/cookie 透传
proxy_set_header host $host;
proxy_set_header x-real-ip $remote_addr;
proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
proxy_set_header x-forwarded-proto $scheme;
proxy_set_header x-forwarded-host $host;
proxy_set_header x-forwarded-port $server_port;
# cookie 处理
proxy_pass_header set-cookie;
proxy_cookie_path / /;
# 长连接
proxy_set_header connection "";
}
# 健康检查(需 nginx_upstream_check_module)
location /health {
access_log off;
return 200 "healthy\n";
add_header content-type text/plain;
}
}
}方案选择建议
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 中小型项目,快速上线 | nginx ip_hash | 零代码改动,快速解决 |
| 中大型项目,长期维护 | redis session | 标准方案,生态成熟 |
| 微服务/云原生架构 | jwt + api 网关 | 无状态,天然适合容器化 |
| 极高性能要求 | openresty lua session | 减少后端交互,nginx 层处理 |
核心原则:session 共享的本质是让状态外置,nginx 作为入口只需做好透明透传和负载均衡。
到此这篇关于nginx解决前后端分离架构下的session共享的几种方法的文章就介绍到这了,更多相关nginx session共享内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论