• 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]:	" + 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;
        }
    
    }
     
  • 相关阅读:
    2019.8.30 玉米田
    2019暑假集训 最短路计数
    2019暑假集训 旅行计划
    2019暑假集训 文件压缩
    0033-数字和星期转换
    0032-分数等级转换
    0031-闰年判断
    0030-购买衣服
    0029-求最小的数
    0028-判断奇偶
  • 原文地址:https://www.cnblogs.com/yaomajor/p/6168760.html
Copyright © 2020-2023  润新知