spring security中的csrf防御原理(跨域请求伪造)

什么是csrf?

csrf又称跨域请求伪造,攻击方通过伪造用户请求访问受信任站点。CSRF这种攻击方式在2000年已经被国外的安全人员提出,但在国内,直到06年才开始被关注,08年,国内外的多个大型社区和交互网站分别爆出CSRF漏洞,如:NYTimes.com(纽约时报)、Metafilter(一个大型的BLOG网站),YouTube和百度HI......而现在,互联网上的许多站点仍对此毫无防备,以至于安全业界称CSRF为“沉睡的巨人”。

举个例子,用户通过表单发送请求到银行网站,银行网站获取请求参数后对用户账户做出更改。在用户没有退出银行网站情况下,访问了攻击网站,攻击网站中有一段跨域访问的代码,可能自动触发也可能点击提交按钮,访问的url正是银行网站接受表单的url。因为都来自于用户的浏览器端,银行将请求看作是用户发起的,所以对请求进行了处理,造成的结果就是用户的银行账户被攻击网站修改。

解决方法基本上都是增加攻击网站无法获取到的一些表单信息,比如增加图片验证码,可以杜绝csrf攻击,但是除了登陆注册之外,其他的地方都不适合放验证码,因为降低了网站易用性

相关介绍:

http://baike.baidu.com/view/1609487.htm?fr=aladdin

spring-servlet中配置csrf

 <!-- Spring csrf 拦截器 -->
 <mvc:interceptors>
  <mvc:interceptor>
   <mvc:mapping path="/login" />
   <bean class="com.wangzhixuan.commons.csrf.CsrfInterceptor" />
  </mvc:interceptor>
 </mvc:interceptors>

在类中声明Csrf拦截器,用来生成或去除CsrfToken

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.wangzhixuan.commons.scan.ExceptionResolver;
import com.wangzhixuan.commons.utils.WebUtils;

/**
 * Csrf拦截器,用来生成或去除CsrfToken
 *
 * @author L.cm
 */
public class CsrfInterceptor extends HandlerInterceptorAdapter {
 private static final Logger logger = LogManager.getLogger(ExceptionResolver.class);

 @Autowired
 private CsrfTokenRepository csrfTokenRepository;

 @Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  HandlerMethod handlerMethod = (HandlerMethod) handler;
  // 非控制器请求直接跳出
  if (!(handler instanceof HandlerMethod)) {
   return true;
  }
  CsrfToken csrfToken = handlerMethod.getMethodAnnotation(CsrfToken.class);
  // 判断是否含有@CsrfToken注解
  if (null == csrfToken) {
   return true;
  }
  // create、remove同时为true时异常
  if (csrfToken.create() && csrfToken.remove()) {
   logger.error("CsrfToken attr create and remove can Not at the same time to true!");
   return renderError(request, response, Boolean.FALSE, "CsrfToken attr create and remove can Not at the same time to true!");
  }
  // 创建
  if (csrfToken.create()) {
   CsrfTokenBean token = csrfTokenRepository.generateToken(request);
   csrfTokenRepository.saveToken(token, request, response);
   // 缓存一个表单页面地址的url
   csrfTokenRepository.cacheUrl(request, response);
   request.setAttribute(token.getParameterName(), token);
   return true;
  }
  // 判断是否ajax请求
  boolean isAjax = WebUtils.isAjax(handlerMethod);
  // 校验,并且清除
  CsrfTokenBean tokenBean = csrfTokenRepository.loadToken(request);
  if (tokenBean == null) {
   return renderError(request, response, isAjax, "CsrfToken is null!");
  }
  String actualToken = request.getHeader(tokenBean.getHeaderName());
  if (actualToken == null) {
   actualToken = request.getParameter(tokenBean.getParameterName());
  }
  if (!tokenBean.getToken().equals(actualToken)) {
   return renderError(request, response, isAjax, "CsrfToken not eq!");
  }
  return true;
 }

 private boolean renderError(HttpServletRequest request, HttpServletResponse response,
   boolean isAjax, String message) throws IOException {
  // 获取缓存的cacheUrl
  String cachedUrl = csrfTokenRepository.getRemoveCacheUrl(request, response);
  // ajax请求直接抛出异常,因为{@link ExceptionResolver}会去处理
  if (isAjax) {
   throw new RuntimeException(message);
  }
  // 非ajax CsrfToken校验异常,先清理token
  csrfTokenRepository.saveToken(null, request, response);
  logger.info("Csrf[redirectUrl]:\t" + cachedUrl);
  response.sendRedirect(cachedUrl);
  return false;
 }

 /**
  * 用于清理@CsrfToken保证只能请求成功一次
  */
 @Override
 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
   ModelAndView modelAndView) throws Exception {
  HandlerMethod handlerMethod = (HandlerMethod) handler;
  // 非控制器请求直接跳出
  if (!(handler instanceof HandlerMethod)) {
   return;
  }
  CsrfToken csrfToken = handlerMethod.getMethodAnnotation(CsrfToken.class);
  if (csrfToken == null || !csrfToken.remove()) {
   return;
  }
  csrfTokenRepository.getRemoveCacheUrl(request, response);
  csrfTokenRepository.saveToken(null, request, response);
 }

}

声明Csrf过滤注解,通过标注来过滤对应的请求

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Csrf过滤注解
 * @author L.cm
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CsrfToken {
 boolean create() default false;
 boolean remove() default false;
}

建立实例对象(操作对象)

import java.io.Serializable;
import org.springframework.util.Assert;
public class CsrfTokenBean implements Serializable {
 private static final long serialVersionUID = -6865031901744243607L;
 private final String token;
 private final String parameterName;
 private final String headerName;
 /**
  * Creates a new instance
  * @param headerName the HTTP header name to use
  * @param parameterName the HTTP parameter name to use
  * @param token the value of the token (i.e. expected value of the HTTP parameter of
  * parametername).
  */
 public CsrfTokenBean(String headerName, String parameterName, String token) {
  Assert.hasLength(headerName, "headerName cannot be null or empty");
  Assert.hasLength(parameterName, "parameterName cannot be null or empty");
  Assert.hasLength(token, "token cannot be null or empty");
  this.headerName = headerName;
  this.parameterName = parameterName;
  this.token = token;
 }
 public String getHeaderName() {
  return this.headerName;
 }
 public String getParameterName() {
  return this.parameterName;
 }
 public String getToken() {
  return this.token;
 }
}

过滤过程中需要的仓库

package com.wangzhixuan.commons.csrf;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface CsrfTokenRepository {
 /**
  * Generates a {@link CsrfTokenBean}
  *
  * @param request the {@link HttpServletRequest} to use
  * @return the {@link CsrfTokenBean} that was generated. Cannot be null.
  */
 CsrfTokenBean generateToken(HttpServletRequest request);
 /**
  * Saves the {@link CsrfTokenBean} using the {@link HttpServletRequest} and
  * {@link HttpServletResponse}. If the {@link CsrfTokenBean} is null, it is the same as
  * deleting it.
  *
  * @param token the {@link CsrfTokenBean} to save or null to delete
  * @param request the {@link HttpServletRequest} to use
  * @param response the {@link HttpServletResponse} to use
  */
 void saveToken(CsrfTokenBean token, HttpServletRequest request,
   HttpServletResponse response);
 /**
  * Loads the expected {@link CsrfTokenBean} from the {@link HttpServletRequest}
  *
  * @param request the {@link HttpServletRequest} to use
  * @return the {@link CsrfTokenBean} or null if none exists
  */
 CsrfTokenBean loadToken(HttpServletRequest request);
 /**
  * 缓存来源的url
  * @param request request the {@link HttpServletRequest} to use
  * @param response the {@link HttpServletResponse} to use
  */
 void cacheUrl(HttpServletRequest request, HttpServletResponse response);
 /**
  * 获取并清理来源的url
  * @param request the {@link HttpServletRequest} to use
  * @param response the {@link HttpServletResponse} to use
  * @return 来源url
  */
 String getRemoveCacheUrl(HttpServletRequest request, HttpServletResponse response);
}

HttpSessionCsrfTokenRepository

package com.wangzhixuan.commons.csrf;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.wangzhixuan.commons.utils.StringUtils;
public final class HttpSessionCsrfTokenRepository implements CsrfTokenRepository {
 private static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";
 private static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN";
 private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = HttpSessionCsrfTokenRepository.class
   .getName().concat(".CSRF_TOKEN");
 private static final String DEFAULT_CACHE_URL_ATTR_NAME = HttpSessionCsrfTokenRepository.class
   .getName().concat(".CACHE_URL");
 private String parameterName = DEFAULT_CSRF_PARAMETER_NAME;
 private String headerName = DEFAULT_CSRF_HEADER_NAME;
 private String sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME;
 private String cacheUrlAttributeName = DEFAULT_CACHE_URL_ATTR_NAME;
 /*
  * (non-Javadoc)
  *
  * @see org.springframework.security.web.csrf.CsrfTokenRepository#saveToken(org.
  * springframework .security.web.csrf.CsrfToken,
  * javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
  */
 public void saveToken(CsrfTokenBean token, HttpServletRequest request,
   HttpServletResponse response) {
  if (token == null) {
   HttpSession session = request.getSession(false);
   if (session != null) {
    session.removeAttribute(this.sessionAttributeName);
   }
  }
  else {
   HttpSession session = request.getSession();
   session.setAttribute(this.sessionAttributeName, token);
  }
 }
 /*
  * (non-Javadoc)
  *
  * @see
  * org.springframework.security.web.csrf.CsrfTokenRepository#loadToken(javax.servlet
  * .http.HttpServletRequest)
  */
 public CsrfTokenBean loadToken(HttpServletRequest request) {
  HttpSession session = request.getSession(false);
  if (session == null) {
   return null;
  }
  return (CsrfTokenBean) session.getAttribute(this.sessionAttributeName);
 }
 /*
  * (non-Javadoc)
  *
  * @see org.springframework.security.web.csrf.CsrfTokenRepository#generateToken(javax.
  * servlet .http.HttpServletRequest)
  */
 public CsrfTokenBean generateToken(HttpServletRequest request) {
  return new CsrfTokenBean(this.headerName, this.parameterName,
    createNewToken());
 }
 private String createNewToken() {
  return UUID.randomUUID().toString();
 }
 @Override
 public void cacheUrl(HttpServletRequest request, HttpServletResponse response) {
  String queryString = request.getQueryString();
  // 被拦截前的请求URL
  String redirectUrl = request.getRequestURI();
  if (StringUtils.isNotBlank(queryString)) {
   redirectUrl = redirectUrl.concat("?").concat(queryString);
  }
  HttpSession session = request.getSession();
  session.setAttribute(this.cacheUrlAttributeName, redirectUrl);
 }
 @Override
 public String getRemoveCacheUrl(HttpServletRequest request, HttpServletResponse response) {
  HttpSession session = request.getSession(false);
  if (session == null) {
   return null;
  }
  String redirectUrl = (String) session.getAttribute(this.cacheUrlAttributeName);
  if (StringUtils.isBlank(redirectUrl)) {
   return null;
  }
  session.removeAttribute(this.cacheUrlAttributeName);
  return redirectUrl;
 }
}

总结

以上所述是小编给大家介绍的spring security中的csrf防御原理(跨域请求伪造),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

(0)

相关推荐

  • 详解利用spring-security解决CSRF问题

    CSRF介绍 CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF. 具体SCRF的介绍和攻击方式请参看百度百科的介绍和一位大牛的分析: CSRF百度百科 浅谈CSRF攻击方式 配置步骤 1.依赖jar包 <properties> <spring.security.version>4.2.2.RELEASE</spring.security

  • SpringSecurity框架下实现CSRF跨站攻击防御的方法

    一.什么是CSRF 很多朋友在学习Spring Security的时候,会将CORS(跨站资源共享)和CSRF(跨站请求伪造)弄混,以为二者是一回事.其实不是,先解释一下: CORS(跨站资源共享)是局部打破同源策略的限制,使在一定规则下HTTP请求可以突破浏览器限制,实现跨站访问. CSRF是一种网络攻击方式,也可以说是一种安全漏洞,这种安全漏洞在web开发中广泛存在. 当我们使用Spring Security的时候,这种CSRF漏洞默认的被防御掉了.但是你会发现在跨域请求的情况下,我们的PO

  • 使用SpringSecurity处理CSRF攻击的方法步骤

    CSRF漏洞现状 CSRF(Cross-site request forgery)跨站请求伪造,也被称为One Click Attack或者Session Riding,通常缩写为CSRF或XSRF,是一种对网站的恶意利用.尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装成受信任用户的请求来利用受信任的网站.与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性. CSRF是一种

  • spring security CSRF防护的示例代码

    CSRF是指跨站请求伪造(Cross-site request forgery),是web常见的攻击之一. 从Spring Security 4.0开始,默认情况下会启用CSRF保护,以防止CSRF攻击应用程序,Spring Security CSRF会针对PATCH,POST,PUT和DELETE方法进行防护. 我这边是spring boot项目,在启用了@EnableWebSecurity注解后,csrf保护就自动生效了. 所以在默认配置下,即便已经登录了,页面中发起PATCH,POST,P

  • 详解如何在spring boot中使用spring security防止CSRF攻击

    CSRF是什么? CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF.  CSRF可以做什么? 你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求.CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......造成的问题包括:个人隐私泄露以及财产安全. CSRF漏洞现状 CSRF这种攻击方式

  • spring security中的csrf防御原理(跨域请求伪造)

    什么是csrf? csrf又称跨域请求伪造,攻击方通过伪造用户请求访问受信任站点.CSRF这种攻击方式在2000年已经被国外的安全人员提出,但在国内,直到06年才开始被关注,08年,国内外的多个大型社区和交互网站分别爆出CSRF漏洞,如:NYTimes.com(纽约时报).Metafilter(一个大型的BLOG网站),YouTube和百度HI......而现在,互联网上的许多站点仍对此毫无防备,以至于安全业界称CSRF为"沉睡的巨人". 举个例子,用户通过表单发送请求到银行网站,银行

  • Spring Security拦截器引起Java CORS跨域失败的问题及解决

    在已设置CORS的项目中加入Spring Security,导致跨域访问失败,一开始以为是设置错CORS的问题,后来才发现是因为Spring Security的拦截冲突引起的. (一) CORS介绍 CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing). 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制. response响应头 响应头字段名称 作用 Access-Contro

  • SpringSecurity跨域请求伪造(CSRF)的防护实现

    目录 一.CSRF 二.攻击过程 三.防御手段 1.HTTP Referer 2.CsrfToken认证 三.使用SpringSecurity防御CSRF 1.SpringSecurity防御CSRF过程 2.SpringSecurity配置CSRF 3.启动项目测试 一.CSRF CSRF的全称是(Cross Site Request Forgery),可译为跨域请求伪造,是一种利用用户带登录 态的cookie进行安全操作的攻击方式.CSRF实际上并不难防,但常常被系统开发者忽略,从而埋下巨

  • 详解Spring Boot 2.0.2+Ajax解决跨域请求的问题

    问题描述 后端域名为A.abc.com,前端域名为B.abc.com.浏览器在访问时,会出现跨域访问.浏览器对于javascript的同源策略的限制. HTTP请求时,请求本身会返回200,但是返回结果不会走success,并且会在浏览器console中提示: 已拦截跨源请求:同源策略禁止读取位于 https://www.baidu.com/ 的远程资源.(原因:CORS 头缺少 'Access-Control-Allow-Origin'). 解决方案 1.jsonp 2.引用A站的js 3.N

  • CSRF跨站请求伪造漏洞分析与防御

    目录 CSRF 漏洞原理 漏洞危害 防御绕过 漏洞利用 防御措施 总结 CSRF 现在的网站都有利用CSRF令牌来防止CSRF,就是在请求包的字段加一个csrf的值,防止csrf,要想利用该漏洞,要和xss组合起来,利用xss获得该csrf值,在构造的请求中将csrf值加进去,就可以绕过csrf防御,利用该漏洞. 该漏洞与xss的区别:xss是通过执行恶意脚本,获取到用户的cookie等信息,再利用cookie等信息进行绕过登录限制,做一些用户可以做的事. 而csrf是伪造请求,让用户自己执行攻

  • 详解SpringBoot多跨域请求的支持(JSONP)

    在我们做项目的过程中,有可能会遇到跨域请求,所以需要我们自己组装支持跨域请求的JSONP数据,而在4.1版本以后的SpringMVC中,为我们提供了一个AbstractJsonpResponseBodyAdvice的类用来支持jsonp的数据(SpringBoot接收解析web请求是依赖于SpringMVC实现的).下面我们就看一下怎么用AbstractJsonpResponseBodyAdvice来支持跨域请求. 使用AbstractJsonpResponseBodyAdvice来支持跨域请求

  • Django中如何防范CSRF跨站点请求伪造攻击的实现

    CSRF概念 CSRF跨站点请求伪造(Cross-Site Request Forgery). 攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件.发消息,盗取你的账号,添加系统管理员,甚至于购买商品.虚拟货币转账等. CSRF攻击原理以及过程 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A: 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常

  • 详解Spring Security中的HttpBasic登录验证模式

    一.HttpBasic模式的应用场景 HttpBasic登录验证模式是Spring Security实现登录验证最简单的一种方式,也可以说是最简陋的一种方式.它的目的并不是保障登录验证的绝对安全,而是提供一种"防君子不防小人"的登录验证. 就好像是我小时候写日记,都买一个带小锁头的日记本,实际上这个小锁头有什么用呢?如果真正想看的人用一根钉子都能撬开.它的作用就是:某天你的父母想偷看你的日记,拿出来一看还带把锁,那就算了吧,怪麻烦的. 举一个我使用HttpBasic模式的进行登录验证的

  • Spring Boot和Vue跨域请求问题原理解析

    这篇文章主要介绍了Spring Boot和Vue跨域请求问题原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 使用Spring Boot + Vue 做前后端分离项目搭建,实现登录时,出现跨域请求 Access to XMLHttpRequest at 'http://localhost/open/login' from origin 'http://localhost:8080' has been blocked by CORS pol

  • Spring Boot 中该如何防御计时攻击

    松哥最近在研究 Spring Security 源码,发现了很多好玩的代码,抽空写几篇文章和小伙伴们分享一下. 很多人吐槽 Spring Security 比 Shiro 重量级,这个重量级不是凭空来的,重量有重量的好处,就是它提供了更为强大的防护功能. 比如松哥最近看到的一段代码: protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authenticatio

随机推荐