Spring CSRF + AngularJs
Posted
技术标签:
【中文标题】Spring CSRF + AngularJs【英文标题】: 【发布时间】:2015-10-02 01:22:00 【问题描述】:我已经尝试了很多关于这个主题的回答,没有人适合我。
我有一个基本的 CRUD 和 Spring MVC 4.1.7,Spring Security 3.2.3 在 mysql + Tomcat7 上工作。
问题是,当我尝试使用 AngularJS 发布表单时,我一直被错误 403(拒绝访问)阻止。
我发现我需要将我的 CSRF_TOKEN 与 POST 请求一起发送,但我不知道怎么做!
我尝试了很多不同的方法,但没有一个有效。
我的文件
Controller.js
$scope.novo = function novo()
if($scope.id)
alert("Update - " + $scope.id);
else
var Obj =
descricao : 'Test',
saldo_inicial : 0.00,
saldo : 33.45,
aberto : false,
usuario_id : null,
ativo : true
;
$http.post(urlBase + 'caixas/adicionar', Obj).success(function(data)
$scope.caixas = data;
).error(function(data) alert(data));
;
Spring-security.xml
<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"
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.xsd">
<!-- enable use-expressions -->
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/seguro**"
access="hasAnyRole('ROLE_USER','ROLE_ADMIN')" />
<intercept-url pattern="/seguro/financeiro**"
access="hasAnyRole('ROLE_FINANCEIRO','ROLE_ADMIN')" />
<!-- access denied page -->
<access-denied-handler error-page="/negado" />
<form-login login-page="/home/" default-target-url="/seguro/"
authentication-failure-url="/home?error" username-parameter="inputEmail"
password-parameter="inputPassword" />
<logout logout-success-url="/home?logout" />
<!-- enable csrf protection -->
<csrf />
</http>
<!-- Select users and user_roles from database -->
<authentication-manager>
<authentication-provider>
<password-encoder hash="md5" />
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="SELECT login, senha, ativo
FROM usuarios
WHERE login = ?"
authorities-by-username-query="SELECT u.login, r.role
FROM usuarios_roles r, usuarios u
WHERE u.id = r.usuario_id
AND u.login = ?" />
</authentication-provider>
</authentication-manager>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>Barattie ~ Soluções Integradas</display-name>
<!-- The definition of the Root Spring Container shared by all Servlets
and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/spring-security.xml
/WEB-INF/spring/spring-database.xml
/WEB-INF/spring/spring-hibernate.xml
</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.writeStateAtFormEnd</param-name>
<param-value>false</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/home</url-pattern>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- Spring Security -->
<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>
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/erro</location>
</error-page>
更新
我尝试在客户端添加重命名 xsrf,但我一直被拒绝访问。
var app = angular.module('myApp', []).config(function($httpProvider)
$httpProvider.defaults.xsrfCookieName = '_csrf';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token';
);
** 更新 2 **
我尝试实现这样的过滤器。
package sys.barattie.util;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.filter.OncePerRequestFilter;
public class CsrfFilter extends OncePerRequestFilter
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null)
Cookie cookie = new Cookie("XSRF-TOKEN", csrf.getToken());
cookie.setPath("/");
response.addCookie(cookie);
filterChain.doFilter(request, response);
并将我的 spring security 更改为它。
<csrf token-repository-ref="csrfTokenRepository" />
<beans:bean id="csrfTokenRepository" class="org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository">
<beans:property name="headerName" value="X-XSRF-TOKEN" />
</beans:bean>
Web.xml
<filter>
<filter-name>csrfFilter</filter-name>
<filter-class>sys.barattie.util.CsrfFilter</filter-class>
</filter>
但是好像服务器没有运行过滤器,我在里面加了一个System.out.println,但是在debug中看不到消息
【问题讨论】:
【参考方案1】:我做了一些测试,最终得到了这个。
在我的登录控制器中,如果我的用户身份验证成功,我会创建一个名为 AngularJS 令牌的令牌,就像这样。
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null)
Cookie cookie = new Cookie("XSRF-TOKEN", csrf.getToken());
cookie.setPath("/");
response.addCookie(cookie);
之后,我可以成功管理我的 $http.post。
我不知道这是否是最好的方式,但对我来说是有效的方式!
感谢@Joao Evangelista 的帮助
【讨论】:
【参考方案2】:主要问题是集成,Angular 总是寻找一个 cookie 名称 XSRF-TOKEN 而 Spring 发送一个 CSRF_TOKEN,你需要提供一个过滤器来改变它。像这样的:
private static Filter csrfHeaderFilter()
return new OncePerRequestFilter()
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName())
if (csrf != 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)
static CsrfTokenRepository csrfTokenRepository()
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository()
repository.setHeaderName("X-XSRF-TOKEN")
return repository
注意我唯一的例子就是这个,它是用 Groovy 编写的,但只是缺少一些 ;
然后您需要将过滤器和存储库添加到<csrf/>
属性,您可以将实现过滤器的类显式设置为<bean>
,使用@Component
或在配置类上将此方法声明为@Bean
否则,您可以更改 Angular 上的 $http
配置,根据文档设置此设置以匹配 Spring 令牌 cookie 名称和标头名称
xsrfHeaderName – string – Name of HTTP header to populate with the XSRF token.
xsrfCookieName – string – Name of cookie containing the XSRF token.
有关此的更多信息,您可以查看Usage 部分的文档
【讨论】:
我尝试在客户端实现 $http 配置,设置var app = angular.module('myApp', []).config(function($httpProvider) $httpProvider.defaults.xsrfCookieName = 'csrftoken'; $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken'; );
但我一直拒绝访问...我会在服务器端尝试
默认头文件名是X-CSRF-TOKEN
将 cookieName 更改为 _csrf,将标头更改为 X-CSRF-TOKEN,什么都没有...另外我尝试实现服务器端解决方案,但我无法做到,IDE 一直在尝试替换带有“OncePerRequestFilter”的“静态过滤器”
我记得_csrf
不是cookie名称,使用Chrome发出一个简单的请求,访问cookie页面,并在那里检查它,OncePerRequestFilter也实现Filter尝试删除静态修饰符,或者忽略你的 IDE
搞定了,token名字是“JSESSIONID”,设置为HttpOnly【参考方案3】:
我也遇到了这个问题,这是对我有用的解决方案(来自 Spring 文档),请注意我没有使用 $http。相反,我使用 $resource。 ' 你可以让 Spring 使用 Angular 的默认值保存一个 cookie:
<csrf token-repository-ref="tokenRepository"/>
...
</http>
<b:bean id="tokenRepository"
class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
p:cookieHttpOnly="false"/>
请注意,如果您使用 $http,配置对象中有一个名为 xsrfCookieName 的字段,您可以在其中明确设置 cookie 的名称。但是这个选项我没有试过,所以我不知道它有多大用处。
最好的问候。
【讨论】:
【参考方案4】:这应该可行:
扩展 WebSecurityConfigurerAdapter 的类:
@Override
protected void configure(HttpSecurity http) throws Exception
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
..
然后在你的 AngularJS 中你可以使用 csrf 等的任何模块:spring-security-csrf-token-interceptor
【讨论】:
以上是关于Spring CSRF + AngularJs的主要内容,如果未能解决你的问题,请参考以下文章
如何从Postman休息客户端发送spring csrf令牌?
Spring Security - CSRF - 如何重置 CSRF 令牌并记录潜在的 CSRF 攻击(OWASP 推荐)