Spring 安全 - 所有 JQuery Ajax post 请求 return 404
Spring Security - All JQuery Ajax post requests return 404
我所有的 $.ajax
、POST
和 GET
都工作正常,但是一旦我将 Spring security 3.2.6
集成到我的项目中,POST
ajax 请求停止工作,没有登录任何问题。
spring-security.xml
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.2.xsd">
<!--Permit all Web resources to bypass proxy-->
<http pattern="/js/**" security="none"/>
<http pattern="/css/**" security="none"/>
<http pattern="/fonts/**" security="none"/>
<http pattern="/images/**" security="none"/>
<http auto-config="true" use-expressions="true" >
<intercept-url pattern="/login" access="isAnonymous()"/>
<intercept-url pattern="/workflow**" access="hasRole('ROLE_WORKFLOW_ADMIN')"/>
<intercept-url pattern="/**" access="hasAnyRole('ROLE_ADMIN','ROLE_WORKFLOW_ADMIN','ROLE_DMS_ADMIN')"/>
<access-denied-handler error-page="/403"/>
<form-login
login-page="/login"
default-target-url="/dashboard"
authentication-failure-url="/login?error"
username-parameter="username"
password-parameter="password"/>
<logout invalidate-session="true" logout-success-url="/login?logout"/>
<csrf/>
</http>
<!-- Select users and user_roles from database -->
<authentication-manager>
<authentication-provider ref="daoAuthenticationProvider"/>
</authentication-manager>
<beans:bean id="daoAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="authService"/>
</beans:bean>
</beans:beans>
Web.xml
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/mvc-dispatcher-servlet.xml
/WEB-INF/spring-security.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/error</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error</location>
</error-page>
</web-app>
编辑
我尝试访问的 URL 是
这可能与 spring 安全有关吗?
您是否尝试过 post 使用正常的 http 请求来使用 spring 安全性,您必须使用特定的变量名集,您可以 google 它。
同时检查我们是否可以使用 AJAX.
向受限 URL 发送 post 请求
经过三天的折腾,我终于找到了问题所在,这孩子是不是太蠢了。
问题是我在 spring 安全中启用了 csrf
保护。这导致我的 post 请求被禁止触发 access-denied-handler
错误页面,因为我没有将我的 access-denied-handler
映射到 "/403"
错误页面,如下所示,我的http 403/401
被 http 404
掩盖了
<access-denied-handler error-page="/403"/>
简而言之
- 将您的
access-denied-handler
错误页面映射到有效的 url
- 如果您使用 csrf 保护,请始终确保在 ajax post 请求中传递它们
$.ajax({method :'POST', url : '/ajax',data :
{"${_csrf.parameterName}" : "${_csrf.token}"}});
在实现springsecurity 4的时候还有一种情况需要特别参考,一种是需要表单按钮(不是"a href")。
A> 表单 post 请求已发送:输入类型隐藏在 both login/logout 按钮中
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
B> 对于 AJAX post 请求,在您的 JSP 页面中的 taglib 声明后添加以下内容。
<meta name="_csrf" content="${_csrf.token}" />
<meta name="_csrf_header" content="${_csrf.headerName}" />
和一些在 jsp 任何地方 "on ready"
关闭 body 标签
<script type="text/javascript">
$(function() {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
});
</script>
应该有用!
如果您仍然存在 CORS 问题,请参阅下面的文章。
http://www.html5rocks.com/en/tutorials/cors/
使用 Spring Boot 2.2.1 和 Spring MVC、Spring Security 5 和 Thymeleaf 3.0.11 的替代解决方案
很高兴我终于找到了解决这个问题的方法,我也想在这里分享一下:
问题:
在我的例子中,它是一个 $.ajax POST 请求到一个有效的 URL 返回 404 错误状态(未找到)。
@Mushtaq Jameel 已解释问题的最初原因是 csrf,从 Spring Security 4[开始默认启用 =46=] (source).
解决方法:
我没有测试@Mushtaq Jameel 提出的建议,但这个优雅的快速修复对我有用:
AJAX代码:
$.ajax({
type: "POST",
url: "/",
data: $('.cd-signin-modal__form.sign-up').serialize(), // <- FIX
success: // some code
error: // some code
});
}
换句话说,解决方案是在 HTML 表单本身上调用 .serialize() 方法。
然后,_csfr 令牌会自动添加 在 POST 请求中作为附加表单参数:
这又是由于 Spring 安全的较新版本默认启用了 csrf(here 提到这个隐藏的表单参数是自动添加的)。
我的 Spring MVC 控制器然后接受如下形式:
@PostMapping("/")
public String createUser(@Valid @ModelAttribute User user, @Valid @ModelAttribute UserDetails userDetails, BindingResult errors) {
if (errors.hasErrors()) {
// handle errors
}
// persist objects in database
return "index";
}
我所有的 $.ajax
、POST
和 GET
都工作正常,但是一旦我将 Spring security 3.2.6
集成到我的项目中,POST
ajax 请求停止工作,没有登录任何问题。
spring-security.xml
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.2.xsd">
<!--Permit all Web resources to bypass proxy-->
<http pattern="/js/**" security="none"/>
<http pattern="/css/**" security="none"/>
<http pattern="/fonts/**" security="none"/>
<http pattern="/images/**" security="none"/>
<http auto-config="true" use-expressions="true" >
<intercept-url pattern="/login" access="isAnonymous()"/>
<intercept-url pattern="/workflow**" access="hasRole('ROLE_WORKFLOW_ADMIN')"/>
<intercept-url pattern="/**" access="hasAnyRole('ROLE_ADMIN','ROLE_WORKFLOW_ADMIN','ROLE_DMS_ADMIN')"/>
<access-denied-handler error-page="/403"/>
<form-login
login-page="/login"
default-target-url="/dashboard"
authentication-failure-url="/login?error"
username-parameter="username"
password-parameter="password"/>
<logout invalidate-session="true" logout-success-url="/login?logout"/>
<csrf/>
</http>
<!-- Select users and user_roles from database -->
<authentication-manager>
<authentication-provider ref="daoAuthenticationProvider"/>
</authentication-manager>
<beans:bean id="daoAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="authService"/>
</beans:bean>
</beans:beans>
Web.xml
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/mvc-dispatcher-servlet.xml
/WEB-INF/spring-security.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/error</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error</location>
</error-page>
</web-app>
编辑
我尝试访问的 URL 是
这可能与 spring 安全有关吗?
您是否尝试过 post 使用正常的 http 请求来使用 spring 安全性,您必须使用特定的变量名集,您可以 google 它。
同时检查我们是否可以使用 AJAX.
向受限 URL 发送 post 请求经过三天的折腾,我终于找到了问题所在,这孩子是不是太蠢了。
问题是我在 spring 安全中启用了 csrf
保护。这导致我的 post 请求被禁止触发 access-denied-handler
错误页面,因为我没有将我的 access-denied-handler
映射到 "/403"
错误页面,如下所示,我的http 403/401
被 http 404
<access-denied-handler error-page="/403"/>
简而言之
- 将您的
access-denied-handler
错误页面映射到有效的 url - 如果您使用 csrf 保护,请始终确保在 ajax post 请求中传递它们
$.ajax({method :'POST', url : '/ajax',data : {"${_csrf.parameterName}" : "${_csrf.token}"}});
在实现springsecurity 4的时候还有一种情况需要特别参考,一种是需要表单按钮(不是"a href")。
A> 表单 post 请求已发送:输入类型隐藏在 both login/logout 按钮中
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
B> 对于 AJAX post 请求,在您的 JSP 页面中的 taglib 声明后添加以下内容。
<meta name="_csrf" content="${_csrf.token}" />
<meta name="_csrf_header" content="${_csrf.headerName}" />
和一些在 jsp 任何地方 "on ready"
关闭 body 标签<script type="text/javascript">
$(function() {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
});
</script>
应该有用! 如果您仍然存在 CORS 问题,请参阅下面的文章。 http://www.html5rocks.com/en/tutorials/cors/
使用 Spring Boot 2.2.1 和 Spring MVC、Spring Security 5 和 Thymeleaf 3.0.11 的替代解决方案
很高兴我终于找到了解决这个问题的方法,我也想在这里分享一下:
问题:
在我的例子中,它是一个 $.ajax POST 请求到一个有效的 URL 返回 404 错误状态(未找到)。
@Mushtaq Jameel 已解释问题的最初原因是 csrf,从 Spring Security 4[开始默认启用 =46=] (source).
解决方法:
我没有测试@Mushtaq Jameel 提出的建议,但这个优雅的快速修复对我有用:
AJAX代码:
$.ajax({
type: "POST",
url: "/",
data: $('.cd-signin-modal__form.sign-up').serialize(), // <- FIX
success: // some code
error: // some code
});
}
换句话说,解决方案是在 HTML 表单本身上调用 .serialize() 方法。
然后,_csfr 令牌会自动添加 在 POST 请求中作为附加表单参数:
这又是由于 Spring 安全的较新版本默认启用了 csrf(here 提到这个隐藏的表单参数是自动添加的)。
我的 Spring MVC 控制器然后接受如下形式:
@PostMapping("/")
public String createUser(@Valid @ModelAttribute User user, @Valid @ModelAttribute UserDetails userDetails, BindingResult errors) {
if (errors.hasErrors()) {
// handle errors
}
// persist objects in database
return "index";
}