SDWebImage : NSURLErrorDomain - Code = 406 Overriding request headers in SDWebImage

本文介绍了解决iOS应用中使用SDWebImage缓存图片遇到的问题,特别是针对从服务器获取的图片出现的HTTP状态码406错误。通过修改Accept头部设置,成功解决了缓存失败的问题。

The iOS project that I am working on, has decided to use some caching mechanism to improve perfomance and save bandwidth of the user. So as per the recommendation of some blogs and popularity, we have decided to go with SDWebImage cache. After inspecting example code and trying out some sample code I thought it’ll be good to use.

But it wasn’t work with images those are retrieved from our sever. I have hardcoded public images found from the image search. There wasn’t any problem with those. I tried with different methods provided from the API. But the result is the same.

I am getting following error instead of successful response.

Error Domain=NSURLErrorDomain Code=406 “The operation couldn’t be completed. (NSURLErrorDomain error 406.)”

Following is the definition I found from the Wikipedia for HTTP status code 406.

406 Not Acceptable - The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request.

I couldn’t find any satisfiable answers from any Q/A sites. So I have decided to read the source code and identify any possible problem / solution.

After going through several classes, I found that SDWebImageDownloader class has a code related to setting the 'Accept' header value into the request.

_HTTPHeaders = [NSMutableDictionary dictionaryWithObject:@"image/webp,image/*;q=0.8" 
                                                  forKey:@"Accept"];

So above code caused me the problem. Once I changed it, caching started to work and work nicely. But that was a temporary solution, because it’s not good to change library source code. 

I was using UIImageView category provided by the library to retrieve and cache images. So it was problematic to set header values from there. Also only SDWebImageDownloader class providing the necessary API method to change the header value for public users. So at first sight I was so disappointed and thought of trying something else. But luckily SDWebImageDownloader is a singleton object. Every method is using the SDWebImageDownloader downloadImageWithURL:options:progress:completed: method to download images. So I have set a custom header value to 'Accept' header to override the default header.

[SDWebImageDownloader.sharedDownloader setValue:@"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
                             forHTTPHeaderField:@"Accept"];
[self sd_setImageWithURL:[self imageUrlForId:imageId] placeholderImage:image];


spring: application: name: jnpf-boot profiles: # 指定环境配置 dev(开发环境-默认)、test(测试环境)、preview(预生产)、prod(生产环境) active: dev servlet: multipart: #文件传输配置 max-file-size: 100MB #单个数据大小限制 max-request-size: 100MB #请求总数据大小限制 enabled: true #是否启用分段上传支持 mvc: #csrf-origins: ${config.FrontDomain} cors: # 允许跨域的域名 #allowed-origins: ${config.FrontDomain} # 允许跨域的域名正则匹配 如需全部匹配设置为 '**', 因为包含凭据的请求不允许直接写* 这个写法可以直接返回当前请求域名 allowed-origin-patterns: '**' # 允许跨域的请求方法 GET, POST, PUT, DELETE allowed-methods: "*" # 允许跨域的头部信息 Content-Type, Authorization allowed-headers: "*" options-max-age: 18000 headers: server-name: '' x-frame-options: sameorigin x-content-type-options: disabled x-xss-protection: enabled_mode_block hiddenmethod: #隐式方法过滤器 filter: enabled: true #默认开启。开启以支持:PUT,DELETE表单提交方法 jackson: #序列化和反序列化json框架 serialization: write-dates-as-timestamps: true #是否写入日期时间时间戳格式 time-zone: GMT+8 #指定日期格式化时区 main: allow-bean-definition-overriding: true #允许同名bean后者覆盖,默认:true allow-circular-references: true #允许Bean相互引用,默认:false config: # ===============静态资源目录映射================== WebAnnexFilePath: WebAnnexFile DataBackupFilePath: DataBackupFile TemporaryFilePath: TemporaryFile SystemFilePath: SystemFile TemplateFilePath: TemplateFile EmailFilePath: EmailFile DocumentFilePath: DocumentFile DocumentPreviewPath: DocumentPreview UserAvatarFilePath: UserAvatar IMContentFilePath: IMContentFile MPMaterialFilePath: MPMaterial TemplateCodePath: TemplateCode BiVisualPath: BiVisualPath # ===============功能格式限制================== MPUploadFileType: bmp,png,jpeg,jpg,gif,mp3,wma,wav,amr,mp4 WeChatUploadFileType: jpg,png,doc,docx,ppt,pptx,xls,xlsx,pdf,txt,rar,zip,csv,amr,mp4 AllowUploadImageType: jpg,gif,png,bmp,jpeg,tiff,psd,swf,svg,pcx,dxf,wmf,emf,lic,eps,tga #允许上传图片类型 AllowUploadFileType: jpg,gif,png,bmp,jpeg,doc,docx,ppt,pptx,xls,xlsx,pdf,txt,rar,zip,csv,mp3,aac #允许上传文件类型 AllowPreviewFileType: doc,docx,xls,xlsx,ppt,pptx,pdf,jpg,gif,png,bmp,jpeg,txt #允许预览文件类型 PreviewType: kkfile #文件预览方式 (1.yozo 2.kkfile)默认使用kkfile kkFileUrl: http://10.21.8.181:30090/FileServer/ #kkfile文件预览服务地址 # kkFileUrl: http://127.0.0.1:30090/FileServer/ #kkfile文件预览服务地址 # ApiDomain: http://127.0.0.1:30000 #后端域名(文档预览中使用) ApiDomain: http://10.21.8.181:30000 #后端域名(文档预览中使用) FrontDomain: http://10.21.8.181:3000 #前端域名(文档预览中使用) # FrontDomain: http://127.0.0.1:3000 #前端域名(文档预览中使用) AppDomain: http://10.21.8.181:8080 #app/h5端域名配置(文档预览中使用) # AppDomain: http://127.0.0.1:8080 #app/h5端域名配置(文档预览中使用) FlowDomain: http://10.21.8.181:31000 #流程引擎接口地址 # FlowDomain: http://127.0.0.1:31000 #流程引擎接口地址 # CodeAreasName: property #代码生成器模块命名 CodeAreasName: example #代码生成器模块命名 #===================== unipush ===================== AppPushUrl: https://8e84eea8-6922-4033-8e86-67ad7442e692.bspapp.com/unipush #===================== 多租户配置 ===================== MultiTenancy: false #是否开启 MultiTenancyUrl: http://127.0.0.1:30006/api/tenant/DbName/ #多租户项目地址 #===================== 系统及错误报告反馈相关 ===================== SoftName: jnpf-java-boot #项目名 SoftFullName: JNPF快速开发平台 #项目全名 SoftVersion: v5.2.0 #版本号 RecordLog: true #系统日志启用 ErrorReport: false #软件错误报告 ErrorReportTo: surrpot@yinmaisoft.com #软件错误报告接收者 IgexinEnabled: true #推送启动 #===================== APP ===================== AppVersion: v5.2.0 #APP版本号 IgexinAppid: HLFY9T2d1z7MySY8hwGwh4 #APPID:应用的唯一标识 IgexinAppkey: 6Uiduugq648YDChhCjAt59 #APPKEY:公匙(相当于账号) IgexinMastersecret: pEyQm156SJ9iS7PbyjLCZ6 #Mastersecret:私匙(相当于密码) AppUpdateContent: ; #APP更新内容 #===================== 永中office在线预览配置 ===================== YozoDomain: //dcsapi.com/ #永中api域名 YozoDomainKey: 57462250284462899305150 #域名key YozoCloudDomain: //dmc.yozocloud.cn #云预览 YozoAppId: yozoAgR41jgC0062 #appid YozoAppKey: fc3134a9ba8bc6f4c69d635f9adf #app秘钥 YozoEditDomain: //eic.yozocloud.cn #云编辑 #===================== 系统功能配置 ===================== EnableLogicDelete: false #是否开启逻辑删除 CodeCertificateTimeout: 180 # 秒 check-file-pdf: false #检查上传的PDF文件安全 security: # AES加密秘钥 security-key: EY8WePvjM5GGwQzn # 是否开启接口鉴权 enable-pre-auth: true # 接口加密 enable-rest-encrypt: true # 事件配置 event: # 默认时间发布、监听渠道: redis, mq event-publish-type: redis # Redis监听模式:current, all redis-publish-type: current # 接口放行地址 与GatewayWhite中的默认URL合并 gateway: # 禁止访问接口 block-url: ## 配置示例 #- /api/message/Notice #- /api/permission/Users/* # 不验证Token, 放行接口(默认记录日志) white-url: # # 配置示例 #- /api/message/Notice #- /api/permission/Users/* # 放行接口(不记录日志) exclude-url: # # 配置示例 #- /api/message/Notice #- /api/permission/Users/* # 入站IP(禁止配置以外的IP访问block-url配置的接口) white-ip: #- 192.168.0.10 #- 192.168.0.20 # 日志配置 logging: config: classpath:logback-spring.xml level: #自定义第三方包名日志等级 # 解除注释后Druid连接池打印SQL语句 druid.sql.Statement: debug # druid.sql.DataSource: debug # druid.sql.Connection: debug # druid.sql.ResultSet: debug log: level: # 等级 TRACE,DEBUG,INFO,WARN,ERROR(不区分大小写) root: info path: log/${spring.application.name} mybatis-plus: configuration: map-underscore-to-camel-case: true # 下划线自动转驼峰
最新发布
09-28
server: port: 8802 # tomcat: # uriEncoding: "UTF-8" # remoteip: # protocol-header-https-value: "https" # remote-ip-header: "RemoteIpValve" # protocol-header: "X-Forwarded-Proto" servlet: context-path: /gateway cors: allowedOrigin: "*" management: endpoints: web: exposure: include: "*" spring: data: redis: client-type: lettuce # 使用响应式客户端 config: import: - "nacos:uap-gateway-test.properties?group=latest&server-addr=uap.qctc.com:8800" - "nacos:common-config.properties?group=latest&server-addr=uap.qctc.com:8800" application: name: uap-gateway main: web-application-type: reactive allow-bean-definition-overriding: true profiles: active: test # group: # latest: [ test ] # 使用数组形式定义包含的profile # 可添加其他组: # production: [prod, metrics] resources: add-mappings: true cloud: config: profile: dev discovery: enabled: true service-id: zhny-config bus: enabled: false trace: enabled: false nacos: # 增加全局日志禁用参数(关键修复) bootstrap: log: enabled: false # 关闭Nacos启动时的日志系统初始化 logging: config: false # 禁用Nacos内部日志配置 naming: false remote: false default: config: enabled: false discovery: server-addr: uap.qctc.com:8801 #uap.qctc.com:8900 11.14.30.16:8848 group: latest config: server-addr: uap.qctc.com:8801 file-extension: properties namespace: public group: latest name: uap-gateway # Data ID prefix: uap-gateway # shared-configs: # - data-id: common-config.properties # group: latest # refresh: true import-check: enabled: false # 禁用导入检查(可选) gateway: # 全局连接池与超时配置 httpclient: retry: false # 全局关闭重试 # 允许特殊字符 relaxed-cookies: true pool: max-connections: 2000 # 最大总连接数 [2,5](@ref) max-idle-time: 50000ms # 连接空闲超时 connect-timeout: 50000 # 连接超时(毫秒)[2](@ref) response-timeout: 50000 # 响应超时(毫秒)[2](@ref) protool: h2c keepalive: true # 全局跨域配置(按需启用) globalcors: add-to-simple-url-handler-mapping: true cors-configurations: '[/**]': allowed-origins: "*" allow-credentials: false # 保持凭证携带 allowed-methods: "GET,POST" allowedMethods: "GET,POST" allowed-headers: "*" max-age: 3600 # 预检请求缓存时间 # 全局默认过滤器:移除 /api 前缀 [1,4](@ref) default-filters: - RewritePath=/api/(?<segment>.*), /$\{segment} discovery: locator: enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由 routes: - id: gateway_self_api uri: no://op # 特殊标记,表示不转发 predicates: - Path=/gateway/api/isLicenseOverdue filters: - SetPath=/ # 内置过滤器(仅作占位符) # - id: gateway_prefix_route # uri: lb://uap-base # predicates: # - Path=/gateway/api/uap/** # filters: # - StripPrefix=2 # 移除 /gateway/api,保留 /uap/... # 1. uap-base 服务 - id: base uri: lb://uap-base predicates: - Path=/gateway/api/uap/** filters: - StripPrefix=2 - id: customer uri: lb://uap-base predicates: - Path=/gateway/api/customer/** filters: - StripPrefix=2 # 2. zhny-publish-test 服务 - id: market uri: lb://zhny-publish-test predicates: - Path=/gateway/api/market/** filters: - StripPrefix=2 - id: cmsservice uri: lb://zhny-publish-test predicates: - Path=/gateway/api/cmsservice/** filters: - StripPrefix=2 - id: sns uri: lb://zhny-publish-test predicates: - Path=/gateway/api/sns/** filters: - StripPrefix=2 - id: trade uri: lb://zhny-publish-test predicates: - Path=/gateway/api/trade/** filters: - StripPrefix=2 - id: settle uri: lb://zhny-publish-test predicates: - Path=/gateway/api/settle/** filters: - StripPrefix=2 - id: vpp uri: lb://zhny-publish-test predicates: - Path=/gateway/api/vpp/** filters: - StripPrefix=2 - id: nbxy uri: lb://zhny-publish-test predicates: - Path=/gateway/api/nbxy/** filters: - StripPrefix=2 - id: pla uri: lb://zhny-publish-test predicates: - Path=/gateway/api/pla/** filters: - StripPrefix=2 # 3. 其他独立服务 - id: analysis # zhny-esf uri: lb://zhny-publish-test predicates: - Path=/gateway/api/analysis/** filters: - StripPrefix=2 - id: dta # zhny-dta uri: lb://zhny-dta predicates: - Path=/gateway/api/dta/** filters: - StripPrefix=2 - id: crd # zhny-crd uri: lb://zhny-crd predicates: - Path=/gateway/api/crd/** filters: - StripPrefix=2 - id: hnsd # zhny-hnsd uri: lb://zhny-hnsd predicates: - Path=/gateway/api/hnsd/** filters: - StripPrefix=2 - id: grc # zhny-grc uri: lb://zhny-grc predicates: - Path=/gateway/api/grc/** filters: - StripPrefix=2 - id: ecarbon # zhny-ecarbon uri: lb://zhny-ecarbon predicates: - Path=/gateway/api/ecarbon/** filters: - StripPrefix=2 - id: rpt # zhny-rpt uri: lb://zhny-rpt predicates: - Path=/gateway/api/rpt/** filters: - StripPrefix=2 - id: gdt # zhny-gdt uri: lb://zhny-gdt predicates: - Path=/gateway/api/gdt/** filters: - StripPrefix=2 - id: ems # zhny-ems uri: lb://zhny-ems predicates: - Path=/gateway/api/ems/** filters: - StripPrefix=2 - id: ecm # zhny-ecm uri: lb://zhny-ecm predicates: - Path=/gateway/api/ecm/** filters: - StripPrefix=2 - id: hbvpp # zhny-hbvpp uri: lb://zhny-hbvpp predicates: - Path=/gateway/api/hbvpp/** filters: - StripPrefix=2 - id: gcd # zhny-gcd uri: lb://zhny-publish-test predicates: - Path=/gateway/api/gcd/** filters: - StripPrefix=2 - id: hbdkclient # zhny-hbdkclient uri: lb://zhny-hbdkclient predicates: - Path=/gateway/api/hbdkclient/** filters: - StripPrefix=2 - id: fhjhjy # zhny-fhjhjy uri: lb://zhny-fhjhjy predicates: - Path=/gateway/api/fhjhjy/** filters: - StripPrefix=2 - id: sdpxvpp # sd-sdpxvpp uri: lb://sd-sdpxvpp predicates: - Path=/gateway/api/sdpxvpp/** filters: - StripPrefix=2 - id: dts # zhny-dts uri: lb://zhny-dts predicates: - Path=/gateway/api/dts/** filters: - StripPrefix=2 - id: rec # zhny-rec uri: lb://zhny-rec predicates: - Path=/gateway/api/rec/** filters: - StripPrefix=2 - id: ptc # zhny-ptc uri: lb://zhny-ptc predicates: - Path=/gateway/api/ptc/** filters: - StripPrefix=2 - id: lmp # zhny-lmp uri: lb://zhny-publish-test predicates: - Path=/gateway/api/lmp/** filters: - StripPrefix=2 eureka: client: fetch-registry: true # 必须显式启用 register-with-eureka: true # 必须显式启用 healthcheck: enabled: false serviceUrl: defaultZone: http://qctczhny:QCTCzhny6608!@uap.qctc.com:8800/eureka instance: metadata-map: management: context-path: ${server.servlet.context-path}/actuator prefer-ip-address: true instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${spring.application.instance_id:${server.port}} lease-renewal-interval-in-seconds: 2 #续约间隔 lease-expiration-duration-in-seconds: 4 #租约寿命 #status-page-url: http://${spring.cloud.client.ip-address}:${server.port}/api/doc.html health-check-url-path: ${server.servlet.context-path}/actuator/health #swagger: # enabled: true # name: QCTC统一开发平台 #api名称 # description: QCTC统一开发平台接口文档说明 #api描述 # termsOfServiceUrl: http://uap.qctc.com:8802${server.servlet.context-path} # developer: QCTC #api开发 # version: 3.0.0 #api开发 # basic: # enable: true # username: qctc # password: qctcadmin6608 feign: # 启用 Resilience4j circuitbreaker: enabled: true group: enabled: true httpclient: enabled: false okhttp: enabled: true compression: request: enabled: true mime-types: text/html,text/xml,text/plain,application/xml,application/json response: enabled: true # 定义客户端超时 client: config: default: # 全局默认配置 connectTimeout: 5000 # 连接超时(ms) readTimeout: 10000 # 读取超时(ms) # 绑定熔断器和超时器 resilience4jCircuitBreakerName: default resilience4jTimelimiterName: default ####超时配置#### resilience4j: circuitbreaker: instances: default: # 默认熔断器配置(作用于所有 Feign 调用) failureRateThreshold: 50 # 失败率阈值% minimumNumberOfCalls: 10 # 最小调用次数 slidingWindowType: COUNT_BASED slidingWindowSize: 10 # 统计窗口大小 waitDurationInOpenState: 10s # 熔断持续时间 timelimiter: instances: default: timeoutDuration: 5s # 单次调用超时时间 # 日志 logging: file: name: /usr/qctcapps/gateway/data/${spring.application.name}/debug.log #/usr/qctcapps/publish_test pattern: file: "%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID}){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx" level: root: info com.qctc.uap: info # com.alibaba.cloud.nacos.client.NacosPropertySourceBuilder: DEBUG # org.springframework.core.env.PropertySourcesPropertyResolver: DEBUG # org.springframework.cloud.gateway.filter: TRACE # reactor.netty.http.client: DEBUG 配置有问题吗
09-02
<think> 我们面临的核心问题是:通过Nginx代理的POST登录请求返回403 Forbidden,而直接访问后端服务则成功(200 OK)。结合提供的Nginx配置、请求头和Spring Cloud Gateway日志,我们需要从Nginx配置和Spring Cloud Gateway配置两方面进行分析。 ### 一、Nginx配置分析(关键问题) 在用户提供的Nginx配置中,与`/gateway`相关的配置如下: ```nginx location /gateway { client_max_body_size 20m; client_body_buffer_size 128k; proxy_pass http://192.168.1.95:10002; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-Port $remote_port; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; #必须配这个转发 } ``` #### 问题1:错误的`X-Forwarded-Proto`设置 - 当前设置:`proxy_set_header X-Forwarded-Proto https;` - 问题:实际前端访问使用的是HTTP(非HTTPS),但这里强制设置为`https`。 - 后果:后端服务(Spring Cloud Gateway)收到`X-Forwarded-Proto: https`,会认为请求是HTTPS,导致后续处理(如重定向、CORS检查)产生偏差。 #### 问题2:缺少`Origin`头处理 - 从日志中看到,Spring Cloud Gateway进行了CORS检查: ```log o.s.w.c.reactive.DefaultCorsProcessor : Reject: origin is malformed ``` - 原因:Nginx默认不会转发`Origin`头(除非显式设置),而后端需要此头进行CORS验证。 #### 问题3:Cookie解析失败 - 日志显示: ```log i.n.h.c.http.cookie.ServerCookieDecoder : Skipping cookie because name 'retype_30000/' contains invalid char '/' ``` - 根本原因:Cookie名称中包含非法字符`/`(如`retype_30000/=`),导致整个Cookie被丢弃。 ### 二、Spring Cloud Gateway日志分析 关键日志片段: ```log 2025-09-01T00:23:07.264+08:00 TRACE ... o.s.c.g.h.p.PathRoutePredicateFactory : Pattern "/gateway/api/uap/**" matches against value "/gateway/api/uap/login/v1/login" 2025-09-01T00:23:07.264+08:00 DEBUG ... o.s.c.g.h.RoutePredicateHandlerMapping : Route matched: base ... [其他路由匹配日志] ... 2025-09-01T00:23:07.265+08:00 DEBUG ... o.s.w.c.reactive.DefaultCorsProcessor : Reject: origin is malformed 2025-09-01T00:23:07.265+08:00 DEBUG ... o.s.w.s.adapter.HttpWebHandlerAdapter : [d2fa72ca-9] Completed 403 FORBIDDEN ``` #### 问题1:CORS校验失败 - 日志明确记录:`Reject: origin is malformed` - 可能的原因: - 请求中没有`Origin`头(Nginx未转发) - `Origin`头格式不正确(如包含端口但未在CORS配置中允许) #### 问题2:路由匹配成功但被CORS拦截 - 路由正确匹配到`base`(指向`uap-base`服务),说明路由配置无问题。 - 403发生在CORS检查阶段,证明问题在跨域配置。 ### 三、解决方案 #### 步骤1:修正Nginx配置 ```nginx location /gateway { client_max_body_size 20m; client_body_buffer_size 128k; proxy_pass http://192.168.1.95:10002; # 关键修正1:动态设置X-Forwarded-Proto(根据实际协议) proxy_set_header X-Forwarded-Proto $scheme; # 关键修正2:显式传递Origin头(若存在) proxy_set_header Origin $http_origin; # 保留原有其他头设置 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-Port $remote_port; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 新增:处理Cookie非法字符问题(避免Nginx丢弃Cookie) # 方案:重写Cookie,移除非法字符(示例移除名称中的斜杠) set $clean_cookie $http_cookie; if ($http_cookie ~* "(.*)(retype_30000/|abc666_30000/|pk_30000/)(.*)") { set $clean_cookie $1$3; } proxy_set_header Cookie $clean_cookie; } ``` #### 步骤2:调整Spring Cloud Gateway的CORS配置 在网关配置(如`application.yml`)中添加全局CORS配置: ```yaml spring: cloud: gateway: globalcors: cors-configurations: '[/**]': allowedOrigins: - "http://sz3_jdk17.qctc.com:30000" - "http://sz4_jdk17.qctc.com:30000" # 按需添加其他前端地址 allowedMethods: - GET - POST - PUT - DELETE - OPTIONS allowCredentials: true allowedHeaders: "*" ``` #### 步骤3:验证修复 1. **检查请求头**:确保Nginx转发后,后端收到的请求包含: - `X-Forwarded-Proto: http`(与前端实际协议一致) - `Origin: http://sz3_jdk17.qctc.com:30000` - 有效的Cookie(无非法字符) 2. **测试命令**: ```bash # 模拟前端请求(注意指定Origin和Cookie) curl -v -X POST 'http://sz3_jdk17.qctc.com:30000/gateway/api/uap/login/v1/login' \ -H "Origin: http://sz3_jdk17.qctc.com:30000" \ -H "Cookie: retype_30000=SM2; abc666_30000=some_value; pk_30000=some_value" \ -H "Content-Type: application/json" \ -d '{"username":"test", "password":"test"}' ``` ### 四、根本原因总结 | 层级 | 问题 | 导致后果 | |---------------------|-----------------------------------------------|------------------------------| | **Nginx** | 硬编码`X-Forwarded-Proto: https` | 后端协议判断错误 | | | 未转发`Origin`头 | CORS检查失败 | | | Cookie名称含非法字符`/` | 会话丢失 | | **Spring Gateway** | 未显式配置CORS允许`http://sz3_jdk17.qctc.com:30000` | 拒绝跨域请求 | §§相关问题§§ 1. Nginx中如何处理Cookie包含特殊字符的问题? 2. Spring Cloud Gateway如何动态配置CORS允许的域名? 3. 如何验证`X-Forwarded-Proto`设置是否正确影响后端服务? 4. 除了CORS,还有哪些原因会导致POST请求返回403? </think> ### 分析Nginx与Spring Cloud Gateway配置解决403 Forbidden问题 #### 一、Nginx配置核心问题 1. **Cookie非法字符问题** ```log Skipping cookie because name 'retype_30000/' contains invalid char '/' ``` - 问题:Cookie名称包含非法字符`/`导致Nginx丢弃所有Cookie - 影响:会话验证失败触发403 - 修复方案: ```nginx # 在location /gateway 中添加Cookie重写 set $clean_cookie $http_cookie; if ($http_cookie ~* "(.*)(retype_30000/|abc666_30000/|pk_30000/)(.*)") { set $clean_cookie $1$3; } proxy_set_header Cookie $clean_cookie; ``` 2. **CORS配置缺失** - 问题:Nginx未处理`Origin`头且未返回CORS响应头 - 证据:后端日志`Reject: origin is malformed` - 修复方案: ```nginx # 在location /gateway 中添加 add_header 'Access-Control-Allow-Origin' "$http_origin" always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' '*'; ``` 3. **代理协议头错误** ```nginx proxy_set_header X-Forwarded-Proto https; # 强制HTTPS ``` - 问题:前端实际使用HTTP(`http://sz3_jdk17.qctc.com:30000`),但强制设为HTTPS - 修正方案: ```nginx proxy_set_header X-Forwarded-Proto $scheme; # 动态匹配协议 ``` #### 二、Spring Cloud Gateway问题 1. **CORS校验失败** ```log o.s.w.c.reactive.DefaultCorsProcessor : Reject: origin is malformed ``` - 根本原因:网关未收到有效的`Origin`头(被Nginx过滤) - 解决方案:显式配置允许源 ```yaml # application.yml spring: cloud: gateway: globalcors: cors-configurations: '[/**]': allowed-origins: - "http://sz3_jdk17.qctc.com:30000" allow-credentials: true ``` 2. **安全头冲突** - 现象:Nginx返回`X-Frame-Options: DENY`与网关的`SAMEORIGIN`冲突 - 解决方案:统一策略(在Nginx中覆盖) ```nginx proxy_hide_header X-Frame-Options; add_header X-Frame-Options "SAMEORIGIN"; ``` #### 三、完整修复流程 1. **修改Nginx配置** (/etc/nginx/conf.d/sz3.conf) ```nginx location /gateway { client_max_body_size 20m; client_body_buffer_size 128k; proxy_pass http://192.168.1.95:10002; # 修复Cookie set $clean_cookie $http_cookie; if ($http_cookie ~* "(.*)(retype_30000/|abc666_30000/|pk_30000/)(.*)") { set $clean_cookie $1$3; } proxy_set_header Cookie $clean_cookie; # 修复CORS add_header 'Access-Control-Allow-Origin' "$http_origin" always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; # 修复协议头 proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host:$server_port; # 统一安全头 proxy_hide_header X-Frame-Options; add_header X-Frame-Options "SAMEORIGIN"; } ``` 2. **重启Nginx服务** ```bash sudo nginx -t && sudo systemctl restart nginx ``` 3. **验证修复** ```bash curl -X POST 'http://sz3_jdk17.qctc.com:30000/gateway/api/uap/login/v1/login' \ -H "Origin: http://sz3_jdk17.qctc.com:30000" \ -H "Cookie: retype_30000=SM2" \ -H "Content-Type: application/json" \ -d '{"username":"test"}' ``` #### 四、技术原理图 ```mermaid graph LR A[浏览器] -->|1. POST /login| B(Nginx) B -->|2. 修复Cookie/CORS| C[Spring Gateway] C -->|3. 认证请求| D[微服务] D -->|4. 200响应| C C -->|5. 添加CORS头| B B -->|6. 返回响应| A ``` #### 五、根本原因总结 | 层级 | 问题 | 导致后果 | |---------------------|-------------------------------|--------------------------| | **Nginx** | Cookie含非法字符`/` | 会话丢失 → 403 | | | 未转发/处理`Origin`头 | CORS校验失败 → 403 | | | 强制`X-Forwarded-Proto: https`| 协议不匹配 → 安全拒绝 | | **Spring Gateway** | 未配置显式CORS允许源 | 拒绝跨域请求 → 403 |
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值