背景/原样
我目前正在使用带有Spring Security实现的SpringBoot开发单页Angular JS应用程序。该申请适用于“学习计划者”,学生可以在日历中输入工作学习假。到目前为止,我们让学生能够登录,并在日历客户端输入详细信息。
目标/待
能够在我们的客户端捕获事件添加之后 javascript,我们正试图使用Ajax POST将其发送回我们的服务器。一旦发送到服务器,目的是将其保存在我们的数据库中,以防止学生在下次学生查看日历时加载它。
问题
我们遇到的问题是围绕Ajax POST方法,导致我相信通过向POST引入csrf标头以试图通过Spring Security。如果没有csrf标头,我会在Chrome中看到网络流量并收到403(未经授权)错误。引入了csrf标头后,没有记录网络流量,服务器没有被命中,并且ajax“错误”功能被命中。
没有CSRF令牌
Chrome中的403错误
403(禁止) “在请求参数'_csrf'或标题'X-XSRF-TOKEN'上找到”无效的CSRF令牌'null'。“
使用CSRF令牌
(从spring docs改编为'beforesend'。如果我只是将文档中的函数添加到底部og my js文件中,也会发生相同的情况。)
抛出错误
SyntaxError:无法在'XMLHttpRequest'上执行'setRequestHeader':'$(_ csrf.headerName}'不是有效的HTTP标头字段名称。
援助范围
其他相关信息
@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;
}
}
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);
}
}
@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标题内的代码(其他页面加载为'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'来修复上面描述的文字字符串问题,但这仍然给我们错误的标头/标记未定义。我发布的问题的实际解决方案是作为答案发布的,但如果有人在Angular项目中检索html文件中的元数据时遇到类似的问题,我认为将“内容”更改为“th:content”可能会有所帮助。
<head>
<title>Study Planner</title>
...css files...
<meta name="_csrf" th:content="${_csrf.token}" />
<meta name="_csrf_header" th:content="${_csrf.headerName}" />
</head>
答案 0 :(得分:0)
经过多次混乱,我们终于解决了这个问题。虽然我发现大多数文档指向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时,我们似乎正在“咆哮错误的树”。
一旦意识到这一点,我们实际上遇到了一些引导我们从cookie中检索令牌值的Django docs。
<强>解决方案强>
我们将此功能添加到我们的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);
}
});
}
希望如果他们遇到类似的问题,这会有所帮助。