Spring Security 3.0.0.M1 发布

工程 | Luke Taylor | 2009年6月3日 | ...

我们很高兴宣布 Spring Security 3.0 的第一个里程碑版本现已可供下载。该版本也可以通过 Maven 里程碑仓库获取,地址是http://maven.springframework.org/milestone。与 Spring 3.0 一样,这是第一个要求最低 JDK 1.5 才能运行的版本,同时也需要 Spring 3.0,因此如果您尚未在使用,应该获取 Spring 3.0.0.M3 版本。那么这个版本有什么新内容和变化呢?

基于表达式的访问控制

此版本首次包含了我们基于Spring-EL 的授权支持的预览。您现在可以在方法注解和 Web 安全中使用表达式。与熟悉的属性和基于投票器的机制相比,这开启了许多新的可能性。一个简单的例子可能是一个好的开始。这是一个用于 Web 应用程序的例子,使用了 security 命名空间

  <http use-expressions="true">
     <intercept-url pattern="/secure/**" access="hasRole('ROLE_SUPERVISOR') and hasIpAddress('192.168.1.0/24')" />
     ...
  </http>;

内置的hasRole('ROLE_SUPERVISOR')表达式并不复杂——它只是检查当前用户的权限列表,如果用户具有指定的角色则返回 true。但我们添加了一个额外的表达式来指定发出请求的 IP 地址必须在子网/掩码参数定义的范围内。根据您的网络设置,这可能实用也可能不实用,但它说明了基于表达式的方法有多强大。以前包含在“access”XML 属性中的安全属性列表已被布尔表达式取代。如果表达式求值为“true”,则授予访问权限。如果求值为“false”,则拒绝访问权限。

@Pre 和 @Post 注解

方法安全性比我们刚刚看到的 Web 请求的简单允许或拒绝更复杂一些。为了更全面地支持在方法安全中使用表达式,我们引入了四个新的注解,这些注解可以包含在方法调用之前和之后应用的表达式属性。要启用它们的支持,可以在global-method-security命名空间元素上添加一个新属性

    <global-method-security pre-post-annotations="enabled"/>

最明显有用的注解是@PreAuthorize,它控制方法是否实际可以被调用。例如(来自“Contacts”示例应用程序)


    @PreAuthorize("hasRole('ROLE_USER')")
    public void create(Contact contact);

,这意味着只有具有“ROLE_USER”角色的用户才允许访问。这里没什么新东西。但如果是

  
    @PreAuthorize("hasPermission(#contact, 'admin')")
    public void deletePermission(Contact contact, Sid recipient, Permission permission);

这里我们实际上是使用一个方法参数作为表达式的一部分来决定当前用户是否对给定的联系人拥有“admin”权限。hasPermission()表达式通过应用程序上下文链接到 Spring Security ACL 模块(请参见 Contacts 示例配置了解如何实现)。如果您的代码编译时包含调试信息,您可以按名称访问任何方法参数作为表达式变量。Spring-EL 的任何功能都可以在表达式中使用,因此您也可以访问参数的属性。例如,如果您想让某个特定方法只允许用户名与联系人用户名匹配的用户访问,您可以这样写


    @PreAuthorize("#contact.name == principal.name)")
    public void doSomething(Contact contact);

这里我们正在访问另一个内置表达式,即当前 Spring Security 的“principal”Authentication对象,该对象从安全上下文中获取。您也可以直接访问Authentication对象本身,使用表达式名称“authentication”。授权也可以在方法调用发生后执行,使用@PostAuthorize注解。要访问方法的返回值,请在表达式中使用内置名称“returnObject”。

过滤

您可能已经知道,Spring Security 也支持对集合和数组进行过滤,现在可以使用表达式来实现。这最常用于方法的返回值。例如
    
    @PreAuthorize("hasRole('ROLE_USER')")
    @PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")
    public List getAll();

@PostFilter注解一起使用时,Spring Security 会遍历返回的集合,并移除其中提供的表达式为 false 的任何元素。“filterObject”名称指的是集合中的当前对象。您也可以在方法调用之前进行过滤,使用@PreFilter,尽管这可能是一个不太常见的需求。语法完全相同,但如果存在多个集合类型的参数,您需要使用此注解的“filterTarget”属性按名称选择其中一个。

代码库重组

在 3.0 之前的版本中,Spring Security 的大部分代码都包含在spring-security-corejar 包中。多年来,随着更多功能的添加,跟踪代码库内部以及第三方库的依赖关系变得越来越困难。例如,用户很难确定核心 Maven pom.xml 中列出的哪些依赖项是框架内特定功能集所必需的。此外,最初的包结构和类名自该框架于 2003 年作为 Acegi Security 起源以来一直存在,当时只支持几种基本的身份验证机制。随着代码量的增加和功能集的扩展,这种包结构已开始显露其年代。
Spring Security 2.0.4 Package Structure

Spring Security 2.0.4 包结构

上图展示了由 Structure101 生成的 2.0.4 版本中 core、core-tiger、cas-client 和 acl jar 包的高级包图。您无需成为代码结构专家就能意识到这里存在一些问题。包内部存在大量循环引用,并且没有清晰的整体依赖结构。此外,还存在一些包跨 jar 边界拆分的问题,这可能导致 OSGi 问题。这种代码结构的脆弱性可能会随着 Spring Security 的演进而带来维护负担,因此我们决定在 3.0 版本中重组代码,为未来的开发提供一个稳定的基础。让我们来看看现在是如何组织的。

项目 Jar 包

我们做的第一件事是将核心拆分成几个 jar 包。该spring-security-corejar 包现在只包含基本的身份验证和访问控制代码,并且更加清晰。例如,它不再依赖 LDAP 或 servlet API,并且现在有专门用于 Web 特定的代码和 LDAP 的独立 jar 包。我们还将命名空间解析代码拆分到另一个单独的 jar 包中,因为它依赖于大多数其他 jar 包,并且不暴露任何您可能直接在应用程序中使用的公共 API。只有当您在应用程序上下文 XML 文件中使用 Spring Security 命名空间配置时,才需要使用它。主要的项目 jar 包如下表所示。
Jar 包名称 描述 何时使用 根包
spring-security-core 核心身份验证和访问控制类及接口。远程调用支持和基本资源供应 API。 使用 Spring Security 的任何应用程序都必需。支持独立应用程序、远程客户端、方法(服务层)安全和 JDBC 用户资源供应。 org.springframework.security.core, org.springframework.security.access, org.springframework.security.authentication, org.springframework.security.provisioning, org.springframework.security.remoting
spring-security-web 过滤器和其他 Web 安全基础设施及相关代码。任何依赖于 servlet API 的内容。 如果您需要 Spring Security Web 身份验证服务和基于 URL 的访问控制 org.springframework.security.web
spring-security-config 命名空间解析代码。 如果您正在使用 Spring Security XML 命名空间。 org.springframework.security.config
spring-security-ldap LDAP 身份验证和资源供应代码。 如果您需要使用 LDAP 身份验证或管理 LDAP 用户条目。 org.springframework.security.ldap
spring-security-acl 域对象 ACL 实现。 如果您需要在应用程序中对特定的域对象实例应用安全性。 org.springframework.security.acls
spring-security-cas-client Spring Security 的 CAS 客户端集成。 如果您想将 Spring Security Web 身份验证与 CAS 单点登录服务器一起使用。 org.springframework.security.cas
spring-security-openid OpenID Web 身份验证支持。 如果您需要针对外部 OpenID 服务器进行用户身份验证。 org.springframework.security.openid
现在在 jar 包级别有了更清晰的关注点分离。例如,只有在编写 Web 应用程序时才需要 Web jar 包(及其传递依赖)。这也使得代码更容易导航和理解。下面展示了构成与上述 2.0.4 版本相同的代码集的 Spring Security 3.0 jar 包之间的依赖关系
Spring Security 3.0 jars

Spring Security 3.0.0.M1 Jar 包依赖

包结构

这些 jar 包的包布局如下图所示。如您所见,不再存在任何循环引用,并且结构更加清晰。
Spring Security 3.0.0.M1 Package Structure

Spring Security 3.0.0.M1 包结构

core 包及其子包包含框架中使用的基本类和接口,核心 jar 包中的另外两个主要包是 authenticationaccessaccess 包包含访问控制/授权代码,例如 AccessDecisionManager 及相关的基于投票器的实现、拦截和方法安全基础设施、注解类以及对 Spring Security 3.0 基于表达式的访问控制的支持。authentication 包包含 AuthenticationManager 及相关类(例如身份验证异常类)、简单的基于 DAO 的身份验证提供者和密码编码器。

其他杂项变更

重命名类

代码库中的一些类名也开始显露其年代。例如,AbstractProcessingFilter现在是AbstractAuthenticationProcessingFilter,其最常见的具体子类AuthenticationProcessingFilter现在是UsernamePasswordAuthenticationProcessingFilter,因为框架中现在有许多处理身份验证请求的过滤器。其对应的AuthenticationEntryPoint现在是LoginUrlAuthenticationEntryPoint。名称模糊的ObjectDefinitionSource已重命名为SecurityMetadataSource,以及AbstractSecurityInterceptor上的相应属性也已类似重命名。实现类和子接口也已类似重命名。HttpSessionContextIntegrationFilter现在是SecurityContextPersistenceFilter,它具有一个可插拔的策略来控制如何存储安全上下文——它不必存储在HttpSession.

身份验证成功或失败时的重定向/转发

另一个问题在于AbstractProcessingFilter是控制浏览器在身份验证成功或失败后重定向到何处的不同方式数量过多。之前混合使用了属性和策略。在 Spring Security 3.0.0.M1 中,这些已被两个独立的策略取代,它们是AuthenticationSuccessHandlerAuthenticationFailureHandler,它们完全负责处理目标地址。查看这些接口的 Javadoc 并查看问题 SEC-745 以获取更多信息。

参考手册和网站更新

显然,3.0 参考手册仍在进行中 :-),但我们已经进行了一些更新。命名空间技术概述章节应提供对框架的合理概述。请随时通过问题跟踪器提交任何想法或补丁。当然,如果您发现任何错误,也请随时提交。

项目网站也已更新。FAQ 添加了一些新问题,并且有一个新页面包含演示视频和在线文章的链接。请查看并告知我们您希望在那里看到什么(外部安全链接页面已在计划中)。

结论

Spring Security 3 仍有很多工作要做,但我们希望您能尝试这个里程碑版本,并就新的表达式语言支持提供反馈。您可以在 Jira ChangeLog 中找到截至目前为止的完整变更列表,您还可以在那里找到当前项目路线图的链接。

社区论坛是提问关于使用 Spring Security 或发起关于新功能讨论的最佳场所。另外,如果您发现任何问题,可以提交一个 Jira Issue

希望您喜欢使用 Spring Security

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅