spring-security中的csrf防御机制(跨域请求伪造)
什么是csrf?
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防御机制(跨域请求伪造)的更多相关文章
- spring boot 中 2.X 的跨域请求
解决跨域: @Configuration @EnableAutoConfiguration public class ZooConfiguration { @Bean public FilterReg ...
- Spring Boot Web应用开发 CORS 跨域请求支持:
Spring Boot Web应用开发 CORS 跨域请求支持: 一.Web开发经常会遇到跨域问题,解决方案有:jsonp,iframe,CORS等等CORS与JSONP相比 1. JSONP只能实现 ...
- js中ajax如何解决跨域请求
js中ajax如何解决跨域请求,在讲这个问题之前先解释几个名词 1.跨域请求 所有的浏览器都是同源策略,这个策略能保证页面脚本资源和cookie安全 ,浏览器隔离了来自不同源的请求,防上跨域不安全的操 ...
- Django框架 之 跨域请求伪造
Django框架 之 跨域请求伪造 浏览目录 同源策略与Jsonp 同源策略 Jsonp jQuery对JSONP的实现 CORS 简介 两种请求 同源策略与Jsonp 同源策略 同源策略(Same ...
- CSRF/XSRF 跨站请求伪造
CSRF/XSRF 跨站请求伪造 CSRF(Cross Site Request Forgery, 跨站域请求伪造)也称 XSRF, 是一种网络的攻击方式,它在 2007 年曾被列为互联网 20 大安 ...
- web 安全 & web 攻防: XSS(跨站脚本攻击)和 CSRF(跨站请求伪造)
web 安全 & web 攻防: XSS(跨站脚本攻击)和 CSRF(跨站请求伪造) XSS(跨站脚本攻击)和CSRF(跨站请求伪造) Cross-site Scripting (XSS) h ...
- ValidateAntiForgeryToken 防止CSRF(跨网站请求伪造)
用途:防止CSRF(跨网站请求伪造). 用法:在View->Form表单中:<%:Html.AntiForgeryToken()%> 在Controller->Action动作 ...
- revel框架教程之CSRF(跨站请求伪造)保护
revel框架教程之CSRF(跨站请求伪造)保护 CSRF是什么?请看这篇博文“浅谈CSRF攻击方式”,说的非常清楚. 现在做网站敢不防CSRF的我猜只有两种情况,一是没什么人访问,二是局域网应用.山 ...
- CSRF(跨站请求伪造)攻击
CSRF(跨站请求伪造)攻击 CSRF(Cross Site Request Forgery,跨站请求伪造)是一种近年来才逐渐被大众了解的网络攻击方式,又被称为One-Click Attack或Ses ...
随机推荐
- 模型验证 IValidatableObject 无法进入 Validate 方法的原因
模型的属性的类型为 decimal 时无法进入 Validate 方法 public decimal Age { get; set; } 当把属性的类型改为可空类型时 则可以进入 Validate 方 ...
- luogu P3600 随机数生成器【dp】
把期望改成方案数最后除一下,设h[i]为最大值恰好是i的方案数,那么要求的就是Σh[i]*i 首先包含其他区间的区间是没有意义的,用单调栈去掉 然后恰好不好求,就改成h[i]表示最大值最大是i的方案数 ...
- HTTPS为什么更安全,请看这里
本文转载于https://foofish.net/https-story-1.html HTTPS 是建立在密码学基础之上的一种安全通信协议,严格来说是基于 HTTP 协议和 SSL/TLS 的组合. ...
- day03 System Math
- hibernate错误总结1
- robot framework 在pycharm中语法无法高亮显示的,显示绿色解决办法(Robot Framework with PyCharm)
Robot Framework with PyCharm up vote1down votefavorite 1 I am totally new to automation and trying t ...
- HTML实例之简单的网页布局
需求: <html> <head> <title>简单的表格网页布局</title> <meta charset="UTF-8" ...
- Unity Shader入门精要学习笔记 - 第8章 透明效果
转载自 冯乐乐的 <Unity Shader入门精要> 透明是游戏中经常要使用的一种效果.在实时渲染中要实现透明效果,通常会在渲染模型时控制它的透明通道.当开启透明混合后,当一个物体被渲染 ...
- 修改php默认的FastCGI模式为ISAPI模式的方法
一.到www.php.net中下载PHP的ZIP文件包.注意版本要对应. 二.将sapi目录中的:php4isapi.dll复制到c:\php目录中. 三.进入虚拟主机管理平台的"网站管理& ...
- [android][avd位置修改][需自己手动]
1.找到avd目录(C:\的那个)并打开,移动.avd文件到需要的位置. 2.修改ini文件path,完成.