Creating a CSRF protection with Spring 3.x--reference

时间:2022-09-25 08:41:06

reference from:http://info.michael-simons.eu/2012/01/11/creating-a-csrf-protection-with-spring-3-1/

Creating a CSRF protection with Spring 3.1

CSRF Attacks still seems to be a problem, a pity that there is no standard solution in the Spring 3.1 framework. Although not probably, i wanted to protect my projects by malicious crafted links.

I didn’t want to use an extra library but something which is already available in the Spring framework. Here is what i come up with:

I choose the token protection mechanism for my implementation.

The core of my solution is the CSRFToken Service:

public interface CSRFTokenService {
public final static String TOKEN_PARAMETER_NAME = "_tk";
 
public final static String TOKEN_ATTRIBUTE_NAME = "CSRFToken";
 
public final static List<String> METHODS_TO_CHECK = Collections.unmodifiableList(Arrays.asList("POST", "PUT", "DELETE"));
 
/** Generates a new CSRF Protection token */
public String generateToken();
 
/** Obtains the token from the session. If there is no token, a new one will be generated */
public String getTokenFromSession(final HttpServletRequest request);
 
/** This method tests, if a token is acceptable when a user is logged in */
public boolean acceptsTokenIn(HttpServletRequest request);
}
import java.security.SecureRandom;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
 
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
 
import de.dailyfratze.services.CSRFTokenService;
 
@Service("csrfTokenService")
public class CSRFTokenServiceImpl implements CSRFTokenService {
private final SecureRandom random = new SecureRandom();
 
@Override
public String generateToken() {
final byte[] bytes = new byte[32];
random.nextBytes(bytes);
return Base64.encodeBase64URLSafeString(bytes);
}
 
@Override
public String getTokenFromSession(final HttpServletRequest request) {
return request.getUserPrincipal() == null ? null : this.getTokenFromSessionImpl(request.getSession(false));
}
 
private String getTokenFromSessionImpl(final HttpSession session) {
String token = null;
 
if(session != null) {
token = (String) session.getAttribute(TOKEN_ATTRIBUTE_NAME);
if(StringUtils.isBlank(token))
session.setAttribute(TOKEN_ATTRIBUTE_NAME, (token = generateToken()));
}
return token;
}
 
@Override
public boolean acceptsTokenIn(HttpServletRequest request) {
boolean rv = false;
 
// Token is only verified if principal is not null
if(request.getUserPrincipal() == null)
rv = true;
else {
final HttpSession session = request.getSession(false);
rv = session != null && this.getTokenFromSessionImpl(session).equals(request.getParameter(TOKEN_PARAMETER_NAME));
}
return rv;
}
}

“getTokenFromSession” is called right after a user logs in, so that the token gets stored into his session.

As you can see in the implementation of “acceptsTokenIn”, the token is only needed and verified when the principal is not null, meaning when a user is authenticated.

The interface contains some constants: The name of the token in forms and requests and the name of the attribute under which the token is stored in the session. The token itself is just a base64 of some random bytes.

I only want the token to be checked in writing methods: METHODS_TO_CHECK, meaning only in put, delete and posts requests. My applications don’t change state based on get requests.

So where to check for the token? I use a pretty simple Spring “HandlerInterceptor”:

package de.dailyfratze.controller;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
 
import de.dailyfratze.services.CSRFTokenService;
 
public class CSRFInterceptor implements HandlerInterceptor {
@Autowired
private CSRFTokenService csrfTokenService;
 
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
boolean rv = true;
if(CSRFTokenService.METHODS_TO_CHECK.contains(StringUtils.defaultIfBlank(request.getMethod(), "").toUpperCase()) && !csrfTokenService.acceptsTokenIn(request)) {
response.addHeader("X-DailyFratze-InvalidCSRFToken", Boolean.toString(true));
response.sendError(HttpServletResponse.SC_FORBIDDEN);
rv = false;
}
return rv;
}
 
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
 
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}

This interceptor stops the chain if the request method should be checked and the token is not acceptable by sending a HTTP forbidden error. The additional response header is used by Ajax calls to present a dialog that the session is invalidated.

How to get the token into forms? I wanted to be able to change the token name in only one place so i came up with the following custom tag:

import java.io.IOException;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
 
import org.apache.commons.lang.StringUtils;
 
import de.dailyfratze.services.CSRFTokenService;
import de.dailyfratze.utils.HelperRegistry;
 
/**
* Creates a hidden input field with the CSRF Token
* @author michael.simons, 2011-09-20
*/
public class CSRFTokenTag extends TagSupport {
private static final long serialVersionUID = 745177955805541350L;
 
private boolean plainToken = false;
 
@Override
public int doStartTag() throws JspException {
final CSRFTokenService csrfTokenService = HelperRegistry.getHelper(super.pageContext.getServletContext(), super.pageContext.getRequest(), CSRFTokenService.class, "csrfTokenService");
final String token = csrfTokenService.getTokenFromSession((HttpServletRequest) super.pageContext.getRequest());
if(!StringUtils.isBlank(token))
try {
if(plainToken)
pageContext.getOut().write(token);
else
pageContext.getOut().write(String.format("<input type=\"hidden\" name=\"%1$s\" id=\"%1$s\" value=\"%2$s\" />", CSRFTokenService.TOKEN_PARAMETER_NAME, token));
} catch (IOException e) {
}
return SKIP_BODY;
}
 
@Override
public int doEndTag() throws JspException {
return EVAL_PAGE;
}
 
public boolean isPlainToken() {
return plainToken;
}
 
public void setPlainToken(boolean plainToken) {
this.plainToken = plainToken;
}
 
public static String getTokenParameterName() {
return CSRFTokenService.TOKEN_PARAMETER_NAME;
}
}

with the corresponding mapping:

<?xml version="1.0" encoding="UTF-8" ?>
<taglib 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-jsptaglibrary_2_1.xsd"
version="2.1">
<tlib-version>1.0</tlib-version>
<short-name>df</short-name>
<uri>http://michael-simons.eu/taglibs/df</uri>
<tag>
<name>csrfToken</name>
<tag-class>de.dailyfratze.tags.CSRFTokenTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>plainToken</name>
<required>false</required>
</attribute>
</tag>
<function>
<name>csrfTokenParameter</name>
<function-class>de.dailyfratze.tags.CSRFTokenTag</function-class>
<function-signature>java.lang.String getTokenParameterName()</function-signature>
</function>
</taglib>

I can use this tag in forms like so:

<form method="post" action="foobar">
<df:csrfToken />
</form>

Or for generating url parameters for example for ajax calls like so:

<c:url value="/foobar">
<c:param name="${df:csrfTokenParameter()}">
<df:csrfToken plainToken="true"/>
</c:param>
</c:url>

So if a token is invalid, the user is either redirect to an error page if it is a normal post, ajax calls through jQuery can be handled like so:

function isInvalidCSRFToken = function(xhr) {
var rv = false;
if(xhr.status == 403 && xhr.getResponseHeader('X-DailyFratze-InvalidCSRFToken') == 'true') {
alert($('Session is invalid').text());
rv = true;
}
return rv;
}
 
$.ajax({
type: 'post',
url: theUrl,
dataType: 'text',
complete: function(xhr, status) {
if(isInvalidCSRFToken(xhr))
return;
// handle the result
}
});

The code snippets are all taken from a running project. If you want to use them, use them. The package names are missing and must be added. Also the JavaScript code isn’t complete.

Feel free to comment, if you have suggestions, remarks or anything else. Also, if you can use this, i’d be happy to hear from you.

---------------------------------------------------------------------------------------------------------------------------------------------------------

CSRF PROTECTION WITH SPRING SECURITY REVISITED

reference from:http://info.michael-simons.eu/2014/01/29/csrf-protection-with-spring-security-revisited/

At the end of last year, Spring Security 3.2 was released and brought a lot of new features, among them a built-in “Cross Site Request Forgery” protection”.

Nearly two years earlier i wrote my CSRF protection implementation with Spring Security 3.1, have a look here.

I really like the built-in implementation and most of it is very similar to my solution. The main difference is that the protection is at security filter level and not at application level like mine was. Also they use a token class for encapsulating the tokens name and value.

My solution can be very easily adapted to Spring Security 3.2.

First of all, configure it with the onliner

<csrf  />

or use the new annotation based configuration method.

Then throw away everything from my solution except the CSRFTokenTag. Edit the later one to contain the following code:

import java.io.IOException;
import java.util.Random;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
 
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
 
/**
* Creates a hidden input field with the CSRF Token
* @author michael.simons, 2011-09-20
*/
@Configurable
public class CSRFTokenTag extends TagSupport {
private final static Random random = new Random(System.currentTimeMillis());
 
private static final long serialVersionUID = 745177955805541350L;
 
private boolean plainToken = false;
private String elementId;
 
@Autowired
private CsrfTokenRepository csrfTokenRepository;
 
@Override
public int doStartTag() throws JspException {
final CsrfToken token = csrfTokenRepository.loadToken((HttpServletRequest) super.pageContext.getRequest());
if(token != null)
try {
if(plainToken)
pageContext.getOut().write(token.getToken());
else
pageContext.getOut().write(String.format("<input type=\"hidden\" name=\"%s\" id=\"%s\" value=\"%s\" />", token.getParameterName(), StringUtils.isNotBlank(this.elementId) ? this.elementId : String.format("%s_%d", token.getParameterName(), random.nextInt()), token.getToken()));
} catch (IOException e) {
}
return SKIP_BODY;
}
 
@Override
public int doEndTag() throws JspException {
return EVAL_PAGE;
}
 
public boolean isPlainToken() {
return plainToken;
}
 
public void setPlainToken(boolean plainToken) {
this.plainToken = plainToken;
}
 
public String getElementId() {
return elementId;
}
 
public void setElementId(String elementId) {
this.elementId = elementId;
}
}

The guys at Spring have a nice suggestions for including the token for AJAX/Jsons request. The new filter also validates request headers. The recommend adding the header name and token value to the pages meta information like so

  <meta name="_csrf" content="${_csrf.token}"/>
<meta name="_csrf_header" content="${_csrf.headerName}"/>

and then manually add the header to each JavaScript request made with jQuery.

An alternative for jQuery users would be the following pre filter:

$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
jqXHR.setRequestHeader(header, token);
});

I’m happy to be able to get rid of some code of mine, though the solution worked quite well for 2 years now.

Creating a CSRF protection with Spring 3.x--reference的更多相关文章

  1. How to resolve CSRF protection error while adding service through Ambari api

    Short Description: This article will describe on how to disable CSRF protection in Ambari. Article A ...

  2. HTML form without CSRF protection&comma;HTML表单没有CSRF保护

    HTML form without CSRF protection =HTML表单没有CSRF保护 CSRF是伪造客户端请求的一种攻击,CSRF的英文全称是Cross Site Request For ...

  3. springMVC常见错误-解决org&period;springframework&period;beans&period;factory&period;BeanCreationException&colon; Error creating bean with name &&num;39&semi;org&period;spring

    笔者参考文档: https://blog.csdn.net/sinat_24928447/article/details/47807105 可能错误原因即解决方法: 1.配置文件错误 a)这是配置文件 ...

  4. spring mybatis circular reference

    摘要: Error creating bean with name 'XXX': Requested bean is currently in creation: Is there an unreso ...

  5. Spring Boot Admin Reference Guide

    1. What is Spring Boot Admin? Spring Boot Admin is a simple application to manage and monitor your S ...

  6. Spring错误——Spring 注解——factory-bean reference points back to the same bean definition

    背景:学习Spring,在使用注解@Bean的name属性配置<bean>实例时,不能注册实例成功 报错 WARNING: Exception encountered during con ...

  7. 【Spring实战】----开篇(包含系列目录链接)

    [Spring实战]----开篇(包含系列目录链接) 置顶2016年11月10日 11:12:56 阅读数:3617 终于还是要对Spring进行解剖,接下来Spring实战篇系列会以应用了Sprin ...

  8. Cross Site Request Forgery &lpar;CSRF&rpar;--spring security -转

    http://docs.spring.io/spring-security/site/docs/3.2.0.CI-SNAPSHOT/reference/html/csrf.html 13. Cross ...

  9. Spring Security Oauth2 &colon; Possible CSRF detected

    Spring Security Oauth2 : Possible CSRF detected 使用Spring Security 作为 Oauth2 授权服务器时,在授权服务器登录授权后,重定向到客 ...

随机推荐

  1. 3n&plus;1问题

    猜想: 对于任意大于1的自然数n,若n为奇数,则将n变为3n+1,否则变为n的一半. 经过若干次这样的变换,一定会使n变为1.例如3->10->5->16->8->2-& ...

  2. SourceTree克隆仓库时,总是提示输入密码

    1.SourceTree的“工具”-“选项”-“一般”,配置上SSH秘钥: 2.将SSH密钥,配置到GitLab里: 3.仓库的“源路径 / URL”,填写SSH地址:

  3. ScrollView 尽量避免嵌套RelativeLayout&comma;非常惨痛的教训

    <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android=&q ...

  4. Linux 安装Maven和nexus代理仓库

    1    说明 2    安装步骤 2.1      下载地址 2.2      MAVEN安装步骤 2.2.1   解压Maven安装包 2.2.2  配置环境变量 2.3      Sonatyp ...

  5. c&plus;&plus; 名字粉碎&lpar;name mangling&rpar;

    转自Ibm: Name mangling is the encoding of function and variable names into unique names so that linker ...

  6. 自己动手做聊天机器人 二十九-重磅:近1GB的三千万聊天语料供出

    Reference: http://www.shareditor.com/blogshow/?blogId=112 经过半个月的倾力打造,建设好的聊天语料库包含三千多万条简体中文高质量聊天语料,近1G ...

  7. postman 第1节 安装启动&lpar;转&rpar;

    安装: 1.mac app安装 浏览器访问https://www.getpostman.com/apps,选择Get the Mac App,下载安装即可 2.chrome app安装 浏览器访问ht ...

  8. 关于MySQL什么时候使用索引问题以及什么情况下应不建或少建索引

    一,什么情况下使用索引1. 表的主关键字 自动建立唯一索引 2. 表的字段唯一约束 ORACLE利用索引来保证数据的完整性 3. 直接条件查询的字段 在SQL中用于条件约束的字段 如zl_yhjbqk ...

  9. python深度学习库keras——安装

    TensorFlow安装keras需要在TensorFlow之上才能运行.所以这里安装TensorFlow.TensorFlow需要vs2015环境,需要wein64位环境,所以32位的小伙伴需要升级 ...

  10. Shell sleep指定延迟时间

    可以给时间,让上一条命令执行完毕后,并且退出 sleep 1 睡眠1秒sleep 1s 睡眠1秒sleep 1m 睡眠1分sleep 1h 睡眠1小时