此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Security 7.0.4spring-doc.cadn.net.cn

Spring Security 常见问题解答

通用问题

此FAQ解答了以下通用问题:spring-doc.cadn.net.cn

Spring Security 是否可以处理我所有应用程序的安全需求?

Spring Security 为您提供了一个灵活的框架来满足您的身份验证和授权需求,但构建安全应用还需要考虑许多其他因素,而这些因素超出了其范围。 网络应用程序容易受到各种攻击,您应该在开始开发之前了解这些攻击,以便从一开始就将它们纳入设计和编码考虑之中。 请访问 OWASP 网站 获取有关 web 应用程序开发者面临的主要问题以及您可以采取的防范措施的信息。spring-doc.cadn.net.cn

为什么不用web.xml安全设置?

假设您正在基于Spring开发企业应用程序。 通常,您需要解决四个安全问题:身份验证、Web请求安全性、服务层安全性(实现业务逻辑的方法)以及领域对象实例安全性(不同的领域对象可以有不同的权限)。考虑到这些典型的需要,我们有以下考虑:spring-doc.cadn.net.cn

  • 认证: servlet 规范提供了一种认证的方法。 然而,你需要配置容器来执行认证操作,这通常需要编辑特定于容器的“领域”设置。 这就形成了一个非便携性的配置。另外,如果你需要编写实际的 Java 类来实现容器的认证接口,它变得更加非便携性。 使用 Spring Security,你可以实现完全便携性——甚至在 WAR 档案级别也是如此。 此外,Spring Security 提供了一种选择生产经验证明的认证提供者和机制,这意味着你可以在部署时切换你的认证方法。 这对于编写需要在未知目标环境中工作的产品的软件提供商来说尤其有价值。spring-doc.cadn.net.cn

  • Web请求安全: servlet规范提供了一种保护您的请求URI的安全方法。
    然而,这些URI只能以servlet规范自身有限的URI路径格式来表示。
    Spring Security提供了更为全面的方法。
    例如,您可以使用Ant路径或正则表达式,您还可以考虑URI中的其他部分(而不仅仅是请求页面),比如可以考虑HTTP GET参数,并且您可以实现自己的运行时配置数据来源。
    这意味着您可以在实际执行您的Web应用程序期间动态地更改您的Web请求安全。spring-doc.cadn.net.cn

  • 服务层和领域对象安全:Servlet 规范中缺乏对服务层安全或领域对象实例安全的支持代表了多层应用中的严重限制。 通常,开发人员要么忽略这些要求,要么在MVC控制器代码中实现安全逻辑(甚至更糟糕的是,在视图内部)。这种方法存在严重的缺点:spring-doc.cadn.net.cn

    • 职责分离:授权是一个横切关注点,应该以这种方式实现。 MVC控制器或视图中实现授权代码会使测试控制器和授权逻辑更加困难,更难调试,并且通常会导致代码重复。spring-doc.cadn.net.cn

    • 支持富客户端和Web服务:如果必须最终支持其他类型的客户端,嵌入在web层中的授权代码是不可重用的。 应当考虑Spring远程导出器仅导出服务层豆(而非MVC控制器)。因此,为了支持多种客户端类型,授权逻辑需要位于服务层。spring-doc.cadn.net.cn

    • 层叠问题:在MVC控制器或视图中实现与服务层方法或领域对象实例相关的授权决策是不正确的架构层次。 虽然可以将主体传递到服务层以使其能够做出授权决策,但这会在每个服务层方法上引入额外的参数。 更优雅的方法是使用ThreadLocal来持有主体,尽管这可能会增加开发时间,在成本效益分析中,使用专门的安全框架可能更具经济性。spring-doc.cadn.net.cn

    • 授权码质量:人们常说,Web框架能够‘使做正确的事情变得容易,而让做错误的事情变得更加困难’。安全框架也是如此,因为它们是以抽象的方式为广泛的目的设计的。 从头开始编写自己的授权代码并不能提供框架所提供的“设计检查”,而且内部授权代码通常缺乏来自大规模部署、同行评审和新版本所带来的改进。spring-doc.cadn.net.cn

对于简单的应用程序,Servlet 规范的安全性可能已经足够。 尽管从 Web 容器的可移植性、配置要求、有限的 web 请求安全性灵活性以及不存在的服务层和域对象实例安全性考虑,在这些情况下开发人员往往会选择其他解决方案。spring-doc.cadn.net.cn

需要哪些Java和Spring框架版本?

Spring Security 3.0 和 3.1 要求至少使用 JDK 1.5,并且需要 Spring 3.0.3 作为最低版本。 最好使用最新发布的版本以避免问题。spring-doc.cadn.net.cn

Spring Security 2.0.x 要求最低 JDK 版本为 1.4,并且是基于 Spring 2.0.x 构建的。 它也应该与使用 Spring 2.5.x 的应用程序兼容。spring-doc.cadn.net.cn

我有一个复杂的场景。可能是哪里出错了?

(此答案通过处理特定场景来应对复杂情况。)spring-doc.cadn.net.cn

假设你是一个Spring Security的新手,并且需要构建一个支持HTTPS下的CAS单点登录的应用程序,同时允许某些URL进行本地的基本身份验证,从多个后端用户信息源(LDAP和JDBC)进行认证。你已经复制了一些配置文件,但发现它们不起作用。可能会出什么问题?spring-doc.cadn.net.cn

您在使用这些技术之前需要对其有了解,才能成功构建应用程序。 安全问题相当复杂。 通过 Spring Security 的命名空间使用登录表单和一些硬编码用户来设置简单的配置是相对容易的。 转移到使用后端 JDBC 数据库也非常容易。 然而,如果您尝试直接跳到像这种情况这样的复杂部署场景中,您几乎肯定会感到沮丧。 要设置 CAS 系统、配置 LDAP 服务器以及正确安装 SSL 证书都需要很大的学习曲线跳跃。 因此,您需要一步步来。spring-doc.cadn.net.cn

从 Spring Security 的角度来看,你应该首先按照网站上的“快速入门”指南进行操作。 这将引导你完成一系列步骤,帮助你启动并运行,并对框架的操作方式有所了解。 如果你使用的是其他你不熟悉的科技,请先做一些研究,确保你能独立地使用它们,然后再将它们结合到复杂的系统中。spring-doc.cadn.net.cn

常见问题

此部分概述了人们在使用Spring Security时遇到的最常见问题:spring-doc.cadn.net.cn

当我尝试登录时,会收到一条错误消息,内容是“Bad Credentials”。这是怎么回事?

这表示身份验证失败。 它没有说明原因,因为避免提供可能帮助攻击者猜出账户名或密码的详细信息是良好的实践。spring-doc.cadn.net.cn

这也意味着,如果你在网上提出这个问题,除非你提供额外的信息,否则不要期望得到回答。 与任何问题的处理一样,你应该检查调试日志的输出,并注意任何异常堆栈跟踪和相关消息。 你应该在调试器中单步执行代码,以查看身份验证失败的位置和原因。 你还应该编写一个测试用例,在应用程序之外测试你的身份验证配置。 如果你使用哈希密码,请确保存储在数据库中的值与你应用程序中配置的 PasswordEncoder 生成的值完全一致spring-doc.cadn.net.cn

我的应用程序在尝试登录时进入了“无限循环”。发生了什么?

用户常遇到的一个问题是陷入无限循环并不断重定向到登录页面,这通常是因为不小心将登录页面配置为“受保护”资源。 请确保您的配置允许匿名访问登录页面。 您可以使用 authorizeHttpRequests DSL 来实现这一点。spring-doc.cadn.net.cn

当您使用命名空间或DSL基于配置时,在加载应用程序上下文时会进行检查,如果您的登录页面似乎被保护,则会记录一条警告消息。spring-doc.cadn.net.cn

我得到了一个带有消息“访问被拒绝(用户是匿名用户);”的异常。这是怎么回事?

这是一条调试级别消息,当未注册用户首次尝试访问受保护资源时出现。spring-doc.cadn.net.cn

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)

这在正常情况下不应该引起任何担忧。spring-doc.cadn.net.cn

为什么我在注销应用程序之后仍然能看到受保护的页面?

这是最常见的原因,您的浏览器缓存了该页面,并且您正在查看从浏览器缓存中检索的副本。 通过检查是否实际发送了请求(检查服务器访问日志和调试日志或使用合适的浏览器调试插件,例如 Firefox 的“Tamper Data”)来验证这一点。这与 Spring Security 无关,您应该配置您的应用程序或服务器以设置适当的 Cache-Control 响应标头。 请注意,SSL 请求从不被缓存。spring-doc.cadn.net.cn

我得到了一个带有消息“SecurityContext中未找到认证对象”的异常。这是怎么回事?

以下列表显示了当匿名用户首次尝试访问受保护的资源时发生的调试级别消息。然而,此列表展示了在您的过滤器链配置中没有AnonymousAuthenticationFilter时会发生什么情况:spring-doc.cadn.net.cn

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)

这是正常的,无需担心。spring-doc.cadn.net.cn

我无法让LDAP认证工作。我的配置哪里出错了?

注意,LDAP 目录的权限通常不允许你读取用户的密码。
因此,通常无法使用 什么是 UserDetailsService 及其是否需要? 在其中 Spring Security 将存储的密码与用户提交的密码进行比较。
最常用的解决方案是使用 LDAP “绑定”,这是 LDAP 协议支持的一种操作。参见此处。通过这种方法,Spring Security 会通过尝试以用户身份对目录进行身份验证来验证密码。spring-doc.cadn.net.cn

LDAP身份验证最常见的问题是缺乏对目录服务器树结构和配置的理解。<br> 这种差异在不同的公司之间存在,因此你需要自己去了解。<br> 在将Spring Security LDAP配置添加到应用程序之前,你应该编写一个简单的测试,使用标准的Java LDAP代码(不涉及Spring Security),确保你能先让这个工作起来。<br> 例如,为了验证用户身份,你可以使用以下代码:<br>spring-doc.cadn.net.cn

@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);

}
@Test
fun ldapAuthenticationIsSuccessful() {
    val env = Hashtable<String, String>()
    env[Context.SECURITY_AUTHENTICATION] = "simple"
    env[Context.SECURITY_PRINCIPAL] = "cn=joe,ou=users,dc=mycompany,dc=com"
    env[Context.PROVIDER_URL] = "ldap://mycompany.com:389/dc=mycompany,dc=com"
    env[Context.SECURITY_CREDENTIALS] = "joespassword"
    env[Context.INITIAL_CONTEXT_FACTORY] = "com.sun.jndi.ldap.LdapCtxFactory"
    val ctx = InitialLdapContext(env, null)
}

会话管理

会话管理问题是常见问题的来源。<br> 如果您正在开发Java Web应用程序,您应该了解Servlet容器和用户浏览器之间的会话是如何保持的。<br> 您还应理解安全cookie与非安全cookie之间的区别以及在使用HTTP和HTTPS之间切换的影响。<br> Spring Security 与维护会话或提供会话标识符无关。<br> 这一切完全由Servlet容器处理。spring-doc.cadn.net.cn

我正在使用 Spring Security 的并发会话控制来防止用户在同一时间内多次登录。但在登录之后打开另一个浏览器窗口,仍然可以再次登录。为什么我可以多次登录?

浏览器通常每个浏览器实例只维护一个会话。 您无法同时拥有两个独立的会话。 因此,如果您在另一个窗口或标签页中再次登录,您只是在同一个会话中进行重新认证。 所以,如果您在另一个窗口或标签页中再次登录,您就是在同一个会话中进行重新认证。 服务器对标签页、窗口或浏览器实例一无所知。 它看到的只是 HTTP 请求,并根据这些请求中包含的 JSESSIONID cookie 的值将它们与特定会话关联起来。 当用户在会话期间进行身份验证时,Spring Security 的并发会话控制会检查他们拥有的其他已认证会话的数量。 如果他们已经在同一会话中通过身份验证,则重新认证不会产生任何效果。spring-doc.cadn.net.cn

为什么通过Spring Security鉴权时会改变会话ID?

使用默认配置时,Spring Security 在用户认证后会更改会话ID。 如果使用的是Servlet 3.1或更新版本的容器,会话ID将简单地进行更换。 如果使用的是较旧的容器,则Spring Security 将会话标记为无效,创建一个新的会话,并将会话数据转移到新的会话中。 通过这种方式更改会话标识符可以防止“会话固定”攻击。 您可以在线上或参考手册中找到更多关于此内容的信息。spring-doc.cadn.net.cn

我使用 Tomcat(或其他 Servlet 容器)并在登录页面启用了 HTTPS,认证后切换回 HTTP。但这不起作用。我在认证后最终还是回到了登录页面。

不工作了 - 认证后我只是回到了登录页面。spring-doc.cadn.net.cn

这发生是因为在HTTPS下创建的会话,其会话Cookie被标记为“安全”,不能随后在HTTP下使用。浏览器不会将Cookie发送回服务器,并且任何会话状态(包括安全上下文信息)都会丢失。首先在HTTP下启动会话应该可以解决问题,因为此时的会话Cookie没有标记为安全。 然而,Spring Security 的 会话固定保护 可能会干扰这一点,因为它会导致一个新的会话ID Cookie被发送回用户的浏览器,通常带有“安全”标志。 要解决这个问题,可以禁用会话固定保护。但在较新的Servlet容器中,你也可以配置会话Cookie从不使用“安全”标志。spring-doc.cadn.net.cn

切换HTTP和HTTPS generally不是一个好主意,因为任何使用HTTP的应用程序都容易受到中间人攻击。 为了真正安全,用户应该从访问你的站点开始就使用HTTPS,并且一直使用它直到他们登出。 即使是从通过HTTP访问的页面上点击了一个HTTPS链接也是潜在的风险。 如果你需要更多说服力,请查看像 sslstrip 这样的工具。spring-doc.cadn.net.cn

我并没有在 HTTP 和 HTTPS 之间切换,但我的会话仍然丢失了。这是怎么回事?

会话通过交换会话 cookie 或在 URL 中添加 jsessionid 参数来维护(如果使用 JSTL 输出 URL,或者在重定向前调用 HttpServletResponse.encodeUrl 对 URL 进行编码,则会发生这种情况)。如果客户端禁用了 cookies,并且您没有重新编写 URL 以包含 jsessionid,会话将会丢失。 注意,出于安全原因,使用 cookies 更为优选,因为这样不会在 URL 中暴露会话信息。spring-doc.cadn.net.cn

我尝试使用并发会话控制支持功能,但在确保我已经注销并且没有超出允许的会话数量的情况下,仍然无法重新登录。哪里出问题了?

请确保在web.xml文件中添加了监听器。 这非常重要,以确保当会话被销毁时,Spring Security会话注册表能够被告知。 如果没有这样做,会话信息将不会从注册表中移除。 以下示例在一个web.xml文件中添加了监听器:spring-doc.cadn.net.cn

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

Spring Security 在我已配置为不创建会话的情况下,即使设置了 create-session 属性为 never,仍然创建了一个会话。哪里出问题了?

这通常意味着用户的应用程序在某个地方创建了会话,但用户并不知情。 最常见的原因是 JSP。许多人不知道 JSP 默认会创建会话。 为了防止 JSP 创建会话,请在页面顶部添加 <%@ page session="false" %> 指令。spring-doc.cadn.net.cn

如果在确定会话创建的位置时遇到困难,可以通过添加调试代码来追踪位置。一种方法是向应用程序中添加一个javax.servlet.http.HttpSessionListener,并在Thread.dumpStack()方法中调用sessionCreatedspring-doc.cadn.net.cn

我进行POST请求时收到了403 Forbidden错误。怎么回事?

如果HTTP POST返回HTTP 403 Forbidden错误,但使用HTTP GET时可以正常工作,则问题很可能与CSRF有关。要么提供CSRFTokens,要么禁用CSRF保护(后者不被推荐)。spring-doc.cadn.net.cn

我通过使用RequestDispatcher转发请求到另一个URL,但是我的安全约束没有被应用。

默认情况下,过滤器不应用于重定向或包含操作。 如果您确实希望安全过滤器也应用于重定向或包含操作,则必须在 web.xml 文件中显式地通过使用 <dispatcher> 元素(它是 <filter-mapping> 元素的子元素)来配置这些设置。spring-doc.cadn.net.cn

我已将Spring Security的<global-method-security>元素添加到我的应用上下文中,但如果我对Spring MVC控制器bean(Struts动作等)添加了安全注解,这些注解似乎没有效果。为什么呢?

在Spring Web应用程序中,持有分发器Servlet的Spring MVC bean的应用上下文通常与主要的应用上下文分开。它通常定义在一个名为myapp-servlet.xml的文件中,其中myapp是Spring DispatcherServletweb.xml文件中分配的名字。应用程序可以有多个DispatcherServlet实例,每个实例都有其独立的应用上下文。这些“子”上下文中定义的 bean 对应用程序的其他部分不可见。"父"应用上下文是由你在web.xml文件中定义的ContextLoaderListener加载的,并且对所有子上下文都是可见的。这个父上下文通常是你定义安全配置的地方,包括<global-method-security>元素。因此,应用于这些 Web Bean 中方法的任何安全约束都不会被强制执行,因为这些 Bean 无法从 DispatcherServlet 上下文中看到。您需要将<global-method-security>声明移动到Web上下文中,或者将您要保护的bean移入主应用程序上下文中。spring-doc.cadn.net.cn

一般情况下,我们建议在服务层应用方法安全,而不是在个别Web控制器上。spring-doc.cadn.net.cn

我确实已经对一个用户进行了认证,但在某些请求中尝试访问 SecurityContextHolder 时,Authentication 却为 null。为什么我看不见用户信息?

为什么我看不到用户信息?spring-doc.cadn.net.cn

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

authorize JSP 标签在使用 URL 属性时为什么不会遵守我的方法安全注解?

方法安全在使用url标签的<sec:authorize>属性时不会隐藏链接,因为无法轻易反向工程得出哪个URL映射到哪个控制器端点。我们受到限制是因为控制器可以依赖头部信息、当前用户以及其他细节来决定调用什么方法。spring-doc.cadn.net.cn

Spring Security 架构问题

此部分解答了常见的Spring Security架构问题:spring-doc.cadn.net.cn

如何知道类X在哪个包中?

通过在IDE中安装Spring Security源代码来定位类是最好的方法。分发包包括项目划分成各个模块的源码jar文件。 将这些添加到你的项目源路径,然后你就可以直接导航到Spring Security类(在Eclipse中使用Ctrl-Shift-T)。这也会使调试更容易,并且可以通过直接查看代码来排查异常,从而了解错误发生时的情况。spring-doc.cadn.net.cn

namespace元素如何映射到传统的bean配置?

在参考指南的命名空间附录中,有一般概述说明了命名空间创建哪些bean。 此外,在上还有一篇详细的博客文章,标题为“Spring Security 命名空间背后的内容”。如果你想要了解全部细节,那么代码位于Spring Security 3.0分发包中的spring-security-config模块内。 你最好先阅读标准Spring框架参考文档中关于命名空间解析的章节。spring-doc.cadn.net.cn

"ROLE_" 代表什么意思?

ROLE_ 是一种标识给定权限性质的方式。 带有 ROLE_ 前缀的权限意味着这是一个角色,很可能源自基于角色的访问控制(RBAC)授权模型。spring-doc.cadn.net.cn

拥有前缀可以清楚地区分OAuth 2.0范围(后者使用SCOPE_)和其他来源授予的权限。spring-doc.cadn.net.cn

您可以选择不在权限前添加前缀。 现代 Spring Security 授权组件允许您提供完整的权限名称,从而使前缀变得不必要。 例如,authorizeHttpRequests@PreAuthorize 允许您调用 hasAuthorityhasRolespring-doc.cadn.net.cn

如何确定需要添加哪些依赖项以使我的应用程序与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 文件。依此类推。spring-doc.cadn.net.cn

对于第三方jar包,情况并不总是那么明显。 一个良好的起点是从预构建的示例应用中复制WEB-INF/lib目录下的那些文件。 对于基本的应用程序,可以从教程示例开始。 对于基本的应用程序,可以从教程示例开始。 如果你想使用嵌入式的测试服务器进行LDAP操作,请以LDAP示例作为起点。 参考手册还包含一个附录此处链接,列出了每个Spring Security模块的一级依赖关系,并提供了它们是否可选以及何时必需的一些信息。spring-doc.cadn.net.cn

如果您使用Maven构建项目,在pom.xml文件中添加适当的Spring Security模块作为依赖项,框架所需的核心JAR包会自动拉取。 任何在Spring Security的pom.xml文件中标记为“可选”的模块都需要您自己在自己的pom.xml文件中手动添加。spring-doc.cadn.net.cn

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

需要将以下依赖添加到您的项目中:spring-doc.cadn.net.cn

<dependency>
		<groupId>com.unboundid</groupId>
		<artifactId>unboundid-ldapsdk</artifactId>
		<version>7.0.1</version>
		<scope>runtime</scope>
</dependency>
implementation 'com.unboundid:unboundid-ldapsdk:7.0.1'

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

Spring Security 7 移除了对 Apache DS 的支持。 请改用 UnboundIDspring-doc.cadn.net.cn

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

UserDetailsService 是一个用于加载特定于用户账户的数据的 DAO 接口。 它唯一的功能就是为框架内的其他组件提供这些数据。 它不负责验证用户身份。 使用用户名和密码组合验证用户通常是由 DaoAuthenticationProvider 完成的,UserDetailsService 会注入一个 #appendix-faq-ldap-authentication 来让它加载用户的数据(如密码等),并与提交的值进行比较。 请注意,如果你使用 LDAP,则 这种做法可能不适用spring-doc.cadn.net.cn

如果您想自定义认证过程,应该自己实现AuthenticationProvider。 您可以参见这篇博客文章,了解如何将Spring Security认证与Google App Engine集成。spring-doc.cadn.net.cn

常见问题解答

此部分回答了关于Spring Security的一些常见问题:spring-doc.cadn.net.cn

我需要使用比用户名更多的信息进行登录。如何支持额外的登录字段(例如公司名称)?

此问题反复出现,因此您可以通过在线搜索获取更多信息。spring-doc.cadn.net.cn

提交的登录信息将由UsernamePasswordAuthenticationFilter的一个实例处理。您需要自定义这个类以处理额外的数据字段。一种选项是使用您自己的自定义认证Tokens类(而不是标准的UsernamePasswordAuthenticationToken)。另一种选项是将额外字段与用户名拼接起来(例如,可以使用:字符作为分隔符),并通过UsernamePasswordAuthenticationToken的username属性传递这些信息。spring-doc.cadn.net.cn

您还需要自定义实际的认证过程。 如果使用了自定义的认证Tokens类,例如,您将需要编写一个AuthenticationProvider(或扩展标准的DaoAuthenticationProvider)来处理它。如果您已经拼接了字段,您可以实现自己的UserDetailsService来拆分这些字段,并加载适当的用户数据用于认证。spring-doc.cadn.net.cn

如何在请求的URL中只有一部分片段(fragment)值不同(例如:/thing1#thing2 和 /thing1#thing3)的情况下应用不同的 intercept-url 约束?

您无法做到这一点,因为片段不会从浏览器传输到服务器。 从服务器的角度来看,这些 URL 是相同的。 这是一个来自 GWT 用户的常见问题。spring-doc.cadn.net.cn

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

您无法做到这一点(除非使用类似于线程局部变量的方法),因为提供给接口的唯一信息是用户名。 相反,您应该直接实现UserDetailsService并从提供的AuthenticationProviderTokens中提取相关信息。spring-doc.cadn.net.cn

在标准的Web设置中,getDetails()对象上的Authentication方法将返回一个WebAuthenticationDetails实例。如果您需要额外的信息,则可以向您正在使用的认证过滤器注入一个自定义的AuthenticationDetailsSource。 如果使用命名空间,例如使用<form-login>元素,那么应该移除该元素并用指向显式配置的<custom-filter>UsernamePasswordAuthenticationFilter声明来替换它。spring-doc.cadn.net.cn

如何从UserDetailsService中访问HttpSession?

你不能这样做,因为UserDetailsService不了解servlet API。如果你想存储自定义用户数据,你应该自定义返回的UserDetails对象。 然后可以通过线程本地变量SecurityContextHolder随时访问它。调用SecurityContextHolder.getContext().getAuthentication().getPrincipal()会返回这个自定义对象。spring-doc.cadn.net.cn

如果您确实需要访问会话,请务必通过自定义Web层来实现。spring-doc.cadn.net.cn

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

你不能(也不应该,即使你找到了一种方法可以做到)。你可能误解了它的用途。 参见“什么是UserDetailsService?”,在FAQ的前面部分。spring-doc.cadn.net.cn

如何在应用程序中动态定义受保护的URL?

人们经常询问如何将受保护的URL与其安全元数据属性之间的映射存储在数据库中,而不是存储在应用程序上下文中。spring-doc.cadn.net.cn

您首先应该问自己是否真的需要这样做。 如果一个应用程序需要安全,那么它也需要基于定义的策略进行彻底的安全测试。 在部署到生产环境之前,可能还需要进行审计和接受测试。 重视安全性的企业应意识到,通过在运行时修改配置数据库中的一两行来更改安全设置可能会立即抹去他们严谨的测试过程带来的所有好处。 如果您已经考虑了这一点(例如,在应用程序内部使用多层安全),Spring Security 允许您完全自定义安全元数据的来源。 如果需要的话,您可以使其完全动态。spring-doc.cadn.net.cn

方法安全和网络安全性都受到AuthorizationManager实现的保护。 对于网络安全性,您可以提供您自己的AuthorizationManager<RequestAuthorizationContext>的实现,并将其供应给过滤器链 DSL 如下所示:spring-doc.cadn.net.cn

@Component
public class DynamicAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
	private final MyExternalAuthorizationService authz;

	// ...

    @Override
    public AuthorizationResult authorize(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
		// query the external service
    }
}

// ...

http
    .authorizeHttpRequests((authorize) -> authorize.anyRequest().access(dynamicAuthorizationManager))
@Component
class DynamicAuthorizationManager : AuthorizationManager<RequestAuthorizationContext?> {
    private val rules: MyAuthorizationRulesRepository? = null

    // ...

    override fun authorize(authentication: Supplier<Authentication?>?, context: RequestAuthorizationContext?): AuthorizationResult {
		// look up rules from the database
    }
}

// ...

http {
    authorizeHttpRequests {
        authorize(anyRequest, dynamicAuthorizationManager)
    }
}

对于方法安全,您可以提供您自己的AuthorizationManager<MethodInvocation>实现,并将其像这样提供给Spring AOP:spring-doc.cadn.net.cn

@Component
public class DynamicAuthorizationManager implements AuthorizationManager<MethodInvocation> {
	private final MyExternalAuthorizationService authz;

	// ...

    @Override
    public AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocation invocation) {
		// query the external service
    }
}

// ...

@Bean
static Advisor securedAuthorizationAdvisor(DynamicAuthorizationManager dynamicAuthorizationManager) {
    return AuthorizationManagerBeforeMethodInterceptor.secured(dynamicAuthorizationManager)
}
@Component
class DynamicAuthorizationManager : AuthorizationManager<MethodInvocation?> {
    private val authz: MyExternalAuthorizationService? = null

     // ...
    override fun authorize(authentication: Supplier<Authentication?>?, invocation: MethodInvocation?): AuthorizationResult {
		// query the external service
    }
}

companion object {
    @Bean
    fun securedAuthorizationAdvisor(dynamicAuthorizationManager: DynamicAuthorizationManager): Advisor {
        return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(dynamicAuthorizationManager)
    }
}

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

The LdapAuthenticationProvider bean(Spring Security中处理正常LDAP认证的bean)是通过两个单独的策略接口配置的,一个用于执行认证,另一个用于加载用户权限,分别称为LdapAuthenticatorLdapAuthoritiesPopulator。 The DefaultLdapAuthoritiesPopulator从LDAP目录中加载用户权限,并有各种配置参数,允许您指定这些权限应如何获取。spring-doc.cadn.net.cn

要使用JDBC,您可以自己实现接口,并使用适用于您模式的任何SQL语句:spring-doc.cadn.net.cn

public class MyAuthoritiesPopulator implements LdapAuthoritiesPopulator {
    @Autowired
    JdbcTemplate template;

    List<GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
        return 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.
             */
            @Override
            public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
                return new SimpleGrantedAuthority("ROLE_" + rs.getString(1));
            }
        });
    }
}
class MyAuthoritiesPopulator : LdapAuthoritiesPopulator {
    @Autowired
    lateinit var template: JdbcTemplate

    override fun getGrantedAuthorities(userData: DirContextOperations, username: String): MutableList<GrantedAuthority?> {
        return template.query("select role from roles where username = ?",
            arrayOf(username)
        ) { rs, _ ->
            /**
             * 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.
             */
            SimpleGrantedAuthority("ROLE_" + rs.getString(1))
        }
    }
}

您然后可以将此类的 bean 添加到您的应用上下文中,并将其注入到 LdapAuthenticationProvider 中。这部分内容在参考手册的 LDAP 章节中使用显式 Spring bean 配置的部分有所介绍。 请注意,这种情况下不能使用命名空间进行配置。 您还应该查阅相关类和接口的 Javadocspring-doc.cadn.net.cn

我想修改由命名空间创建的bean的属性,但schema中没有支持它的内容。除了放弃使用命名空间,我还能怎么办?

namespace功能有意限制,因此它不能涵盖你可以用普通bean完成的一切。 如果你只想做一些简单的修改,比如修改一个bean或注入不同的依赖关系,可以通过在配置中添加BeanPostProcessor来实现。 你可以在Spring参考手册中找到更多信息。为了做到这一点,你需要知道哪些bean被创建了,因此你也应该阅读前面问题中提到的关于namespace如何映射到Spring bean的文章。spring-doc.cadn.net.cn

通常,您会将所需的功能添加到postProcessBeforeInitializationBeanPostProcessor方法中。假设您希望自定义由AuthenticationDetailsSource元素创建的UsernamePasswordAuthenticationFilter使用的form-login。您想从请求中提取一个名为CUSTOM_HEADER的特定标头,并在验证用户时使用它。 处理器类将如下所示:spring-doc.cadn.net.cn

public class CustomBeanPostProcessor 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;
		}
}
class CustomBeanPostProcessor : BeanPostProcessor {
    override fun postProcessAfterInitialization(bean: Any, name: String): Any {
        if (bean is UsernamePasswordAuthenticationFilter) {
            println("********* Post-processing $name")
            bean.setAuthenticationDetailsSource(
                AuthenticationDetailsSource<HttpServletRequest, Any?> { context -> context.getHeader("CUSTOM_HEADER") })
        }
        return bean
    }

    override fun postProcessBeforeInitialization(bean: Any, name: String?): Any {
        return bean
    }
}

然后您可以在应用上下文中注册此bean。 Spring 会自动在应用上下文定义的bean上调用它。spring-doc.cadn.net.cn