程序员Java

Spring Security 源码之 Filter Part

2020-11-20  本文已影响0人  AlienPaul

ChannelProcessingFilter

判断哪些请求适用于HTTPS或HTTP协议,或者不受约束,并自动跳转到配置约定的通道。

@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;
    // FilterInvocation是一个容器类。包装了request,response和filterChain
    FilterInvocation filterInvocation = new FilterInvocation(request, response, chain);
    // 返回这个request是否要求是SECURE_CHANNEL还是INSECURE_CHANNEL,通过RequestMatcher匹配request
    // securityMetadataSource,包含了requestMatcher和ConfigAttribute的映射关系
    // 这里获取匹配这个request的requestMatcher所对应的ConfigAttribute
    // ConfigAttribute包含ANY_CHANNEL,REQUIRES_SECURE_CHANNEL和REQUIRES_INSECURE_CHANNEL
    // 分别表示任意通道(不切换),要求安全通道和要求非安全通道
    Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(filterInvocation);
    // 如果这个request有相关配置
    if (attributes != null) {
        this.logger.debug(LogMessage.format("Request: %s; ConfigAttributes: %s", filterInvocation, attributes));
        // 判断filterInvocation是否满足配置
        // ChannelDecisionManager包含了ChannelProcessor
        // ChannelProcessor有InsecureChannelProcessor和SecureChannelProcessor两个子类
        // channelProcessor会根据request的配置,跳转到HTTPS或者是HTTP通道
        this.channelDecisionManager.decide(filterInvocation, attributes);
        // 如果response已经提交,直接返回
        if (filterInvocation.getResponse().isCommitted()) {
            return;
        }
    }
    chain.doFilter(request, response);
}

CurrentSessionFilter

用于判断session是否超时(expired),对于超时的session,将这个用户登出。主要用于限制同一个用户多次登陆的场景。

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    // 获取session
    HttpSession session = request.getSession(false);
    // 如果获取到了session
    if (session != null) {
        // 根据session id获取session详细信息
        SessionInformation info = this.sessionRegistry.getSessionInformation(session.getId());
        if (info != null) {
            // 如果session已过期
            if (info.isExpired()) {
                // Expired - abort processing
                this.logger.debug(LogMessage
                        .of(() -> "Requested session ID " + request.getRequestedSessionId() + " has expired."));
                // 登出用户
                doLogout(request, response);
                // 告诉sessionInformationExpiredStrategy处理session超时事件
                // 重定向到session过期URL
                this.sessionInformationExpiredStrategy
                        .onExpiredSessionDetected(new SessionInformationExpiredEvent(info, request, response));
                return;
            }
            // Non-expired - update last request date/time
            // 如果没过期,刷新session的最后访问时间
            this.sessionRegistry.refreshLastRequest(info.getSessionId());
        }
    }
    chain.doFilter(request, response);
}

SecurityContextPersistenceFilter

请求到来的时候负责从repository(默认存储在HttpSession)读取securityContext(包含认证信息),存储到SecurityContextHolder中,请求完成的时候再清理掉SecurityContextHolder保存的securityContext。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

    // 检查请求是否已经被这个filter处理过
    // 确保只处理一次
    if (request.getAttribute(FILTER_APPLIED) != null) {
        // ensure that filter is only applied once per request
        chain.doFilter(request, response);
        return;
    }

    final boolean debug = logger.isDebugEnabled();

    // 设置属性,标记请求已经被此filter处理过
    request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

    // 如果需要强制创建session
    if (forceEagerSessionCreation) {
        HttpSession session = request.getSession();

        if (debug && session.isNew()) {
            logger.debug("Eagerly created session: " + session.getId());
        }
    }

    // 包装request和response
    HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
            response);
    // 从repository中读取securityContext
    SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

    try {
        // 设置SecurityContext到SecurityContextHolder中
        SecurityContextHolder.setContext(contextBeforeChainExecution);

        // 继续下一个filter
        chain.doFilter(holder.getRequest(), holder.getResponse());

    }
    finally {
        SecurityContext contextAfterChainExecution = SecurityContextHolder
                .getContext();
        // Crucial removal of SecurityContextHolder contents - do this before anything
        // else.
        // 请求处理完毕后,清空SecurityContextHolder
        SecurityContextHolder.clearContext();
        // 保存SecurityContext
        repo.saveContext(contextAfterChainExecution, holder.getRequest(),
                holder.getResponse());
        // 清除经过此filter处理的标记
        request.removeAttribute(FILTER_APPLIED);

        if (debug) {
            logger.debug("SecurityContextHolder now cleared, as request processing completed");
        }
    }
}

SecurityContextRepository

该接口负责持久化保存SecurityContext
它有如下子类:

HeadWriterFilter

用于向HTTP响应添加一些header。

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    // 是否需要在filterChain.doFilter之前添加header
    if (this.shouldWriteHeadersEagerly) {
        doHeadersBefore(request, response, filterChain);
    }
    else {
        doHeadersAfter(request, response, filterChain);
    }
}

doHeadersBeforedoHeadersAfter方法最终都会调用writeHeaders方法。
writeHeaders方法遍历每一个HeaderWriter改写HTTP header。

void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
    for (HeaderWriter writer : this.headerWriters) {
        writer.writeHeaders(request, response);
    }
}

HeaderWriter

它是所有改写HTTP header的接口,只有一个方法writerHeaders
根据添加header的不同,下面分别介绍它的实现类。这些实现类基本都是为response添加安全增强的HTTP header。

CacheControlHeadersWriter

添加"Cache-Control:no-cache, no-store, max-age=0, must-revalidate","Pragma:no-cache"和"Expires:0"。即禁用掉所有的缓存,必须向原服务器发送验证请求。

ClearSiteDataHeaderWriter

添加Clear-Site-Data header。用于清除浏览器数据。可使用如下值:

CompositeHeaderWriter

包含多个HeaderWriter,是一个复合类。

ContentSecurityPolicyHeaderWriter

添加CSP header。用于告诉浏览器需要加载资源的范围,哪些资源可以加载,哪些资源禁止加载。

DelegatingRequestMatcherHeaderWriter

代理类型,包含一个requestMatcherheaderWriter,如果请求可以被requestMatcher匹配,使用headerWriter改写header

FeaturePolicyHeaderWriter

添加Feature-Policy header。用于禁用或者启用浏览器特性。多用于限制周边设备比如加速度感应器,电池信息和摄像头等。

HpkpHeaderWriter

添加Public-Key-Pins header。响应头将特定的加密公钥与特定的 Web服务器相关联,以降低伪造证书对 MITM 攻击的风险。该header目前已废弃不建议使用

HstsHeaderWriter

添加Strict-Transport-Security header。强制浏览器使用HTTPS通道访问服务器。包含如下配置项:

ReferrerPolicyHeaderWriter

添加Referrer-Policy header。Referer是一个请求头,用于告诉服务器用户是从哪个页面跳转来的。这个字段包含的内容可能会泄漏用户敏感信息。Referrer-Policy用于限制浏览器发送referer的内容,有如下几个配置项:

这一段描述内容来自:https://www.cnblogs.com/amyzhu/p/9716493.html。如有侵权可联系删除。

StaticHeadersWriter

包含header列表,将header列表中所有header写入response。

XContentTypeOptionsHeaderWriter

添加"X-Content-Type-Options:nosniff"。作用为资源的MIME类型不可被更改,阻止浏览器自动推断MIME类型。防止基于 MIME 类型混淆的攻击。

XFrameOptionsHeaderWriter

添加X-Frame-Options header。用于限制页面是否可以在iframe中展示,防止clickjack攻击。有如下配置项:

XssProtectionHeaderWriter

添加X-XSS-Protection header,用于防范XSS攻击。如果加入了mode=block。浏览器会在检测到XSS攻击后,停止渲染页面。

CsrfFilter

此Filter负责防御CSRF攻击。负责生成和校验csrf token。

@Override
protected void doFilterInternal(HttpServletRequest request,
        HttpServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
    request.setAttribute(HttpServletResponse.class.getName(), response);

    // 使用tokenRepository获取token
    CsrfToken csrfToken = this.tokenRepository.loadToken(request);
    final boolean missingToken = csrfToken == null;
    // 如果缺失csrf token,重新生成一个
    if (missingToken) {
        csrfToken = this.tokenRepository.generateToken(request);
        this.tokenRepository.saveToken(csrfToken, request, response);
    }
    // 将csrf token放入request
    request.setAttribute(CsrfToken.class.getName(), csrfToken);
    request.setAttribute(csrfToken.getParameterName(), csrfToken);

    // 判断请求是否要求csrf保护,即除了GET,HEAD,TRACE,OPTIONS之外的请求需要CSRF保护
    // 如果不需保护,运行后面的filter
    if (!this.requireCsrfProtectionMatcher.matches(request)) {
        filterChain.doFilter(request, response);
        return;
    }

    // 获取request header中的实际csrf token
    String actualToken = request.getHeader(csrfToken.getHeaderName());
    if (actualToken == null) {
        // 如果header中没有,从请求参数中获取csrf token
        actualToken = request.getParameter(csrfToken.getParameterName());
    }
    // 如果实际的token和要求的token不一致,发生了csrf攻击,拒绝访问
    if (!csrfToken.getToken().equals(actualToken)) {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Invalid CSRF token found for "
                    + UrlUtils.buildFullRequestUrl(request));
        }
        if (missingToken) {
            this.accessDeniedHandler.handle(request, response,
                    new MissingCsrfTokenException(actualToken));
        }
        else {
            this.accessDeniedHandler.handle(request, response,
                    new InvalidCsrfTokenException(csrfToken, actualToken));
        }
        return;
    }

    // csrf校验通过,允许访问
    filterChain.doFilter(request, response);
}

CsrfTokenRepository

CSRF token的生成,保存和获取不由CsrfFilter直接负责,而是交给了CsrfTokenRepository。该接口有如下3个方法:

public interface CsrfTokenRepository {

    // 创建一个CSRF token,通常为一个UUID
    CsrfToken generateToken(HttpServletRequest request);

    // 保存CSRF token
    // 如果token为null,相当于删除这个token
    void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response);

    // 从request加载token
    CsrfToken loadToken(HttpServletRequest request);

}

该接口有如下3个实现类:

上一篇下一篇

猜你喜欢

热点阅读