Angular 带有 csrf 令牌的 Spring Boot Spring 安全应用程序 Ajax POST 在 Token/Header 未定义时失败
Angular SpringBoot SpringSecurity Application Ajax POST with csrf Token Fails on Token/Header being undefined
上下文/原样
我目前正在开发一个 single-page Angular JS 应用程序,使用 SpringBoot 和 Spring 安全实现。该应用程序适用于 'Study Planner',学生可以在日历中输入学习休假。到目前为止,我们让学生能够登录,并在日历中输入详细信息 client-side。
目标/成为
能够在我们的 client-side 中捕获事件添加之后
javascript,我们正在尝试使用 Ajax POST 将其发送回我们的服务器。一旦发送到服务器,目标就是将其保存在我们的数据库中以供学生查看,以便学生下次查看日历时可以加载它。
问题
我们遇到的问题是围绕 Ajax POST 方法,我相信是由于将 csrf header 引入 POST 以试图通过 Spring 安检。如果没有 csrf header,我会看到 Chrome 内的网络流量并收到 403(未经授权)错误。随着 csrf header 的引入,没有网络流量记录,服务器没有被命中,ajax "error" 函数被命中。
无 CSRF 令牌
- 学生登录他们的日历
- 学生加入活动
- 警报已触发'newEventData function'
- 警报已触发'we failed'
403 Chrome
错误
403(禁止)
"Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'."
使用 CSRF 令牌
(改编自 spring docs 到 'beforesend'。如果我只是将文档中的函数添加到我的 js 文件的底部,也会发生同样的情况)
- 学生登录他们的日历
- 学生加入活动
- 警报已触发'newEventData function'
- 警报已触发'beforesend'
- 警报已触发'we failed'
抛出错误
语法错误:无法在 'XMLHttpRequest' 上执行 'setRequestHeader':'$(_csrf.headerName}' 不是有效的 HTTP header 字段名称。
需要协助的地方
- 如何查看有关 Ajax 错误的更多信息。
- 什么可能导致根本没有显示网络流量,即使 ajax 正在触发并返回错误。
- 配置 spring 安全性以避免这种情况的任何提示(除了简单地关闭它)
其他相关信息
- 我们扩展了 WebSecurityConfigAdapter
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
/**
* Set up url based security
*/
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().antMatchers("/index.html", "/home.html",
"/login.html", "/", "/user","/login")
.permitAll().anyRequest().authenticated().and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class).csrf()
.csrfTokenRepository(csrfTokenRepository()).and().logout();
}
@Override
/**
* Set up a service to handle authentication requests
*/
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.userDetailsService(userDetailsService).passwordEncoder(new
// BCryptPasswordEncoder());
auth.userDetailsService(userDetailsService);
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
- 我们还扩展了 'OncePerRequestFilder'(虽然 none 这些系统输出正在 Ajax POST 上打印)
public class CsrfHeaderFilter extends OncePerRequestFilter {
/**
* Overrides SpringSecurity filter
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
System.out.println("In doFilterInternal");
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
System.out.println("csrf is null");
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null && !token.equals(cookie.getValue())) {
System.out.println("cookie null token not and token doesnt equal cookie");
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
System.out.println("doing filter chain");
filterChain.doFilter(request, response);
}
}
- 这就是我们要打的方法。如果我从我们的 HttpSecurity 中删除 csrf 安全性,这会很好并打印出所有详细信息。
@RequestMapping(method = RequestMethod.POST, value = "/Student/{studentusername}/universitydays")
public void newEvent(String id, String text, String start, String end, @PathVariable String studentusername) {
System.out.println(text);
System.out.println(studentusername);
System.out.println(id);
System.out.println(start);
System.out.println(new Date(Long.parseLong(start)));
System.out.println(new Date(Long.parseLong(end)));
}
抱歉超长 post,这是我已经研究了一段时间但对 web-app 安全性没有很好理解的东西,所以我正在努力将各个部分组合在一起.任何指导将不胜感激。
干杯,
斯科特
更新
添加了 Paqman 提示的错误参数后,我现在有了以下信息,但仍然不确定如何处理。
SyntaxError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': '${_csrf.headerName}' is not a valid HTTP header field name.
使用我的 chrome 控制台时,这些以文本变量的形式返回。我认为这是不正确的?
我在 index.html header 中的代码(其他页面加载为 'partials')是:
<head>
<title>Study Planner</title>
...css files...
<meta name="_csrf" content="${_csrf.token}" />
<meta name="_csrf_header" content="${_csrf.headerName}" />
</head>
进一步更新
我们通过使用 th:content 而不是 'content' 设法解决了上述文字字符串问题,但这仍然给我们带来了 header/token 未定义的错误。我已经 post 编辑了我们问题的实际解决方案作为答案,但如果有人在 Angular 项目的 html 文件中检索 meta-data 时遇到类似问题,我相信会改变'content' 到 'th:content' 可能会有所帮助。
See this related question
<head>
<title>Study Planner</title>
...css files...
<meta name="_csrf" th:content="${_csrf.token}" />
<meta name="_csrf_header" th:content="${_csrf.headerName}" />
</head>
经过一番纠结,我们终于解决了这个问题。尽管我发现的大多数文档都指向存储在会话中的 csrf 令牌,但在我们的应用程序中,我们覆盖了默认的 Spring 安全行为以将其存储在 cookie 中(取自此 SpringBoot Angular Guide)。
public class CsrfHeaderFilter extends OncePerRequestFilter {
/**
* Overrides SpringSecurity filter
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
System.out.println("csrf is null");
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
因此,在关注 Spring 文档 here.
时,我们似乎是 'barking up the wrong tree'
意识到这一点后,我们实际上遇到了一些 Django docs 指导我们从 cookie 中检索令牌值。
解决方案
我们将此函数添加到 javascript 中以检索令牌:
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
将 var 设置为该函数返回的值
var csrftoken = getCookie('XSRF-TOKEN');
最后对 HEADER 进行了硬编码(不确定我们以后是否会遇到问题),并使用上面为令牌创建的变量。
function newEventData(ev) {
$.ajax({
"url" : "/Student/" + loggedinusername
+ "/universitydays?id=" + ev.id + "&text="
+ ev.text + "&start="
+ Date.parse(ev.start_date) + "&end="
+ Date.parse(ev.end_date),
"method" : "POST",
beforeSend : function(xhr) {
xhr.setRequestHeader("X-XSRF-TOKEN", csrftoken);
},
"success" : function() {
},
"error" : function(jqXHR, textStatus, errorThrown) {
alert("error: " + errorThrown);
}
});
}
希望这对遇到类似问题的其他人有所帮助。
上下文/原样
我目前正在开发一个 single-page Angular JS 应用程序,使用 SpringBoot 和 Spring 安全实现。该应用程序适用于 'Study Planner',学生可以在日历中输入学习休假。到目前为止,我们让学生能够登录,并在日历中输入详细信息 client-side。
目标/成为
能够在我们的 client-side 中捕获事件添加之后 javascript,我们正在尝试使用 Ajax POST 将其发送回我们的服务器。一旦发送到服务器,目标就是将其保存在我们的数据库中以供学生查看,以便学生下次查看日历时可以加载它。
问题
我们遇到的问题是围绕 Ajax POST 方法,我相信是由于将 csrf header 引入 POST 以试图通过 Spring 安检。如果没有 csrf header,我会看到 Chrome 内的网络流量并收到 403(未经授权)错误。随着 csrf header 的引入,没有网络流量记录,服务器没有被命中,ajax "error" 函数被命中。
无 CSRF 令牌
- 学生登录他们的日历
- 学生加入活动
- 警报已触发'newEventData function'
- 警报已触发'we failed'
403 Chrome
错误403(禁止) "Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'."
使用 CSRF 令牌
(改编自 spring docs 到 'beforesend'。如果我只是将文档中的函数添加到我的 js 文件的底部,也会发生同样的情况)
- 学生登录他们的日历
- 学生加入活动
- 警报已触发'newEventData function'
- 警报已触发'beforesend'
- 警报已触发'we failed'
抛出错误
语法错误:无法在 'XMLHttpRequest' 上执行 'setRequestHeader':'$(_csrf.headerName}' 不是有效的 HTTP header 字段名称。
需要协助的地方
- 如何查看有关 Ajax 错误的更多信息。
- 什么可能导致根本没有显示网络流量,即使 ajax 正在触发并返回错误。
- 配置 spring 安全性以避免这种情况的任何提示(除了简单地关闭它)
其他相关信息
- 我们扩展了 WebSecurityConfigAdapter
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
/**
* Set up url based security
*/
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().antMatchers("/index.html", "/home.html",
"/login.html", "/", "/user","/login")
.permitAll().anyRequest().authenticated().and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class).csrf()
.csrfTokenRepository(csrfTokenRepository()).and().logout();
}
@Override
/**
* Set up a service to handle authentication requests
*/
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.userDetailsService(userDetailsService).passwordEncoder(new
// BCryptPasswordEncoder());
auth.userDetailsService(userDetailsService);
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
- 我们还扩展了 'OncePerRequestFilder'(虽然 none 这些系统输出正在 Ajax POST 上打印)
public class CsrfHeaderFilter extends OncePerRequestFilter {
/**
* Overrides SpringSecurity filter
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
System.out.println("In doFilterInternal");
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
System.out.println("csrf is null");
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null && !token.equals(cookie.getValue())) {
System.out.println("cookie null token not and token doesnt equal cookie");
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
System.out.println("doing filter chain");
filterChain.doFilter(request, response);
}
}
- 这就是我们要打的方法。如果我从我们的 HttpSecurity 中删除 csrf 安全性,这会很好并打印出所有详细信息。
@RequestMapping(method = RequestMethod.POST, value = "/Student/{studentusername}/universitydays")
public void newEvent(String id, String text, String start, String end, @PathVariable String studentusername) {
System.out.println(text);
System.out.println(studentusername);
System.out.println(id);
System.out.println(start);
System.out.println(new Date(Long.parseLong(start)));
System.out.println(new Date(Long.parseLong(end)));
}
抱歉超长 post,这是我已经研究了一段时间但对 web-app 安全性没有很好理解的东西,所以我正在努力将各个部分组合在一起.任何指导将不胜感激。
干杯, 斯科特
更新
添加了 Paqman 提示的错误参数后,我现在有了以下信息,但仍然不确定如何处理。
SyntaxError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': '${_csrf.headerName}' is not a valid HTTP header field name.
使用我的 chrome 控制台时,这些以文本变量的形式返回。我认为这是不正确的?
我在 index.html header 中的代码(其他页面加载为 'partials')是:
<head>
<title>Study Planner</title>
...css files...
<meta name="_csrf" content="${_csrf.token}" />
<meta name="_csrf_header" content="${_csrf.headerName}" />
</head>
进一步更新
我们通过使用 th:content 而不是 'content' 设法解决了上述文字字符串问题,但这仍然给我们带来了 header/token 未定义的错误。我已经 post 编辑了我们问题的实际解决方案作为答案,但如果有人在 Angular 项目的 html 文件中检索 meta-data 时遇到类似问题,我相信会改变'content' 到 'th:content' 可能会有所帮助。
See this related question
<head>
<title>Study Planner</title>
...css files...
<meta name="_csrf" th:content="${_csrf.token}" />
<meta name="_csrf_header" th:content="${_csrf.headerName}" />
</head>
经过一番纠结,我们终于解决了这个问题。尽管我发现的大多数文档都指向存储在会话中的 csrf 令牌,但在我们的应用程序中,我们覆盖了默认的 Spring 安全行为以将其存储在 cookie 中(取自此 SpringBoot Angular Guide)。
public class CsrfHeaderFilter extends OncePerRequestFilter {
/**
* Overrides SpringSecurity filter
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
System.out.println("csrf is null");
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
因此,在关注 Spring 文档 here.
时,我们似乎是 'barking up the wrong tree'意识到这一点后,我们实际上遇到了一些 Django docs 指导我们从 cookie 中检索令牌值。
解决方案
我们将此函数添加到 javascript 中以检索令牌:
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
将 var 设置为该函数返回的值
var csrftoken = getCookie('XSRF-TOKEN');
最后对 HEADER 进行了硬编码(不确定我们以后是否会遇到问题),并使用上面为令牌创建的变量。
function newEventData(ev) {
$.ajax({
"url" : "/Student/" + loggedinusername
+ "/universitydays?id=" + ev.id + "&text="
+ ev.text + "&start="
+ Date.parse(ev.start_date) + "&end="
+ Date.parse(ev.end_date),
"method" : "POST",
beforeSend : function(xhr) {
xhr.setRequestHeader("X-XSRF-TOKEN", csrftoken);
},
"success" : function() {
},
"error" : function(jqXHR, textStatus, errorThrown) {
alert("error: " + errorThrown);
}
});
}
希望这对遇到类似问题的其他人有所帮助。