I Already tried many of the aswers about this topic, no one works for me.
I Have a basic CRUD with Spring MVC 4.1.7, Spring Security 3.2.3 working on MySQL + Tomcat7.
The problem is, when i try to POST form with AngularJS, I keep being blocked by error 403 ( access denied ).
I figured out that I need to send my CSRF_TOKEN with the POST request, but I can't figure out HOW!
I Tried so many diferent ways and no one works.
My Files
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>
UPDATE
I tried to add to rename at client side the xsrf, but I keep getting access denied.
var app = angular.module('myApp', []).config(function($httpProvider) {
$httpProvider.defaults.xsrfCookieName = '_csrf';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token';
});
** UPDATE 2 **
I tried to implement a filter like this.
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);
}
}
And changed my spring security to it.
<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>
But it is like that the server doesn't run the filter, I've added a System.out.println to it, but can't see the messages in the debug
The main problem is the integration, Angular always look for a cookie name XSRF-TOKEN and Spring sends a CSRF_TOKEN, you need to provide a filter to change this. Something like this:
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
}
NOTICE The only example I have is this, is written in Groovy, but is just missing some ;
Then you need to add the filter and the repository to <csrf/>
properties, you can explicity set a class implementing a Filter as <bean>
, using @Component
or declaring this methods as @Bean
on a configuration class
Otherwise you can change the $http
configuration on Angular, according to docs setting this settings to match Spring token cookie name and header name
xsrfHeaderName – {string} – Name of HTTP header to populate with the XSRF token.
xsrfCookieName – {string} – Name of cookie containing the XSRF token.
More info about this you can check the docs on Usage section
I did some tests and end up with this.
In my login controller, if my user authentication is successful, I create a token with the name of the AngularJS Token, just like this.
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = new Cookie("XSRF-TOKEN", csrf.getToken());
cookie.setPath("/");
response.addCookie(cookie);
}
After that, I can manage my $http.post successfully.
I don't know if this is the best way, but is the way that worked for me!
Thanks for the help @Joao Evangelista