我创建了一个端点”/api/chat”,当调用时,它会向OpenAI API发送另一个请求(已付费且API密钥有效,所有这些都已准备好)。在Postman中测试这个端点时,我首先请求我的/auth/login端点以获取一个有效的JWT登录令牌,将其复制到/api/chat调用的Authorization头中,然后发送请求(登录认证令牌100%有效,并且对所有其他端点也同样有效)。
当请求/api/chat时,Postman处理请求大约需要1.5到2秒。在第一秒内,我的Intellij运行控制台记录了正确的日志,表明请求已被发送。在剩余的时间里,它接着记录了从OpenAI API模型中预期的精确响应。你可能会认为这意味着一切都执行得很好,看起来确实如此,但随后Postman(如果你在网站上尝试,也会如此)返回403而不是200 OK。这是一个问题,因为尽管我可以在IDE控制台中物理地看到正确的响应被记录,没有任何错误,但403意味着我无法提取任何数据来使用,并且网站无法接收到它,并将其视为一个完全失败的请求。
我“修复”这个问题的唯一方法是在我的SecurityConfig文件中将/api/chat端点设为公开,这使得它以相同的方式执行并给出相同的结果,但如预期的那样返回200 OK。然而,我当然不能这样做,因为这意味着人们可以公开访问一个对我来说每次请求都需要付费的端点。
我花了很多时间试图解决这个问题,包括谷歌搜索,向GPT提问 – 什么都没有。验证了我所有的JWT认证内容,尝试了一些在整个过程中保留认证令牌的方法,都没有奏效。CORS和CSRF问题,看起来也不是这些。我只能认为这是Spring Security的问题,因为在SecurityConfig中将端点添加到.permitAll()列表中(使其公开)是唯一让它如预期那样返回200 OK的方法。这非常令人沮丧,因为它执行和记录的正是它应该的,但不知为何返回403。
这是我第一个使用Spring的项目,所以如果这是个小问题,请帮帮我。提前感谢。API密钥已设置在我的环境变量中,尽管我包含了一些错误日志,但从未记录过错误,只有我从AI模型中期望的正确响应,然后如我所述返回403。我会包含相关代码片段,但如果你认为问题出在另一个文件或这些文件的另一部分,请告诉我,我会分享我能分享的。不太重要的是,我认为我使用的是React作为前端。
:
安全配置:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf() .disable() .authorizeHttpRequests(authorize -> authorize .requestMatchers("/auth/**", "/", "/index.html", "/manifest.json", "/static/**", "/*.js", "/*.jsx", "/*.css", "/home", "/log-in", "/sign-up") .permitAll() .requestMatchers("/auth/signup", "/auth/login").anonymous() .anyRequest() .authenticated() ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .authenticationProvider(authenticationProvider) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build();
控制器:
@PostMapping public Mono<ResponseEntity<String>> getChatCompletion(@RequestBody ChatRequest chatRequest, @RequestHeader HttpHeaders headers) { return openAiService.getChatCompletion(chatRequest.getInput()) .map(response -> { logger.info("Response: {}", response); // 记录响应 return ResponseEntity.ok(response); }) .defaultIfEmpty(ResponseEntity.noContent().build()); } public static class ChatRequest { private String input; public String getInput() { return input; } public void setInput(String input) { this.input = input; } }
服务:
public Mono<String> getChatCompletion(String userInput) { String requestBody = String.format(""" { "model": "gpt-3.5-turbo", "messages": [ { "role": "system", "content": "MY CONTENT" }, { "role": "user", "content": "%s" } ], "temperature": 1, "max_tokens": 256, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0 } """, userInput); logger.info("Sending request to OpenAI with body: {}", requestBody); return this.webClient.post() .uri("/chat/completions") .header("Content-Type", "application/json") .header("Authorization", "Bearer " + openaiApiKey) .bodyValue(requestBody) .retrieve() .bodyToMono(String.class) .doOnNext(response -> logger.info("Received response from OpenAI: {}", response)) .doOnError(WebClientResponseException.class, error -> { logger.error("Error response from OpenAI: {}", error.getResponseBodyAsString()); }) .doOnError(error -> logger.error("Error occurred: ", error)); }
我的Postman请求(不太相关,这不是问题所在):
{input: "MY INPUT"}
回答:
我尝试添加了@ResponseBody,但这并没有解决问题。经过更多的挖掘,似乎是Mono<ResponseEntity>返回类型的问题,因为这告诉Spring以异步和非阻塞的方式处理请求。将方法转换为在Mono上使用”.block()”进行阻塞,并使其成为一个普通的阻塞调用,允许它在继续之前等待来自服务的响应。这意味着你不会获得响应式编程解决方案的好处,但至少我们现在可以继续进行。
如果你遇到同样的问题,尝试将调用转换为传统的阻塞调用,看看是否有效。仍然不太清楚Spring的响应式代码中是什么导致了这个问题,但我很高兴现在可以继续进行。