【问题标题】:angularjs and spring security give 405 error when enable csrf启用csrf时,angularjs和spring security给出405错误
【发布时间】:2015-12-01 21:40:15
【问题描述】:

我有一个 angularjs 应用程序。这一步是注册用户,所以是POST方法。提交注册表的angularjs服务如下:

 homeApp.factory('mainService', ['$http', function($http) {

        var mainService = {};

        mainService.signupArtist = function(data){
            var promise = $http.post('../user/register/artist', data) .then(function(response) {
                return response.data;
            });
            return promise;
        }

        return mainService;

    }]);

我的 spring 安全设置如下:

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security-3.2.xsd
    http://www.springframework.org/schema/util
    http://www.springframework.org/schema/util/spring-util-3.2.xsd">

    <http auto-config="false" use-expressions="true" entry-point-ref="customAuthenticationEntryPoint">

        <intercept-url pattern="/artist/**" access="hasRole('ROLE_ARTIST')" />
        <intercept-url pattern="customer/**" access="hasRole('ROLE_CUSTOMER')" />

        <!-- access denied page -->
        <access-denied-handler error-page="/user/403" />

        <logout invalidate-session="true" logout-success-url="/user/login" />
        <custom-filter ref="authenticationFilter" position="FORM_LOGIN_FILTER" />
        <!-- enable csrf protection -->
        <csrf/>
        <custom-filter after="CSRF_FILTER" ref="csrfHeaderFilter" />
        <csrf token-repository-ref="csrfTokenRepository" />
    </http>

    <!-- Select users and user_roles from database -->
    <authentication-manager alias="authenticationManager">
        <authentication-provider ref="authenticationProvider" >
        </authentication-provider>
    </authentication-manager>

    <beans:bean id="encoder"
        class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />


    <beans:bean id="customAuthenticationEntryPoint"
        class="com.tong.learn.service.security.CustomAuthenticationEntryPoint">
        <beans:property name="loginPageUrl" value="/user/login" />
        <beans:property name="returnParameterEnabled" value="true" />
        <beans:property name="returnParameterName" value="r" />
    </beans:bean>

    <beans:bean id="authenticationFilter"
        class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
        <beans:property name="authenticationManager" ref="authenticationManager" />
        <beans:property name="filterProcessesUrl" value="/j_spring_security_check" />

            <!-- change here if customize form action
        handler are for login with ajax POST -->
        <beans:property name="authenticationFailureHandler"
            ref="securityLoginFailureHandler" />
        <beans:property name="authenticationSuccessHandler"
            ref="securityLoginSuccessHandler" />
        <beans:property name="passwordParameter" value="password" />
        <beans:property name="usernameParameter" value="username" />
    </beans:bean>

    <beans:bean id="securityLoginSuccessHandler"
        class="com.tong.learn.service.security.SecurityLoginSuccessHandler">
    </beans:bean>

    <beans:bean id="securityLoginFailureHandler"
        class="com.tong.learn.service.security.SecurityLoginFailureHandler">
        <beans:property name="defaultFailureUrl" value="/user/login" />
    </beans:bean>

    <beans:bean id="csrfHeaderFilter"
        class="com.tong.learn.service.security.CsrfHeaderFilter" >
    </beans:bean>

    <beans:bean id="csrfTokenRepository" class="org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository">
        <beans:property name="headerName" value="X-XSRF-TOKEN" />
    </beans:bean>


</beans:beans>

这些来自Spring website的链接。

当我提交表单时,服务器给了我一个 405 错误,这是出乎意料的,因为我一直在本地主机上,而不是从不同的域请求一些东西。 cookie 应该是正确的。

请求和响应如下:

General
Remote Address:[::1]:8080
Request URL:http://localhost:8080/learn/user/register/artist
Request Method:POST
Status Code:405 Method Not Allowed
Response Headers
view source
Allow:GET
Content-Language:en
Content-Length:1090
Content-Type:text/html;charset=ISO-8859-1
Date:Sun, 06 Sep 2015 03:48:16 GMT
Server:Apache-Coyote/1.1
Request Headers
view source
Accept:application/json, text/plain, */*
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4
Connection:keep-alive
Content-Length:2
Content-Type:application/json;charset=UTF-8
Cookie:JSESSIONID=2E77A6690C8F56BFA8F51AF37974BA1B; XSRF-TOKEN=2dfa09e4-9201-4b14-b2d3-fea8a9117be3
Host:localhost:8080
Origin:http://localhost:8080
Referer:http://localhost:8080/learn/home/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36
X-Requested-With:XMLHttpRequest
X-XSRF-TOKEN:2dfa09e4-9201-4b14-b2d3-fea8a9117be3

我不知道出了什么问题。你能帮助我吗?谢谢。

【问题讨论】:

  • 域不是问题,405 表示您的服务器不允许您执行POST 请求。您正在遵循的教程在 groovy 文件中设置了一些标题。
  • 但是当我删除 spring-security xml 中的“”行时,它可以工作了!发生了什么事?
  • 对不起,我不知道春天。我所能做的就是指出错误的含义。
  • 我没有注意到 groovy 文件。有没有其他方法可以做到这一点?因为我对 groovy 文件一无所知...

标签: java angularjs spring-mvc spring-security


【解决方案1】:

更新:

经过一番调查,我发现我误解了 CSRF 和 CORS (https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS)。防止 CSRF 攻击很重要。我认为只有在可以通过其他方式阻止 CSRF 时,您才能绕过 CsrfFilter。

================================================ ======

我以前遇到过类似的问题。根据this post,一旦启用SCRF,所有http请求都会被CsrfFilter拦截,CsrfFilter包含一个私有类DefaultRequiresCsrfMatcher

private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
    private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");

    /* (non-Javadoc)
     * @see org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.servlet.http.HttpServletRequest)
     */
    public boolean matches(HttpServletRequest request) {
        return !allowedMethods.matcher(request.getMethod()).matches();
    }
}

对于 GET|HEAD|TRACE|OPTIONS 方法,它只会通过,而 POST 方法将根据其 CSRF 令牌进行检查。

因此,如果您不打算使用 CSRF 将您的 POST URL 暴露给其他域,只需为 CSRFFilter 实现另一个 Matcher 以绕过 POST 方法或某些 URL 模式。

【讨论】:

  • 是的,我不打算让其他域使用我的帖子网址。你的意思是我什至不需要为这些网址添加 csrf 过滤器?其实我没有其他域的帖子,所以我可以取消csrf保护吗?
  • No csrf 与域无关,用于对抗攻击者伪造请求。在您的问题中发布您的课程 com.tong.learn.service.security.CsrfHeaderFilter
  • 经过一番调查,我发现我误解了 CSRF 和 CORS (developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS)。防止 CSRF 攻击很重要。
  • 谢谢,但我发现更改 angularjs 端比更改 spring 安全端更容易。
【解决方案2】:

更新:

检查这个类似的SO 问题,其中有示例 angularjs http 拦截器直接获取 csrf 参数并在所有请求中添加标头。

默认 angularJS 的原因不能像 link 中提到的那样工作

默认情况下,AngularJS 提供了一种实现跨站请求伪造的机制,但是这种机制仅适用于 cookie。由于 Spring Security 通过将令牌设置为 HTTP 参数来工作,因此 AngularJS 提供的开箱即用解决方案将不起作用。

正如spring-security-csrf-token-interceptor 项目的文档中提到的那样——“一个在所有 HTTP 请求中设置 Spring Security CSRF 令牌信息的 AngularJS 拦截器”——它通过进行头部调用来接收 X-CSRF-TOKEN 来工作,然后存储此令牌并随每个 http 请求一起发送出去。

总结:在您的项目中添加上述项目中提到的角度拦截器来解决您的问题。检查该 github 项目中提到的示例。

【讨论】:

  • 谢谢!!我按照您提供的链接找到了这样做的方法。
猜你喜欢
  • 2014-03-03
  • 2020-03-13
  • 2018-02-18
  • 2017-11-21
  • 2015-12-10
  • 2015-12-05
  • 2016-02-20
  • 1970-01-01
  • 2018-03-05
相关资源
最近更新 更多