JHipster:CORS 在表单登录时失败(实际请求,不是飞行前)

Posted

技术标签:

【中文标题】JHipster:CORS 在表单登录时失败(实际请求,不是飞行前)【英文标题】:JHipster: CORS fails on form login (actual request, not pre-flight) 【发布时间】:2018-02-14 05:12:37 【问题描述】:

我想使用第二个前端应用程序扩展我的 jHipster 单片设置,该应用程序从不同的 URL 访问相同的 API。作为第一步,我在application.yml 中启用了CORS,并使用withCredentials 标志从前端发送请求。我正在使用会话并且没有 JWT 身份验证。

许多方法现在都按预期工作,但不是全部。飞行前(OPTIONS 请求)总是通过并按预期工作。此调用的响应包含正确的 CORS 标头。

但是,实际请求(例如登录的 POST 请求)还需要响应中的标头 (Access-Control-Allow-Origin)。此标头在我的自定义 REST 接口上自动设置,但未在 jHipster 生成的方法上设置,例如 /api/authentication/api/logout。它也不适用于受 Spring-Security 保护的资源,例如 /api/account(仅当未登录时,401,之后它可以使用正确的标头按预期工作)

例如,对于注销,Google Chrome 会在控制台中对以下消息做出反应,即使呼叫在“网络”选项卡中通过(POST 响应状态 200):

XMLHttpRequest 无法加载 http://localhost:8080/api/logout。请求的资源上不存在“Access-Control-Allow-Origin”标头。 Origin 'http://localhost:9000' 因此不允许访问。

我在想,我在这里做错了什么。我猜标题设置不正确。我现在可以手动添加标题(例如在 AjaxAuthenticationSuccessHandler 中),但这似乎不对。

我正在使用相当过时的 jHipster 3.7.0 版本。但是,我宁愿不更新核心项目。

您有什么想法,是什么导致了这个问题?



标题

以下是对/api/logout 的 POST 调用的完整标头。 OPTIONS 调用按预期工作,但在 POST 响应中缺少 Access-Control-Allow-Origin 标头:

OPTIONS请求

OPTIONS /api/logout HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: POST
Origin: http://localhost:9000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/60.0.3112.113 Safari/537.36
Access-Control-Request-Headers: x-csrf-token
Accept: */*
DNT: 1
Referer: http://localhost:9000/
Accept-Encoding: gzip, deflate, br
Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4,de-CH;q=0.2,it;q=0.2

OPTIONS回复

HTTP/1.1 200 OK
Access-Control-Allow-Headers: x-csrf-token
Date: Mon, 11 Sep 2017 13:54:57 GMT
Connection: keep-alive
Access-Control-Allow-Origin: http://localhost:9000
Vary: Origin
Access-Control-Allow-Credentials: true
Content-Length: 0
Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS
Access-Control-Max-Age: 1800

POST请求

POST /api/logout HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 0
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/plain, */*
Origin: http://localhost:9000
X-CSRF-TOKEN: [***token removed in this snippet***]
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
DNT: 1
Referer: http://localhost:9000/
Accept-Encoding: gzip, deflate, br
Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4,de-CH;q=0.2,it;q=0.2
Cookie: [***removed cookies in this snippet***]

POST回复

HTTP/1.1 200 OK
Expires: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Set-Cookie: CSRF-TOKEN=null; path=/; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:00 GMT
Set-Cookie: JSESSIONID=[***removed jsessionID in this snippet***]; path=/; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:00 GMT
Set-Cookie: remember-me=null; path=/; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:00 GMT
X-XSS-Protection: 1; mode=block
Pragma: no-cache
Date: Mon, 11 Sep 2017 13:54:57 GMT
Connection: keep-alive
X-Content-Type-Options: nosniff
Content-Length: 0

复制

您可以使用this demo project of jHipster in version 3.7.0 重现此行为。然后,在src/main/resources/application.yml 中启用 CORS 设置(全部)。之后,在localhost:8080 上创建一个新用户并激活它。最后,尝试从另一个端口(例如简单的节点服务器或 xampp)使用以下 JS sn-p 进行身份验证。您也可以尝试对/api/account 进行简单的POST 调用,这将导致401 错误。有关错误消息,请参阅 Google Chrome 控制台。

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.16.2/axios.js"></script>
<script type="text/javascript">
var Http = axios.create(
  baseURL: 'http://localhost:8080/api',
);

Http.interceptors.request.use(function (config) 
  config.xsrfCookieName = 'CSRF-TOKEN';
  config.xsrfHeaderName = 'X-CSRF-TOKEN';
  config.withCredentials = true;
  return config;
);

var credentials = 
  username: 'test-user',
  password: 'test123',
  rememberMe: true
;

Http.post('authentication', 'j_username=' + credentials.username +
'&j_password=' + credentials.password +
'&remember-me=' + credentials.rememberMe +
'&submit=Login');
</script>

【问题讨论】:

对 /api 端点的其他 POST 请求是否有效(例如,创建实体)? CORS 已为 /api/** 路径注册,因此行为应该相同 @ChristopheBornet 是的,/api 之后的端点工作。我刚刚发现,例如如果 Spring Security 不拒绝访问(例如 401),则端点 /api/account 有效。如果在方法体中抛出异常,则其他端点可以完美地处理抛出的异常。看来,所有 Spring Security 方法都有问题,而且 Spring Security 阻止了调用。 @ssc-hrep3 您是否尝试在默认的SecurityConfiguration.java 文件中添加antMatcher 以避免对OPTIONS 进行安全检查?在配置方法中,添加:.antMatchers(org.springframework.http.HttpMethod.OPTIONS, "/api/**").permitAll() @ishworkafley 是的。这已经是默认 jHipster 配置的一部分。并且与此问题无关,因为OPTIONS 调用按预期工作,只有后面的实际请求不包含所需的标头。 【参考方案1】:

您似乎遇到了this CORS issue,其中 Spring Security 的过滤器链中 CorsFilter 之前的过滤器之一会引发错误。请求永远不会到达CorsFilter,导致响应中缺少 CORS 标头。这就是 Chrome 抱怨控制台中缺少标头的原因,即使这是一个不同的错误。

您需要将CorsFilter 放在CsrfFilterUsernamePasswordAuthenticationFilter 之前,这样如果其中任何一个过滤器出现问题,响应仍会获得CORS 标头。为此,请将以下代码添加到SecurityConfiguration.java

// import CorsFilter
import org.springframework.web.filter.CorsFilter;
...
...
// inject for JHipster v3 apps, add to constructor for JHipster v4 apps
@Inject
private CorsFilter corsFilter;
...
...
// add this line in the configure method before ".exceptionHandling()"
.addFilterBefore(corsFilter, CsrfFilter.class)

同样在SecurityConfiguration.java 中,您可以设置@EnableWebSecurity(debug = true) 来查看每个请求的过滤器链。您可以通过确保 CorsFilter 在链中的 CsrfFilterUsernamePasswordAuthenticationFilter 之前来验证一切是否正确:

Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CorsFilter     <--------- Before CsrfFilter and UsernamePasswordAuthenticationFilter
    CsrfFilter
    CsrfCookieGeneratorFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    RememberMeAuthenticationFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]

如果您在新客户端中遇到 CSRF 问题,您可能需要在 POST 之后发出 GET 请求以刷新 CSRF 令牌。 JHipster 如何处理此问题的示例可见when logging out。

【讨论】:

很高兴为您提供帮助。感谢您的报告,此问题已在主生成器 github.com/jhipster/generator-jhipster/pull/6373 中得到修复【参考方案2】:

看看org.springframework.web.cors.CorsConfiguration Spring bean 是如何在JHipster common application properties 中配置的。我认为您缺少以下配置行:

exposed-headers: "Authorization"

几个小时前我刚刚记录了这一点,also added this configuration by default a few hours ago。

在明天应该发布的下一个版本中,您应该有一个名为 Separating the front-end and the API server 的新页面,它应该可以更好地解释这一点 - 如果您找到了更好的解决方案,请毫不犹豫地改进该页面,这是一个第一个版本。

【讨论】:

感谢您的回答!我实际上使用会话进行身份验证并且没有 JWT 令牌。主要问题是从 Spring 应用程序返回的请求带有成功的状态码,但 POST 响应中缺少 Access-Control-Allow-Origin 标头,这导致浏览器正确设置 cookie 但拒绝请求。

以上是关于JHipster:CORS 在表单登录时失败(实际请求,不是飞行前)的主要内容,如果未能解决你的问题,请参考以下文章

当我使用 liquibase 更改模型数据时,JHipster 集成测试失败

CORS请求期间的JHipster 3错误

尽管有 CORS,但在使用 jHipster oAuth 时出现未经授权的错误

Jhipster CORS 启用

CORS XMLHttpRequest 使用 POST 将多部分表单数据发送到 Softlayer 对象存储失败

邮件服务器未连接时,Jhipster Health Endpoint失败