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
放在CsrfFilter
和UsernamePasswordAuthenticationFilter
之前,这样如果其中任何一个过滤器出现问题,响应仍会获得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
在链中的 CsrfFilter
和 UsernamePasswordAuthenticationFilter
之前来验证一切是否正确:
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 oAuth 时出现未经授权的错误