Web应用的安全--翻译自spring security 5.1.5

        大多数Spring Security用户会在使用HTTP和servlet API的应用程序中使用该框架。在本章节,我们将了解Spring Security是如何为应用程序的Web层提供身份验证和访问控制功能。我们将查看名称空间的facade(外观设计模式),并查找实际上是哪些类和接口被组装了起来以提供Web层的安全。在某些情况下,有必要使用传统的bean配置来提供对配置的完全控制,因此我们还将了解如何在不使用命名空间的情况下直接配置这些类。

10.1 Security过滤器链

        Spring Security的Web基础结构完全基于标准的servlet过滤器。它不在内部使用servlet或任何其他基于servlet的框架(如SpringMVC),因此它没有强关联到任何特定的Web技术。它处理HttpServletRequest和HttpServletResponse,而不关心这些请求是来自浏览器、Web服务客户机、HttpInvoker还是Ajax应用程序。

        Spring Security在内部维护一个过滤器链,链中每个过滤器都有特定的职责,并且链中使用和不使用那些过滤器,取决于所请求的服务。过滤器的顺序很重要,因为它们之间存在依赖关系。如果您一直在使用命名空间进行配置,那么过滤器将自动为您配置,并且您不必显式定义任何SpringBean,但有时您希望完全控制安全过滤器链,要么因为您使用的是名称空间中不支持的功能,或者您正在使用自定义的类版本。

10.1.1 委托过滤器代理 DelegatingFilterProxy

        当使用servlet过滤器时,你显然需要在web.xml中声明它们,否则它们将被servlet容器忽略。在Spring Security中,过滤器类也是在应用程序上下文中定义的SpringBean,因此能够利用Spring丰富的依赖注入工具和生命周期接口。Spring的 DelegatingFilterProxy 提供了web.xml和应用程序上下文之间的链接。

当使用 DelegatingFilterProxy 时,您将在web.xml文件中看到类似的内容:

<filter>

<filter-name>myFilter</filter-name>

<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

</filter>

<filter-mapping>

<filter-name>myFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

请注意,过滤器实际上是一个 DelegatingFilterProxy,而不是真正实现过滤器逻辑的类。 DelegatingFilterProxy 所做的是将过滤器的方法委托给一个从Spring应用程序上下文获得的bean。这使得bean能够从SpringWeb应用程序上下文生命周期的支持和配置的灵活性中获益。bean必须实现 javax.servlet.Filter,并且它必须与filter name元素中的名称相同。有关详细信息,请阅读关于 DelegatingFilterProxy 的JavaDoc。

10.1.2 过滤链代理 FilterChainProxy 

Spring Security的Web基础结构只能通过委托给 FilterChainProxy 实例来使用。 Security 过滤器不应该被本身使用。理论上,您可以声明在应用程序上下文文件中所需的每个SpringSecurity过滤器bean,并为每个过滤器向web.xml添加一个相应的 DelegatingFilterProxy 条目,确保它们的顺序正确,但这会很麻烦,如果您有很多过滤器,这杨还会很快使web.xml文件混乱不堪。FilterChainProxy 允许我们向web.xml添加一个条目,并全权处理应用程序上下文文件来管理我们的web security beans。它是使用 DelegatingFilterProxy 连接的,就像上面的例子一样,但是使用 filter-name 来设置bean的名称为“filterChainProxy”。然后过滤器链就在应用程序上下文中以相同的bean名称被声明了。这里有个例子:

<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">

<constructor-arg>

    <list>

    <sec:filter-chain pattern="/restful/**" filters="

        securityContextPersistenceFilterWithASCFalse,

        basicAuthenticationFilter,

        exceptionTranslationFilter,

        filterSecurityInterceptor" />

<sec:filter-chain pattern="/**" filters="

        securityContextPersistenceFilterWithASCTrue,

        formLoginFilter,

        exceptionTranslationFilter,

        filterSecurityInterceptor" />

</list>

</constructor-arg>

</bean>

命名空间元素 filter-chain 用于方便设置应用程序中所需的安全筛选器链。它将特定的URL映射到由filters元素中指定的bean名称构建的过滤器列表,并将它们组合到类型为 SecurityFilterChain 的bean中。pattern属性采用Ant路径,最具体的URIs应该首先出现。在运行时,FilterChainProxy 将定位与当前Web请求匹配的第一个URI模式,由filters属性指定的filter bean列表将应用于该请求。过滤器将按照定义的顺序被调用,因此您可以完全控制应用于特定URL的过滤器链。

您可能已经注意到我们在过滤器链中声明了两个SecurityContextPersistenceFilter(ASC是allowSessionCreation的缩写,是SecurityContextPersistenceFilter 的一个属性)。由于Web服务永远不会在将来的请求中提供JSessionID,因此为此类用户代理创建 HttpSession 是浪费的。如果您有一个需要最大可伸缩性的大容量应用程序,我们建议您使用上面所示的方法。对于较小的应用程序,使用单个SecurityContextPersistenceFilter(其默认allowSessionCreation为true)可能就足够了。

注意,FilterChainProxy 不会在配置它的过滤器上调用标准的过滤器生命周期方法。我们建议您使用Spring的应用程序上下文生命周期接口作为替代方法,就像对任何其他SpringBean一样。

当我们研究如何使用命名空间配置来设置Web security 时,我们使用了一个名为“springSecurityFilterChain”的 DelegatingFilterProxy。现在应该可以看到这是由命名空间创建的 FilterChainProxy  的名称。

绕过过滤链

您可以使用属性 filters=“none” 作为提供过滤器bean列表的替代方法。这将完全忽略安全过滤器链中的请求模式。请注意,任何与此路径匹配的内容都不会应用任何身份验证或授权服务,并且可以自由访问。如果要在请求期间使用SecurityContext 的内容,那么它必须通过安全筛选器链传递。否则,SecurityContextHolder 将不会被填充,内容将为空。典型的例子就是:如果你的登录页面时未经过滤器链过滤的,那么页面上将不存在csrfToken的值,那么在系统中开启csrf(默认)但未提交csrfToken的情况下,登录认证将不通过。

10.1.3 过滤器排序

过滤器在链中定义的顺序非常重要。不管您实际使用的过滤器是什么,顺序应该如下:

\bullet  ChannelProcessingFilter,因为它可能需要重定向到其他协议

\bullet  SecurityContextPersistenceFilter,因此可以在Web请求开始时在SecurityContextHolder中设置SecurityContext,并且可以在Web请求结束时将对SecurityContext的任何更改复制到httpSession(准备用于下一个Web请求)

\bullet  ConcurrentSessionFilter,因为它使用SecurityContextHolder功能,需要更新sessionRegistry以反映来自主体的持续请求

\bullet 身份验证处理机制 - UsernamePasswordAuthenticationFilter、CasAuthenticationFilter、BasicAuthenticationFilter 等-以便可以修改SecurityContextHolder以包含有效的Authentication 请求令牌

\bullet SecurityContextHolderWareRequestFilter,如果您正在使用它将支持Spring Security 的HttpServletRequestWrapper 安装到servlet容器中

\bullet JaasApiIntegrationFilter,如果一个 JaasAuthenticationToken 在SecurityContextholder中,它将作为JaasAuthenticationToken的主体来处理FilterChain 。

\bullet  RememberMeAuthenticationFilter,这样,如果没有早期的 authentication 处理机制更新了SecurityContextHolder,并且请求提供了一个允许执行“记住我”服务的cookie,那么将在那里放置一个合适的记住的 Authentication 对象。

\bullet  AnonymousAuthenticationFilter,这样,如果没有早期的身份验证处理机制更新SecurityContextHolder,则会在那里放置一个匿名 Authentication 对象。

\bullet  ExceptionTranslationFilter,捕获任何Spring Security 异常,以便返回HTTP错误响应或启动适当的AuthenticationEntryPoint 。    

\bullet  FilterSecurityInterceptor,用于保护 Web URIs并在拒绝访问时引发异常。

10.1.4 请求匹配和HttpFirewall

Spring Security 有几个方面(您定义的模式)针对传入的请求进行测试,以决定如何处理请求。当 FilterChainProxy  决定请求应该通过哪个过滤器链时,以及当 FilterSecurityInterceptor  决定采用哪种安全约束来作用于一个请求的时侯,就会发生这种情况。在根据您定义的模式进行测试时,了解机制是什么以及使用什么URL值是很重要的。

Servlet规范为 HttpServletRequest  定义了几个属性,这些属性可以通过getter方法访问,并且我们可能希望与之匹配。这些属性是contextPath、servletPath、pathInfo 和 queryString。Spring Security 只对保护应用程序中的路径感兴趣,因此忽略了contextPath 。不幸的是,servlet规范没有准确定义对于特定请求URI,servletPath 和 pathInfo 需要包含什么样的值。例如,URL的每个路径段可能包含参数,如RFC2396中所定义。规范没有明确说明这些是否应该包含在servletPath 和 pathInfo 的值中,并且不同的servlet容器之间的行为不同。当应用程序部署在不从这些值中删除路径参数的容器中时,就产生了危险,攻击者可以将它们添加到请求的URL中,以导致模式匹配意外成功或失败。传入URL中的其他变体也是可能的。例如,它可以包含路径遍历序列(如 /../)或多个正斜杠(//),这也可能导致模式匹配失败。一些容器在执行servlet映射之前将其规范化,但其他容器则没有这样做。为了防止此类问题,FilterChainProxy 使用HttpFirewall 策略检查和包装请求。默认情况下,未规范化的请求被自动拒绝,为了匹配的目的,路径参数和重复斜杠被删除。因此,必须使用 FilterChainProxy 来管理安全过滤器链。请注意,servletpath和pathinfo值是由容器解码的,因此您的应用程序不应该有任何包含分号的有效路径,因为为了进行匹配,这些部分将被删除。

如上所述,默认策略是使用Ant风格的路径进行匹配,这可能是大多数用户的最佳选择。该策略在类AntPathRequestMatcher 中实现,该类使用Spring的 antPathMatcher 对连接的servletPath和pathinfo执行不区分大小写的模式匹配,而忽略queryString。

如果出于某种原因,需要更强大的匹配策略,可以使用正则表达式。策略实现类是 RegexRequestMatcher。有关更多信息,请参阅此类的javadoc。

在实践中,建议您在服务层使用方法安全来控制对应用程序的访问,并且不要完全依赖于在Web应用程序级别定义的安全约束的使用。URLs会发生变化,很难考虑应用程序可能支持的所有可能的URL以及如何处理这些请求。您应该尝试并限制自己使用一些简单易懂的Ant路径。始终尝试使用“deny-by-default”方法,其中您具有最后定义的catch all通配符(/ or)并拒绝访问。

在服务层定义的安全性要强大得多,而且很难绕过,所以您应该始终利用Spring安全性的方法安全选项。

HttpFirewall 还通过拒绝HTTP响应头中的新行字符来防止HTTP响应拆分。

默认情况下使用StrictHttpFirewall 。此实现拒绝看似恶意的请求。如果它对您的需求太严格,那么您可以自定义拒绝哪些类型的请求。但是,重要的是要知道这样做可以使应用程序抵御攻击。例如,如果希望利用SpringMVC的矩阵变量,可以在XML中使用以下配置:

<b:beanid="httpFirewall"class="org.springframework.security.web.firewall.StrictHttpFirewall"p:allowSemicolon="true"/><http-firewallref="httpFirewall"/>

同样的事情可以通过暴露一个严格的防火墙防火墙来实现。

@Bean

publicStrictHttpFirewall httpFirewall() { 

     StrictHttpFirewall firewall =newStrictHttpFirewall(); 

     firewall.setAllowSemicolon(true);

    returnfirewall;

}

StrictHttpFirewall 提供了一个有效的HTTP方法白名单,这些方法可以防止跨站点跟踪(XST)和HTTP动词篡改。默认的有效方法是“DELETE”、“GET”、“HEAD”、“OPTIONS”、“PATCH”、“POST”和“PUT”。如果您的应用程序需要修改有效的方法,您可以配置一个自定义的StrictHttpFirewall bean。例如,以下仅允许http“GET”和“POST”方法:

<b:beanid="httpFirewall"class="org.springframework.security.web.firewall.StrictHttpFirewall"p:allowedHttpMethods="GET,HEAD"/>

<http-firewallref="httpFirewall"/>

同样的事情可以采用java配置的方式,通过暴露一个StrictHttpFirewall bean来实现。

@Bean

publicStrictHttpFirewall httpFirewall() {    

    StrictHttpFirewall firewall = newStrictHttpFirewall();        firewall.setAllowedHttpMethods(Arrays.asList("GET","POST"));

    returnfirewall;

}

如果您正在使用新的mockhttpservletrequest(),它当前会创建一个HTTP方法作为空字符串“”。这是一个无效的HTTP方法,将被Spring安全性拒绝。您可以通过用新的mockhttpservletrequest(“get”,)替换它来解决这个问题。请参阅SPR U 16851了解请求改进此问题的问题。

如果必须允许任何HTTP方法(不推荐),则可以使用 StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true).。这将完全禁用HTTP方法的验证。

10.1.5 与其他基于过滤器的框架一起使用

如果正在使用的其他框架也是基于过滤器的,那么您需要确保请求首先经过Spring Security 过滤器。这使SecurityContextHolder 能够及时被填充以供其他筛选器使用。例如,使用SiteMesh来装饰网页,或者使用Wicket这样的Web框架来处理其请求。

10.1.6 高级命名空间配置

正如我们在前面的命名空间章节中看到的,可以使用多个 http 元素为不同的URL模式定义不同的安全配置。每个元素在内部 FilterChainProxy 和应该映射到它的url模式中创建一个过滤器链。元素将按声明的顺序添加,因此必须首先声明最具体的模式。这里是另一个例子,对于与上面类似的情况,应用程序既支持无状态的RESTfulAPI,也支持用户使用表单登录的普通Web应用程序。

<!-- Stateless RESTful service using Basic authentication -->

<http pattern="/restful/**" create-session="stateless">

<intercept-url pattern='/**' access="hasRole('REMOTE')" />

<http-basic />

</http>

<!-- Empty filter chain for the login page -->

<http pattern="/login.htm*" security="none"/>

<!-- Additional filter chain for normal users, matching all other requests -->

<http>

<intercept-url pattern='/**' access="hasRole('USER')" />

<form-login login-page='/login.htm' default-target-url="/home.htm"/>

<logout />

</http>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容