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

时间:2017-09-05 20:28:19

标签: spring spring-security cors jhipster

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

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

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

对于退出,例如,Google Chrome会在控制台中响应以下消息,即使呼叫在“网络”标签中进行(POST响应状态200):

  

XMLHttpRequest无法加载http://localhost:8080/api/logout。 No' Access-Control-Allow-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片段进行身份验证。您还可以尝试对POST进行简单的/api/account调用,这会导致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>
&#13;
&#13;
&#13;

2 个答案:

答案 0 :(得分:5)

您似乎遇到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

答案 1 :(得分:3)

看一下org.springframework.web.cors.CorsConfiguration如何在JHipster common application properties中配置exposed-headers: "Authorization" Spring bean。我认为您缺少以下配置行:

function OnSuccess(response) { var data = {}; var value = []; JSON.parse(response).forEach(function(d) { data[d.item1] = d.y; value.push(d.item1); }); c3.generate({ data: { json: [data], keys: { value : value }, type: 'donut' }, donut: { title: "Approval", width: 40, label: { show: false } }, color: { pattern: ["#ff9800", "#78c350", "#f7531f"] } }) }

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

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