Angular SpringBoot SpringSecurity 应用程序 Ajax POST 与 csrf 令牌在令牌/标题未定义时失败
Posted
技术标签:
【中文标题】Angular SpringBoot SpringSecurity 应用程序 Ajax POST 与 csrf 令牌在令牌/标题未定义时失败【英文标题】:Angular SpringBoot SpringSecurity Application Ajax POST with csrf Token Fails on Token/Header being undefined 【发布时间】:2016-06-20 21:10:47 【问题描述】:上下文/现状
我目前正在使用带有 Spring Security 实现的 SpringBoot 开发一个单页 Angular JS 应用程序。该应用程序适用于“学习计划者”,学生可以在日历中输入休学假。到目前为止,我们已经让学生能够登录,并将详细信息输入到日历客户端。
目标/成为
在我们的客户端能够捕获事件添加之后 javascript,我们正在尝试使用 Ajax POST 将其发送回我们的服务器。一旦发送到服务器,目标就是将其保存在我们的数据库中以针对学生,以便下次学生查看日历时可以加载它。
问题
我们遇到的问题是围绕 Ajax POST 方法,我相信是由于在 POST 中引入了 csrf 标头以试图通过 Spring Security。如果没有 csrf 标头,我会在 Chrome 中看到网络流量并收到 403(未经授权)错误。引入 csrf 标头后,没有记录网络流量,没有命中服务器,也命中了 ajax“错误”功能。
没有 CSRF 令牌
-
学生登录日历
学生参加活动
警报触发“newEventData 函数”
警报触发“我们失败了”
Chrome 出现 403 错误
403(禁止) “在请求参数 '_csrf' 或标头 'X-XSRF-TOKEN' 上发现无效的 CSRF Token 'null'。”
使用 CSRF 令牌
(改编自 spring docs 以在 'beforesend' 中。如果我只是将文档中的函数添加到我的 js 文件的底部,也会发生同样的情况)
-
学生登录日历
学生参加活动
警报触发“newEventData 函数”
“发送前”触发警报
警报触发“我们失败了”
抛出错误
语法错误:无法对“XMLHttpRequest”执行“setRequestHeader”:“$(_csrf.headerName”不是有效的 HTTP 标头字段名称。
援助领域
如何查看有关 Ajax 错误的更多信息。 什么可能导致根本没有显示网络流量,即使 ajax 正在触发并返回错误。 有关配置弹簧安全装置的任何提示,以避免这种情况(除了简单地关闭它)其他相关信息
我们扩展了 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”(尽管这些系统输出都没有在 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)));
为这篇超长的帖子道歉,这是我一直在寻找的东西,但对网络应用安全性并没有很好的了解,所以我正在努力将各个部分拼凑在一起。任何指导将不胜感激。
干杯, 斯科特。
更新
添加 Paqman 建议的错误参数后,我现在有了以下信息,但仍不确定如何处理。
SyntaxError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': '$_csrf.headerName' is not a valid HTTP header field name.
当使用我的 chrome 控制台时,这些会作为文本变量返回。我认为这是不正确的?
我在 index.html 标头中的代码(其他页面作为“部分”加载)是:
<head>
<title>Study Planner</title>
...css files...
<meta name="_csrf" content="$_csrf.token" />
<meta name="_csrf_header" content="$_csrf.headerName" />
</head>
进一步更新
我们设法通过使用 th:content 而不是 'content' 解决了上述文字字符串问题,但这仍然给我们带来了关于未定义标头/令牌的错误。我已经发布了对我们问题的实际解决方案作为答案,但是如果有人在 Angular 项目中检索 html 文件中的元数据时遇到类似问题,我相信将“内容”更改为“th:内容”可能会有所帮助。
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>
【问题讨论】:
您使用的是哪个版本的 jQuery?您是否尝试过使用 $.ajax 的 headers 属性? var myHeaders = ;我的标头[标头]=令牌; beforeSend : function(xhr) // ..., headers : myHeaders 还有一件事,“错误”函数有 3 个参数:( jqXHR jqXHR, String textStatus, String errorThrown )。也许你会在里面找到一些有用的信息。 您的“$(_csrf.headerName”似乎拼写错误。您以“(”开头,但以“”结尾。或者只是检查您的 HTML内容,尤其是 __csrf 标签,它似乎没有正确的值。 道歉@Paqman 错字只是***,因为它不会让我C&P 退出警报。我认为问题在于设置元,我有这些行: 添加到我的普通 .html 页面中,并在检查时将这些维护为带有 var 名称的字符串。这是页面是 .html 而不是 JSP 的问题吗?我没有编写应用程序的启动代码,但我的同事最初遵循了指南,所以我认为页面类型可以 那么您的 $ vars 似乎没有被处理。也许您的 html 没有被解析,如果没有项目设置,我真的无法在这一点上为您提供帮助。您可以尝试按照您所说的将 .html 文件重命名为 .jsp。 【参考方案1】:经过一番困惑,我们终于解决了这个问题。尽管我发现的大多数文档都指向存储在会话中的 csrf 令牌,但在我们的应用程序中,我们覆盖了默认的 SpringSecurity 行为以将其存储在 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 Docs here 时,我们似乎在“找错了树”。
一旦意识到这一点,我们实际上遇到了一些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);
);
希望这对遇到类似问题的其他人有所帮助。
【讨论】:
以上是关于Angular SpringBoot SpringSecurity 应用程序 Ajax POST 与 csrf 令牌在令牌/标题未定义时失败的主要内容,如果未能解决你的问题,请参考以下文章
maven - Spring Boot/Angular 2/4 项目战构建