到底怎么使用nginx配置一个前后端分离的项目

1.背景

最近在做微服务架构,使用了Spring的Spring Authorization Server(SAS)组件做基于Oauth2.0的授权服务器。SAS是基于spring security封装的,里面引导用户去登录页登录,这个页面框架默认提供的。但是这样的页面无论是样式,还是可扩展性都很不方便,考虑到这一点就用vue3+vite专门做了一个前端工程。但这样就会产生跨域的问题,容易导致整个oauth2的授权链路中断。所以打算使用nginx来屏蔽整个跨域问题

2.方案

按照如上图的方式,对浏览器来说:从头到尾就只跟 yourdomain.com 这一个域名打交道

ruby 复制代码
浏览器 只访问一个地址:https://yourdomain.com 
页面请求: https://yourdomain.com/index.html (前端页面) 
          https://yourdomain.com/css/xxx.css 
          https://yourdomain.com/js/xxx.js 
接口请求: https://yourdomain.com/api/login

nginx 将前端和后端都伪装在同一个域名下,在内部根据url进行分流。

3.授权服务器的nginx配置方式

由于将来必须要使用oauth2.0,而oauth2规范中 要求授权码、令牌传输、回调地址,必须在 TLS/HTTPS 下传输;所以本次配置nginx支持https。

4.准备docker-compose

本次nginx我是用docker部署的方式:

首先 在 D:\docker\nginx 目录下新建一个文件叫 docker-compose.yml

docker-compose.yml 编排和启动一个支持 HTTPS 的 Nginx 容器。

yml 复制代码
version: '3.8'
services:
  nginx-https:
    image: nginx:latest
    container_name: nginx-https-lab
    ports:
      - "443:443"  # 映射 HTTPS 端口
      - "80:80"    # 映射 HTTP 端口
    volumes:
      # 将你的 D 盘目录映射到容器内部
      - ./conf.d:/etc/nginx/conf.d
      - ./html:/usr/share/nginx/html
      - ./certs:/etc/nginx/certs:ro # 证书目录,只读
    extra_hosts:
      - "host.docker.internal:host-gateway"
    restart: always

关于这个yml我有几点说明:

第一个就是目录挂载:

  • ./conf.d:/etc/nginx/conf.d 将本地的./conf.d 挂载到容器的/etc/nginx/conf.d目录。

这样我只要在本地的目录下创建一个nginx的配置文件,然后让nginx重新加载就好了

  • ./html:/usr/share/nginx/html

这个挂载目录目前还用不到,但是将来前端build之后的dist内的资源就可以放在这个路径下,

  • ./certs:/etc/nginx/certs:ro # 证书目录,只读

为了实现https 需要把ssl证书放进对应的目录下,让nginx能加载

4.1 制作nginx配置文件

进入我的 ./conf.d 目录,创建一个新的配置文件,例如 sso.conf。

conf 复制代码
server {
listen 443 ssl http2;
# 1. 这里必须改成本地解析的域名
server_name sso.ilab-x.com;

    # 2. 确保证书文件名和你 D:\docker\nginx\certs 目录下的完全一致
    ssl_certificate     /etc/nginx/certs/sso.ilab-x.com+2.pem;
    ssl_certificate_key /etc/nginx/certs/sso.ilab-x.com+2-key.pem;

    #location / {
    #    proxy_pass http://host.docker.internal:5173;
    #    proxy_http_version 1.1;
    #    proxy_set_header Upgrade $http_upgrade;
    #   proxy_set_header Connection "upgrade";
    #}

	# 先模拟下所有请求代理到9000服务,这个9000是授权微服务的端口
    location / {
        proxy_pass http://host.docker.internal:9000;
    }
}

注意需要配置一下本地域名映射 C:\Windows\System32\drivers\etc\hosts

text 复制代码
127.0.0.1 sso.ilab-x.com

配置完毕后,重启下nginx就可以。

bash 复制代码
docker restart nginx-https-lab

但是这么配置会发现后台服务在重定向的时候,重定向地址有问题:重定向的地址是:host.docker.internal:9000 协议和URI都有点问题。

代理到后台需要加入一些header

conf 复制代码
# 先模拟下所有请求代理到9000服务,这个9000是授权微服务的端口
    location / {
        proxy_pass http://host.docker.internal:9000;
		# ✅ 加上这三行,后端就能拿到正确的域名和 HTTPS 了
		proxy_set_header Host $host;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-Proto $scheme;
    }

上面配置之后,host可以正常解析成sso.ilab-x.com,但是schema还不行。

Spring Boot 默认并不知道自己躲在 Nginx 后面,它会根据当前收到的请求协议(HTTP)来构建重定向地址。虽然在 Nginx 配了 X-Forwarded-Proto $scheme,但 Spring Boot 默认是不理会这些 Header 的。

在授权微服务的 application.yml 中添加:

YAML 复制代码
server:
  forward-headers-strategy: native # 关键:让 Spring 识别并支持 X-Forwarded-* 头部

这个配置会被Tomcat给使用,如果配了 native:SAS 识别到 Nginx 传来的 X-Forwarded-Proto: https,它会返回:Location: sso.ilab-x.com/login

native 这个核心原理解释

sprinboot默认使用的tomcat,TomcatServletWebServerFactory这个类是专门用来配置tomcat容器的。其中有个**TomcatWebServerFactoryCustomizer**定制化器

里面专门有个定制化RemoteIpValve这个类,它是Tomcat官方提供的。

RemoteIpValve 它是 Tomcat 提供的一个非常实用的内置组件,核心作用是:当 Tomcat 部署在反向代理(如 Nginx、Apache)或负载均衡器后面时,帮助 Tomcat 获取到访问用户的真实客户端 IP 地址。

在典型的部署架构中,请求的链路通常是这样的:
客户端 (真实IP: 203.0.113.5)负载均衡器/Nginx (IP: 192.168.1.10)Tomcat 服务器

当请求最终到达 Tomcat 时,Tomcat 看到的直接连接方其实是负载均衡器。因此,如果你在 Java 代码里直接调用 request.getRemoteAddr(),获取到的往往是负载均衡器的内网 IP(例如 192.168.1.10),而不是用户的真实 IP。

为了解决这个问题,Nginx 等代理服务器通常会在转发请求时,在 HTTP 请求头中带上用户的真实 IP(最常用的头是 X-Forwarded-For)。RemoteIpValve 的工作就是拦截这些请求,自动读取并解析这些 HTTP 头,把真实 IP 替换回请求对象中。这样一来,你的业务代码完全不需要修改,照常使用 request.getRemoteAddr() 就能直接拿到真实的客户端 IP 了。

它的核心方法是invoke,构建服务器的时候会被调用。

里面我截取了一段代码RemoteIpValve的invoke方法

java 复制代码
private String protocolHeader = "X-Forwarded-Proto";
if (this.protocolHeader != null) {
    hostHeaderValue = request.getHeader(this.protocolHeader);
    if (hostHeaderValue != null) {
        if (this.isForwardedProtoHeaderValueSecure(hostHeaderValue)) {
            request.setSecure(true);
            request.getCoyoteRequest().scheme().setString("https");
            this.setPorts(request, this.httpsServerPort);
        } else {
            request.setSecure(false);
            request.getCoyoteRequest().scheme().setString("http");
            this.setPorts(request, this.httpServerPort);
        }
    }
}

上面的代码是判断nginx或者上一层代理是否传递过来RemoteIpValve头,如果传递过来,判断是不是https,如果是https那么会给底层tomcat的request设置为secure=true 并且scheme = https。这个request类是tomcat提供的,来自 org.apache.catalina.connector包。同时继承自HttpServletRequest

java 复制代码
public class Request implements HttpServletRequest {
    private static final String HTTP_UPGRADE_HEADER_NAME = "upgrade";
    private static final Log log = LogFactory.getLog(Request.class);
    protected org.apache.coyote.Request coyoteRequest;
    ...

在 RemoteIpValve 处理完后,它修改了 org.apache.catalina.connector.Request 里的 coyoteRequest 对象。

当 Spring Security 调用 request.getScheme() 时,底层其实是在读取 Tomcat 内存里那个被修改过的 字节数组。

但是想让上面的RemoteIpValve生效,它是一个阀门。必须在springboot配置中开启native,这样服务器启动的时候才会去加载RemoteIpValve。底层就是判断用户配置的forwardHeadersStrategy是否等于native。

结论:

5.搞定证书

由于本地开发,我们没法让权威 CA 给我们签发证书。但是我们可以使用mkcert,它会自动把生成的"自签 CA"加入到系统的信任列表里,这样你的浏览器地址栏就不会出现那个烦人的红色感叹号了

可以使用Chocolatey工具来安装mkcert,安装工具完毕之后执行

复制代码
mkcert -install
mkcert sso.ilab-x.com localhost 127.0.0.1

执行完命令,会在目录下生成两个文件

其中sso.ilab-x.com+2.pem就是发给这个域名的证书,里面含有公钥。另一个带key的是私钥。

当浏览器访问这个域名的时候,服务器要出示这个证书。

核心公式:证书 = 你的公钥 + 你的域名 + 上级的签名

下面一段话助你理解mkckert:

当你运行 mkcert sso.ilab-x.com localhost 127.0.0.1 时,后台其实发生了以下三件事:

生成:mkcert 在后台帮你悄悄生成了一对密钥(RSA)。

提取:它把其中的公钥拿出来。

签名:它用它那个已经安装在系统里的"根证书私钥",给这个公钥签了名,最后吐给你一个 sso.ilab-x.com+2.pem。

相关推荐
小小仙。1 小时前
IT自学第四十三天(微服务登录认证)
运维·微服务·架构
2301_780789661 小时前
2025年服务器漏洞生存指南:从应急响应到长效免疫的实战框架
网络·安全·web安全·架构·ddos
东北甜妹1 小时前
K8s RBAC 和持久化存储
云原生·容器·kubernetes
IT大白鼠2 小时前
云原生AI工具链:架构、组件、应用与发展趋势
人工智能·云原生·架构
正在走向自律2 小时前
KES数据库表空间自动创建特性详解:从传统运维痛点到云原生存储落地
运维·云原生·国产数据库·kes
一个天蝎座 白勺 程序猿2 小时前
KES表空间管理的智能化演进:从手动目录创建到云原生弹性存储的自动化之路
运维·云原生·自动化·kingbasees
霑潇雨2 小时前
原生 Zookeeper 实现分布式锁案例
java·分布式·zookeeper·云原生·maven
刀法如飞2 小时前
Ontology本体论是什么?Palantir 技术原理介绍
大数据·人工智能·架构