package org.fenixedu.bennu.spring.security;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.fenixedu.bennu.core.security.SkipCSRF;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
/**
* Interceptor to prevent CSRF attacks on POST, PUT and DELETE methods.
*
* Individual controller methods may choose to bypass this verification by annotating the method with {@link SkipCSRF}.
*
* It will check that any request in the matching conditions contains a {@link CSRFToken} associated with it, either by providing
* a request parameter or header. A token may be obtained via the {@link CSRFTokenRepository#getToken(HttpServletRequest)} method.
*
* @author João Carvalho (joao.pedro.carvalho@tecnico.ulisboa.pt)
*
* @see CSRFToken
* @see CSRFTokenRepository
*
*/
public class CSRFInterceptor implements HandlerInterceptor {
private static final Set<String> METHODS_TO_FILTER = ImmutableSet.of("post", "put", "delete");
private final CSRFTokenRepository tokenRepository;
public CSRFInterceptor(CSRFTokenRepository tokenBean) {
this.tokenRepository = tokenBean;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Checks if the request is in conditions to check for CSRF token
if (METHODS_TO_FILTER.contains(request.getMethod().toLowerCase()) && shouldValidateCSRFToken(handler)) {
// Acquire the expected and the provided token...
CSRFToken token = tokenRepository.getToken(request);
String tokenValue = findToken(token, request);
// ... and compare them
if (Strings.isNullOrEmpty(tokenValue) || !tokenValue.equals(token.getToken())) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "CSRF Token not present or incorrect!");
return false;
}
}
return true;
}
/**
* Determines if the given method should be validated for a CSRF Token.
*/
private boolean shouldValidateCSRFToken(Object handler) {
return !((HandlerMethod) handler).getMethod().isAnnotationPresent(SkipCSRF.class);
}
/*
* Retrieving the token provided by the user, either via header or parameter.
*/
private String findToken(CSRFToken token, HttpServletRequest request) {
String value = request.getParameter(token.getParameterName());
if (Strings.isNullOrEmpty(value)) {
value = request.getHeader(token.getHeaderName());
}
return value;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
// Nothing to do here
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// Nothing to do here
}
}