44. Spring Security 常见问题解答

44.1 一般问题

44.1.1 Spring Security 是否可以满足我所有的应用程序安全要求?

Spring Security 为您的身份验证和授权要求提供了一个非常灵活的框架,但是在构建安全应用程序时,还有许多其他注意事项。 Web 应用程序容易受到各种您应该熟悉的攻击的攻击,最好在开始开发之前就进行攻击,因此您可以从一开始就牢记这些内容进行设计和编码。请访问 http://www.owasp.org/[OWASP 网站],以获取有关 Web 应用程序开发人员面临的主要问题的信息,以及可以针对他们采取的对策。

44.1.2 为什么不只使用 web.xml 安全性?

假设您正在基于 Spring 开发企业应用程序。通常需要解决四个安全问题:身份验证,Web 请求安全性,服务层安全性(即,实现业务逻辑的方法)和域对象实例的安全性(即不同的域对象具有不同的权限)。牢记以下典型要求:

    • Authentication *:Servlet 规范提供了一种身份验证方法。但是,您将需要配置容器以执行身份验证,这通常需要编辑特定于容器的“领域”设置。这将导致不可移植的配置,如果您需要编写实际的 Java 类来实现容器的身份验证接口,则它甚至变得更加不可移植。借助 Spring Security,您可以实现完全的可移植性-直至 WAR 级别。此外,Spring Security 还提供了经过生产验证的身份验证提供程序和机制,供您选择,这意味着您可以在部署时切换身份验证方法。对于编写需要在未知目标环境中工作的产品的软件供应商而言,这尤其有价值。
  • Web 请求安全性:Servlet 规范提供了一种保护请求 URI 的方法。但是,这些 URI 只能以 Servlet 规范自己的受限 URI 路径格式表示。 Spring Security 提供了一种更为全面的方法。例如,您可以使用 Ant 路径或正则表达式,可以考虑 URI 的部分,而不仅仅是请求的页面(例如,可以考虑 HTTP GET 参数),并且可以实现自己的配置数据的运行时源。这意味着您的 Web 请求安全性可以在 Webapp 的实际执行过程中动态更改。

  • 服务层和域对象安全性: Servlet 规范中缺少对服务层安全性或域对象实例安全性的支持,这表示对多层应用程序的严重限制。通常,开发人员要么忽略这些要求,要么在其 MVC 控制器代码中实现安全逻辑(或者更糟的是在视图内部)。这种方法有严重的缺点:

  • *关注点分离:*授权是贯穿各领域的关注点,应照此实施。 MVC 控制器或实现授权代码的视图使测试控制器和授权逻辑更加困难,调试更加困难,并且通常会导致代码重复。

    • *支持富 Client 端和 Web 服务:*如果最终必须支持其他 Client 端类型,则嵌入在 Web 层中的任何授权代码都是不可重用的。应该考虑到 Spring 远程 Export 商仅 Export 服务层 bean(而不是 MVC 控制器)。这样,授权逻辑需要位于服务层中以支持多种 Client 端类型。

    • 层问题: MVC 控制器或视图仅仅是错误的体系结构层,无法实现有关服务层方法或域对象实例的授权决策。尽管可以将主体传递到服务层以使其能够做出授权决策,但这样做会在每个服务层方法上引入一个附加参数。一种更优雅的方法是使用 ThreadLocal 来保存 Principal,尽管这可能会增加开发时间,以至于仅使用专用的安全框架就变得更加经济(基于成本效益)。

    • *授权代码质量:*通常,在 Web 框架中,它们“使做正确的事更容易,而做错事更难”。安全框架是相同的,因为它们以抽象的方式设计用于多种用途。从头开始编写自己的授权代码不会提供框架会提供的“设计检查”,而且内部授权代码通常将缺乏广泛部署,同行评审和新版本带来的改进。

对于简单的应用程序,servlet 规范安全性可能就足够了。尽管在 Web 容器可移植性,配置要求,有限的 Web 请求安全性以及不存在的服务层和域对象实例安全性的上下文中进行考虑,但很清楚的是,为什么开发人员经常寻求替代解决方案。

44.1.3 需要哪些 Java 和 Spring Framework 版本?

Spring Security 3.0 和 3.1 至少需要 JDK 1.5,并且也至少需要 Spring 3.0.3. 理想情况下,您应该使用最新版本,以避免出现问题。

Spring Security 2.0.x 要求最低 JDK 版本为 1.4,并且是针对 Spring 2.0.x 构建的。它也应该与使用 Spring 2.5.x 的应用程序兼容。

44.1.4 我是 Spring Security 的新手,我需要构建一个支持通过 HTTPS 进行 CAS 单一登录的应用程序,同时允许本地对某些 URL 进行基本身份验证,并针对多个后端用户信息源(LDAP 和 JDBC)进行身份验证。我已经复制了一些找到的配置文件,但是它不起作用。有什么事吗

或替代其他复杂方案...

实际上,您需要先了解要使用的技术,然后才能成功使用它们构建应用程序。安全性很复杂。使用登录表单来设置简单的配置,并使用 Spring Security 的命名空间来设置一些硬编码的用户是相当简单的。迁移到使用支持的 JDBC 数据库也很容易。但是,如果您尝试直接跳入这种复杂的部署方案,几乎肯定会感到沮丧。设置 CAS 之类的系统,配置 LDAP 服务器和正确安装 SSL 证书所需的学习曲线有很大的跳跃。因此,您需要一次采取一步。

从 Spring Security 的角度来看,您应该做的第一件事是遵循网站上的“入门”指南。这将带您完成一系列步骤,以启动并运行并了解框架的运行方式。如果使用的是您不熟悉的其他技术,则应进行一些研究,并尝试确保在将它们组合到复杂系统中之前可以单独使用它们。

44.2 常见问题

44.2.1 尝试登录时,收到一条错误消息“ Bad Credentials”。怎么了?

这意味着认证失败。它并没有说明原因,因为最好避免提供可能有助于攻击者猜测帐户名或密码的详细信息。

这也意味着,如果您在论坛中提出此问题,除非您提供其他信息,否则您将找不到答案。与任何问题一样,您应该检查调试日志的输出,注意所有异常堆栈跟踪和相关消息。在调试器中单步执行代码以查看身份验证失败的原因以及原因。编写一个测试案例,在应用程序外部练习您的身份验证配置。失败通常是由于数据库中存储的密码数据与用户 Importing 的密码数据不同。如果您使用的是哈希密码,请确保数据库中存储的值与应用程序中配置的PasswordEncoder完全相同。

44.2.2 当我尝试登录时,我的应用程序进入“无限循环”,这是怎么回事?

无限循环和重定向到登录页面的常见用户问题是由于不小心将登录页面配置为“安全”资源引起的。确保您的配置允许匿名访问登录页面,方法是将其从安全过滤器链中排除,或者将其标记为需要 ROLE_ANONYMOUS。

如果您的 AccessDecisionManager 包含 AuthenticatedVoter,则可以使用属性“ IS_AUTHENTICATED_ANONYMOUSLY”。如果您正在使用标准名称空间配置设置,则该选项自动可用。

从 Spring Security 2.0.1 开始,当您使用基于命名空间的配置时,将在加载应用程序上下文时进行检查,并在登录页面似乎受到保护的情况下记录警告消息。

44.2.3 我收到一条消息“访问被拒绝(用户为匿名);”的异常。怎么了?

这是一条调试级别的消息,它在匿名用户首次尝试访问受保护的资源时发生。

DEBUG [ExceptionTranslationFilter] - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.AccessDeniedException: Access is denied
at org.springframework.security.vote.AffirmativeBased.decide(AffirmativeBased.java:68)
at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:262)

这是正常现象,不必担心。

44.2.4 为什么即使我退出了应用程序,仍然可以看到受保护的页面?

造成这种情况的最常见原因是您的浏览器已经缓存了该页面,并且您看到的是从浏览器缓存中检索到的副本。通过检查浏览器是否确实在发送请求来验证这一点(检查服务器访问日志,调试日志或使用合适的浏览器调试插件,例如 Firefox 的“ Tamper Data”)。这与 Spring Security 无关,您应该配置应用程序或服务器以设置适当的Cache-Control响应头。请注意,永远不会缓存 SSL 请求。

44.2.5 我收到消息“在 SecurityContext 中找不到身份验证对象”的异常。怎么了?

这是另一条调试级别消息,该消息在匿名用户首次尝试访问受保护的资源时出现,但是在您的过滤器链配置中没有AnonymousAuthenticationFilter时出现。

DEBUG [ExceptionTranslationFilter] - Authentication exception occurred; redirecting to authentication entry point
org.springframework.security.AuthenticationCredentialsNotFoundException:
							An Authentication object was not found in the SecurityContext
at org.springframework.security.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:342)
at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:254)

这是正常现象,不必担心。

44.2.6 我无法使用 LDAP 身份验证。我的配置有什么问题?

请注意,LDAP 目录的权限通常不允许您读取用户密码。因此,通常无法使用第 44.3.6 节“什么是 UserDetailsService,我需要一个吗?”,Spring Security 会将存储的密码与用户提交的密码进行比较。最常见的方法是使用 LDAP“ bind”,这是LDAP 协议支持的操作之一。通过这种方法,Spring Security 通过尝试以用户身份验证目录来验证密码。

LDAP 认证最常见的问题是缺乏对目录服务器树结构和配置的了解。不同公司的情况会有所不同,因此您必须自己找出来。在将 Spring Security LDAP 配置添加到应用程序之前,最好使用标准 Java LDAP 代码(不涉及 Spring Security)编写一个简单的测试,并确保您可以使其首先工作。例如,要验证用户身份,可以使用以下代码:

@Test
public void ldapAuthenticationIsSuccessful() throws Exception {
		Hashtable<String,String> env = new Hashtable<String,String>();
		env.put(Context.SECURITY_AUTHENTICATION, "simple");
		env.put(Context.SECURITY_PRINCIPAL, "cn=joe,ou=users,dc=mycompany,dc=com");
		env.put(Context.PROVIDER_URL, "ldap://mycompany.com:389/dc=mycompany,dc=com");
		env.put(Context.SECURITY_CREDENTIALS, "joespassword");
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

		InitialLdapContext ctx = new InitialLdapContext(env, null);

}

44.2.7 会话 Management

会话 Management 问题是论坛问题的常见来源。如果要开发 Java Web 应用程序,则应了解如何在 servlet 容器和用户浏览器之间维护会话。您还应该了解安全和非安全 Cookie 的区别,以及使用 HTTP/HTTPS 以及在两者之间进行切换的含义。 Spring Security 与维护会话或提供会话标识符无关。这完全由 servlet 容器处理。

44.2.8 我正在使用 Spring Security 的并发会话控制来防止用户一次登录多次。登录后打开另一个浏览器窗口时,它不会阻止我再次登录。为什么我可以多次登录?

浏览器通常每个浏览器实例维护一个会话。您不能一次有两个单独的会话。因此,如果您再次在另一个窗口或选项卡中登录,则仅在同一会话中重新进行身份验证。服务器对选项卡,窗口或浏览器实例一无所知。它所看到的只是 HTTP 请求,并根据它们所包含的 JSESSIONID cookie 的值将它们绑定到特定会话。当用户在会话期间进行身份验证时,Spring Security 的并发会话控件会检查他们拥有的“其他身份验证会话”的数量。如果它们已经通过同一会话进行了身份验证,则重新身份验证将无效。

44.2.9 通过 Spring Security 进行身份验证时,为什么会话 ID 会更改?

使用默认配置,Spring Security 在用户认证时更改会话 ID。如果您使用的是 Servlet 3.1 或更高版本的容器,则只需更改会话 ID。如果您使用的是较旧的容器,Spring Security 将使现有会话无效,创建一个新会话,并将会话数据传输到新会话。以这种方式改变会话标识符可以防止“会话固定”攻击。您可以在网上和参考手册中找到有关此内容的更多信息。

44.2.10 我正在使用 Tomcat(或其他一些 servlet 容器),并已为登录页面启用 HTTPS,然后再切换回 HTTP。它不起作用-经过身份验证后,我只能回到登录页面。

发生这种情况是因为在 HTTPS 下创建的会话(会话 cookie 标记为“安全”)无法随后在 HTTP 下使用。浏览器不会将 cookie 发送回服务器,并且任何会话状态都将丢失(包括安全上下文信息)。首先使用 HTTP 启动会话应该可以正常工作,因为会话 cookie 不会被标记为安全。但是,Spring Security 的会话固定保护会对此产生干扰,因为它会导致通常使用安全标志将新的会话 ID cookie 发送回用户的浏览器。要解决此问题,可以禁用会话固定保护,但是在较新的 Servlet 容器中,您还可以配置会话 cookie,使其从不使用安全标志。请注意,在 HTTP 和 HTTPS 之间切换通常不是一个好主意,因为任何完全使用 HTTP 的应用程序都容易受到中间人攻击。为了 true 安全,用户应开始使用 HTTPS 访问您的站点并 continue 使用它,直到注销为止。即使从通过 HTTP 访问的页面上单击 HTTPS 链接也可能存在风险。如果您需要更多说服力,请查看sslstrip之类的工具。

44.2.11 我没有在 HTTP 和 HTTPS 之间切换,但是我的会话仍然丢失

通过交换会话 cookie 或向 URL 添加jsessionid参数来维护会话(如果使用 JSTL 输出 URL 或在 URL 上调用HttpServletResponse.encodeUrl(例如,在重定向之前),则会自动发生)。禁用,并且您不重写 URL 来包含jsessionid,则会话将丢失。请注意,出于安全原因,首选使用 cookie,因为它不会在 URL 中公开会话信息。

44.2.12 我正在尝试使用并发会话控制支持,但是即使我确定我已经注销并且没有超出允许的会话,它也不允许我重新登录。

确保已将侦听器添加到 web.xml 文件。必须确保在会话被销毁时通知 Spring Security 会话注册表。没有它,会话信息将不会从注册表中删除。

<listener>
		<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>

44.2.13 通过将 create-session 属性设置为 never,即使我未配置,Spring Security 也在某个地方创建会话。

这通常意味着用户的应用程序正在某个地方创建会话,但是他们不知道该会话。最常见的罪魁祸首是 JSP。许多人并不知道 JSP 默认情况下会创建会话。为了防止 JSP 创建会话,请将指令<%@ page session="false" %>添加到页面顶部。

如果无法确定要在何处创建会话,可以添加一些调试代码来跟踪位置。一种方法是将javax.servlet.http.HttpSessionListener添加到您的应用程序,该应用程序在sessionCreated方法中调用Thread.dumpStack()

44.2.14 执行 POST 时收到 403 禁止

如果为 HTTP POST 返回了 HTTP 403 Forbidden,但对于 HTTP GET 适用,则该问题很可能与CSRF有关。提供 CSRF 令牌或禁用 CSRF 保护(不建议)。

44.2.15 我正在使用 RequestDispatcher 将请求转发到另一个 URL,但是没有应用我的安全约束。

过滤器默认情况下不应用于转发或包含。如果您确实希望将安全过滤器应用于转发和/或包含,则必须使用元素(的子元素)在 web.xml 中显式配置这些过滤器。

44.2.16 我已经在应用程序上下文中添加了 Spring Security 的\ 元素,但是如果我在 Spring MVC 控制器 bean(Struts 操作等)中添加了安全 Comments,那么它们似乎没有效果。

在 Spring Web 应用程序中,保存用于调度程序 Servlet 的 Spring MVC bean 的应用程序上下文通常与主应用程序上下文分开。它通常在名为myapp-servlet.xml的文件中定义,其中“ myapp”是在web.xml中分配给 Spring DispatcherServlet的名称。一个应用程序可以有多个DispatcherServlet,每个都有自己的隔离应用程序上下文。这些“子”上下文中的 Bean 对应用程序的其余部分不可见。 “父”应用程序上下文由您在web.xml中定义的ContextLoaderListener加载,并且对所有子上下文可见。通常在此父上下文中定义安全配置(包括<global-method-security>元素)。结果,由于无法从DispatcherServlet上下文中看到 Bean,因此不会强制应用到这些 Web Bean 中的方法的任何安全性约束。您需要将<global-method-security>声明移至 Web 上下文,或将要保护的 Bean 移至主应用程序上下文。

通常,我们建议在服务层而不是单个 Web 控制器上应用方法安全性。

44.2.17 我有一个已经通过身份验证的用户,但是当我在某些请求期间尝试访问 SecurityContextHolder 时,Authentication 为 null。为什么看不到用户信息?

如果使用与 URL 模式匹配的<intercept-url>元素中的属性filters='none'从安全过滤器链中排除了该请求,则不会为该请求填充SecurityContextHolder。检查调试日志以查看请求是否正在通过筛选器链。 (您正在阅读调试日志,对吗?)。

44.2.18 使用 URL 属性时,授权 JSP 标记不遵守我的方法安全 Comments。

当使用<sec:authorize>中的url属性时,方法安全性不会隐藏链接,因为我们无法轻易地反向工程哪些 URLMap 到哪个控制器端点,因为控制器可以依赖 Headers,当前用户等来确定要调用的方法。

44.3 Spring 安全架构问题

44.3.1 如何知道 X 属于哪个包类?

定位类的最佳方法是在 IDE 中安装 Spring Security 源代码。该发行版包括项目分成的每个模块的源 jar。将它们添加到项目源路径中,您可以直接导航到 Spring Security 类(在 Eclipse 中为Ctrl-Shift-T)。这也使调试更加容易,并允许您通过直接查看异常发生的地方来查看异常情况,从而对异常进行故障排除。

44.3.2 命名空间元素如何 Map 到常规 bean 配置?

在参考指南的名称空间附录中,概述了由名称空间创建的 bean。 blog.springsource.com上还有一篇详细的博客文章,名为“ Spring Security 命名空间的背后”。如果想知道全部细节,那么代码在 Spring Security 3.0 发行版的spring-security-config模块中。您可能应该先阅读标准 Spring Framework 参考文档中有关名称空间解析的章节。

44.3.3“ ROLE_”是什么意思,为什么我在角色名称上需要它?

Spring Security 具有基于投票者的架构,这意味着访问决策由一系列AccessDecisionVoter s 决定。投票者根据为安全资源指定的“配置属性”(例如方法调用)进行操作。使用这种方法,并非所有属性都可能与所有选民相关,并且选民需要知道何时应该忽略属性(弃权)以及何时应该投票基于属性值授予或拒绝访问权限。最常见的投票者是RoleVoter,默认情况下,只要找到具有“ ROLE_”前缀的属性,投票者就会投票。它将属性(例如“ ROLE_USER”)与当前用户已分配的权限名称进行简单比较。如果找到匹配项(它们具有称为“ ROLE_USER”的权限),则投票批准授予访问权限,否则投票拒绝访问。

可以通过设置RoleVoterrolePrefix属性来更改前缀。如果只需要在应用程序中使用角色,而无需其他自定义投票者,则可以将前缀设置为空字符串,在这种情况下RoleVoter会将所有属性视为角色。

44.3.4 如何知道要添加到我的应用程序中的哪些依赖项才能与 Spring Security 一起使用?

这将取决于您使用的功能以及所开发的应用程序类型。使用 Spring Security 3.0,将项目 jar 分为明显不同的功能区域,因此可以很容易地从应用程序需求中确定所需的 Spring Security jar。所有应用程序都需要spring-security-core jar。如果要开发 Web 应用程序,则需要spring-security-web jar。如果使用安全名称空间配置,则需要spring-security-config jar,要获得 LDAP 支持,则需要spring-security-ldap jar,依此类推。

对于第三方罐子,情况并不总是那么明显。一个好的起点是从一个预先构建的示例应用程序 WEB-INF/lib 目录中复制这些文件。对于基本应用程序,您可以从教程示例开始。如果要对嵌入式测试服务器使用 LDAP,请以 LDAP 示例为起点。参考手册还包括 http://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#appendix-dependencies [附录]列出了每个 Spring 的第一级依赖关系安全模块,其中包含有关它们是否可选以及所需功能的一些信息。

如果您正在使用 maven 构建项目,则将适当的 Spring Security 模块作为依赖项添加到 pom.xml 中,将自动提取框架所需的核心 jar。如果需要,任何在 Spring Security POM 文件中标记为“可选”的文件都必须添加到您自己的 pom.xml 文件中。

44.3.5 运行嵌入式 ApacheDS LDAP 服务器需要哪些依赖项?

如果使用的是 Maven,则需要将以下内容添加到 pom 依赖项中:

<dependency>
		<groupId>org.apache.directory.server</groupId>
		<artifactId>apacheds-core</artifactId>
		<version>1.5.5</version>
		<scope>runtime</scope>
</dependency>
<dependency>
		<groupId>org.apache.directory.server</groupId>
		<artifactId>apacheds-server-jndi</artifactId>
		<version>1.5.5</version>
		<scope>runtime</scope>
</dependency>

其他需要的罐子应暂时移入。

44.3.6 什么是 UserDetailsService,我需要一个吗?

UserDetailsService是 DAO 界面,用于加载特定于用户帐户的数据。除了加载该数据以供框架中的其他组件使用外,它没有其他功能。它不负责验证用户身份。使用用户名/密码组合对用户进行身份验证通常是由DaoAuthenticationProvider执行的,DaoAuthenticationProvider被注入UserDetailsService以便允许它为用户加载密码(和其他数据),以便将其与提交的值进行比较。请注意,如果您使用的是 LDAP,则为这种方法可能行不通

如果要自定义身份验证过程,则应自己实现AuthenticationProvider。有关将 Spring Security 身份验证与 Google App Engine 集成的示例,请参见此blog article

44.4 常见的“操作方法”请求

44.4.1 我需要登录的不仅仅是用户名。如何添加对额外登录字段(例如公司名称)的支持?

这个问题在 Spring Security 论坛中反复出现,因此您可以通过搜索 Files(或通过 google)在那里找到更多信息。

提交的登录信息由UsernamePasswordAuthenticationFilter实例处理。您将需要自定义此类以处理额外的数据字段。一种选择是使用您自己的自定义身份验证令牌类(而不是标准的UsernamePasswordAuthenticationToken),另一种是简单地将多余的字段与用户名连接起来(例如,使用“:”作为分隔符),并将其传递给 username 属性的UsernamePasswordAuthenticationToken

您还需要自定义实际的身份验证过程。例如,如果使用的是自定义身份验证令牌类,则必须编写一个AuthenticationProvider来处理它(或扩展标准DaoAuthenticationProvider)。如果您串联了这些字段,则可以实现自己的UserDetailsService,将其拆分并加载适当的用户数据以进行身份验证。

44.4.2 在只有所请求的 URL 的片段值不同的情况下(例如/ foo#bar 和/ foo#blah?

您无法执行此操作,因为该片段不会从浏览器传输到服务器。从服务器的角度来看,上面的 URL 是相同的。这是 GWT 用户的常见问题。

44.4.3 如何在 UserDetailsService 中访问用户的 IP 地址(或其他 Web 请求数据)?

显然,您不能(不求助于线程局部变量),因为提供给接口的唯一信息是用户名。代替实现UserDetailsService,您应直接实现AuthenticationProvider并从提供的Authentication令牌中提取信息。

在标准的 Web 设置中,Authentication对象上的getDetails()方法将返回WebAuthenticationDetails的实例。如果需要其他信息,可以将自定义AuthenticationDetailsSource注入正在使用的身份验证过滤器中。如果使用命名空间(例如,使用<form-login>元素),则应删除该元素,并用指向明确配置的UsernamePasswordAuthenticationFilter<custom-filter>声明替换它。

44.4.4 如何从 UserDetailsService 访问 HttpSession?

您不能这样做,因为UserDetailsService不了解 Servlet API。如果要存储自定义用户数据,则应自定义返回的UserDetails对象。然后可以通过本地线程SecurityContextHolder随时访问它。调用SecurityContextHolder.getContext().getAuthentication().getPrincipal()将返回此自定义对象。

如果您确实需要访问该会话,则必须通过自定义 Web 层来完成。

44.4.5 如何在 UserDetailsService 中访问用户密码?

您不能(也不应该)。您可能会误解其目的。请参阅上方的“ 什么是 UserDetailsService?”。

44.4.6 如何动态定义应用程序中的安全 URL?

人们经常问如何在数据库中而不是在应用程序上下文中存储安全 URL 和安全元数据属性之间的 Map。

您应该问自己的第一件事是您是否真的需要这样做。如果应用程序需要安全保护,则还需要根据定义的策略对安全性进行彻底测试。在将其推广到生产环境之前,可能需要进行审核和验收测试。一个安全意识强的组织应该意识到,通过更改配置数据库中的一两行,可以在运行时修改安全设置,可以立即消除其辛苦的测试过程的好处。如果考虑到这一点(可能在应用程序中使用多层安全性),那么 Spring Security 允许您完全自定义安全性元数据的来源。如果选择,可以使其完全动态。

方法和 Web 安全都受到AbstractSecurityInterceptor子类的保护,该子类配置了SecurityMetadataSource,从子类中可以获取特定方法或过滤器调用的元数据。为了网络安全,拦截器类为FilterSecurityInterceptor,并且使用标记接口FilterInvocationSecurityMetadataSource。它操作的“受保护对象”类型是FilterInvocation。使用的默认实现(在名称空间<http>中,并且在显式配置拦截器时)都将 URL 模式列表及其对应的“配置属性”列表(ConfigAttribute的实例)存储在内存 Map 中。

要从备用源加载数据,必须使用显式声明的安全过滤器链(通常是 Spring Security 的FilterChainProxy)来定制FilterSecurityInterceptor bean。您不能使用名称空间。然后,您可以实现FilterInvocationSecurityMetadataSource以根据需要为特定的FilterInvocation [25]加载数据。一个非常基本的轮廓如下所示:

public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

		public List<ConfigAttribute> getAttributes(Object object) {
			FilterInvocation fi = (FilterInvocation) object;
				String url = fi.getRequestUrl();
				String httpMethod = fi.getRequest().getMethod();
				List<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>();

				// Lookup your database (or other source) using this information and populate the
				// list of attributes

				return attributes;
		}

		public Collection<ConfigAttribute> getAllConfigAttributes() {
			return null;
		}

		public boolean supports(Class<?> clazz) {
			return FilterInvocation.class.isAssignableFrom(clazz);
		}
	}

有关更多信息,请查看DefaultFilterInvocationSecurityMetadataSource的代码。

44.4.7 如何针对 LDAP 进行身份验证但如何从数据库中加载用户角色?

LdapAuthenticationProvider bean(在 Spring Security 中处理普通的 LDAP 身份验证)配置有两个单独的策略接口,一个用于执行身份验证,另一个用于加载用户权限,分别称为LdapAuthenticatorLdapAuthoritiesPopulatorDefaultLdapAuthoritiesPopulator从 LDAP 目录中加载用户权限,并具有各种配置参数,以允许您指定应如何检索这些权限。

要改为使用 JDBC,您可以使用适合您的模式的任何 SQL 自己实现接口:

public class MyAuthoritiesPopulator implements LdapAuthoritiesPopulator {
		@Autowired
		JdbcTemplate template;

		List<GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
			List<GrantedAuthority> = template.query("select role from roles where username = ?",
																									new String[] {username},
																									new RowMapper<GrantedAuthority>() {
				/**
				 *  We're assuming here that you're using the standard convention of using the role
				 *  prefix "ROLE_" to mark attributes which are supported by Spring Security's RoleVoter.
				 */
				public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
					return new SimpleGrantedAuthority("ROLE_" + rs.getString(1);
				}
			}
		}
	}

然后,您可以将这种类型的 bean 添加到您的应用程序上下文中,并将其注入LdapAuthenticationProvider。在参考手册的 LDAP 章节中有关使用显式 Spring Bean 配置 LDAP 的部分中对此进行了介绍。请注意,在这种情况下,不能使用名称空间进行配置。您还应该向 Javadoc 查询相关的类和接口。

44.4.8 我想修改由名称空间创建的 bean 的属性,但是架构中没有任何东西可以支持它。除了放弃命名空间使用外,我还能做什么?

命名空间功能是有意限制的,因此不能涵盖使用普通 bean 可以做的所有事情。如果您想做一些简单的事情,例如修改 bean 或注入不同的依赖项,则可以通过在配置中添加BeanPostProcessor来实现。可以在Spring 参考手册中找到更多信息。为此,您需要对创建哪些 bean 有一点了解,因此,您还应该阅读有关命名空间如何 Map 到 Spring Bean的上述问题中的博客文章。

通常,您需要将所需的功能添加到BeanPostProcessorpostProcessBeforeInitialization方法中。假设您要自定义UsernamePasswordAuthenticationFilter使用的AuthenticationDetailsSource(由form-login元素创建)。您想要从请求中提取名为CUSTOM_HEADER的特定 Headers,并在验证用户身份时使用它。处理器类如下所示:

public class BeanPostProcessor implements BeanPostProcessor {

		public Object postProcessAfterInitialization(Object bean, String name) {
				if (bean instanceof UsernamePasswordAuthenticationFilter) {
						System.out.println("********* Post-processing " + name);
						((UsernamePasswordAuthenticationFilter)bean).setAuthenticationDetailsSource(
										new AuthenticationDetailsSource() {
												public Object buildDetails(Object context) {
														return ((HttpServletRequest)context).getHeader("CUSTOM_HEADER");
												}
										});
				}
				return bean;
		}

		public Object postProcessBeforeInitialization(Object bean, String name) {
				return bean;
		}
}

然后,您将在应用程序上下文中注册此 bean。 Spring 将在应用程序上下文中定义的 bean 上自动调用它。


[25] FilterInvocation对象包含HttpServletRequest,因此您可以获取 URL 或任何其他相关信息,并根据这些信息来确定返回的属性列表将包含哪些内容。