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

网友投稿 243 2023-05-25

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

什么是csrf?

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

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

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

相关介绍:

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

spring-servlet中配置csrf

在类中声明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;

}

}

总结

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:企业工商类API 推荐
下一篇:教育文化类API推荐
相关文章

 发表评论

暂时没有评论,来抢沙发吧~