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;
    }

}

 

 

转载于:https://www.cnblogs.com/yaomajor/p/6168760.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值