此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

概述

Spring Security 使用 SpEL 进行表达式支持,如果您有兴趣更深入地了解该主题,您应该了解它是如何工作的。 表达式使用“根对象”作为评估上下文的一部分进行评估。 Spring Security 使用用于 Web 和方法安全性的特定类作为根对象,以提供内置表达式和对值(例如当前主体)的访问。Spring中文文档

常用内置表达式

表达式根对象的基类是 。 这提供了一些在 Web 和方法安全性中都可用的常用表达式:SecurityExpressionRootSpring中文文档

表 1.常用内置表达式
表达 描述

hasRole(String role)Spring中文文档

如果当前主体具有指定的角色,则返回。trueSpring中文文档

例:hasRole('admin')Spring中文文档

默认情况下,如果提供的角色不是以 开头,则会添加该角色。 您可以通过修改 on 来自定义此行为。ROLE_defaultRolePrefixDefaultWebSecurityExpressionHandlerSpring中文文档

hasAnyRole(String…​ roles)Spring中文文档

如果当前主体具有任何提供的角色(以逗号分隔的字符串列表形式提供),则返回。trueSpring中文文档

例:。hasAnyRole('admin', 'user')Spring中文文档

默认情况下,如果提供的角色不是以 开头,则会添加该角色。 您可以通过修改 on 来自定义此行为。ROLE_defaultRolePrefixDefaultWebSecurityExpressionHandlerSpring中文文档

hasAuthority(String authority)Spring中文文档

如果当前主体具有指定的权限,则返回。trueSpring中文文档

例:hasAuthority('read')Spring中文文档

hasAnyAuthority(String…​ authorities)Spring中文文档

如果当前主体具有任何提供的权限(以逗号分隔的字符串列表形式给出),则返回。trueSpring中文文档

例:。hasAnyAuthority('read', 'write')Spring中文文档

principalSpring中文文档

允许直接访问表示当前用户的主体对象。Spring中文文档

authenticationSpring中文文档

允许直接访问从 .AuthenticationSecurityContextSpring中文文档

permitAllSpring中文文档

计算结果始终为 。trueSpring中文文档

denyAllSpring中文文档

计算结果始终为 。falseSpring中文文档

isAnonymous()Spring中文文档

如果当前主体是匿名用户,则返回。trueSpring中文文档

isRememberMe()Spring中文文档

如果当前主体是记住我用户,则返回。trueSpring中文文档

isAuthenticated()Spring中文文档

如果用户不是匿名的,则返回。trueSpring中文文档

isFullyAuthenticated()Spring中文文档

如果用户不是匿名用户,并且不是记住我用户,则返回。trueSpring中文文档

hasPermission(Object target, Object permission)Spring中文文档

如果用户有权访问给定权限的所提供目标,则返回。 例。truehasPermission(domainObject, 'read')Spring中文文档

hasPermission(Object targetId, String targetType, Object permission)Spring中文文档

如果用户有权访问给定权限的所提供目标,则返回。 例。truehasPermission(1, 'com.example.domain.Message', 'read')Spring中文文档

表 1.常用内置表达式
表达 描述

hasRole(String role)Spring中文文档

如果当前主体具有指定的角色,则返回。trueSpring中文文档

例:hasRole('admin')Spring中文文档

默认情况下,如果提供的角色不是以 开头,则会添加该角色。 您可以通过修改 on 来自定义此行为。ROLE_defaultRolePrefixDefaultWebSecurityExpressionHandlerSpring中文文档

hasAnyRole(String…​ roles)Spring中文文档

如果当前主体具有任何提供的角色(以逗号分隔的字符串列表形式提供),则返回。trueSpring中文文档

例:。hasAnyRole('admin', 'user')Spring中文文档

默认情况下,如果提供的角色不是以 开头,则会添加该角色。 您可以通过修改 on 来自定义此行为。ROLE_defaultRolePrefixDefaultWebSecurityExpressionHandlerSpring中文文档

hasAuthority(String authority)Spring中文文档

如果当前主体具有指定的权限,则返回。trueSpring中文文档

例:hasAuthority('read')Spring中文文档

hasAnyAuthority(String…​ authorities)Spring中文文档

如果当前主体具有任何提供的权限(以逗号分隔的字符串列表形式给出),则返回。trueSpring中文文档

例:。hasAnyAuthority('read', 'write')Spring中文文档

principalSpring中文文档

允许直接访问表示当前用户的主体对象。Spring中文文档

authenticationSpring中文文档

允许直接访问从 .AuthenticationSecurityContextSpring中文文档

permitAllSpring中文文档

计算结果始终为 。trueSpring中文文档

denyAllSpring中文文档

计算结果始终为 。falseSpring中文文档

isAnonymous()Spring中文文档

如果当前主体是匿名用户,则返回。trueSpring中文文档

isRememberMe()Spring中文文档

如果当前主体是记住我用户,则返回。trueSpring中文文档

isAuthenticated()Spring中文文档

如果用户不是匿名的,则返回。trueSpring中文文档

isFullyAuthenticated()Spring中文文档

如果用户不是匿名用户,并且不是记住我用户,则返回。trueSpring中文文档

hasPermission(Object target, Object permission)Spring中文文档

如果用户有权访问给定权限的所提供目标,则返回。 例。truehasPermission(domainObject, 'read')Spring中文文档

hasPermission(Object targetId, String targetType, Object permission)Spring中文文档

如果用户有权访问给定权限的所提供目标,则返回。 例。truehasPermission(1, 'com.example.domain.Message', 'read')Spring中文文档

Web 安全表达式

若要使用表达式来保护单个 URL,首先需要将元素中的属性设置为 。 然后,Spring Security 期望元素的属性包含 SpEL 表达式。 每个表达式的计算结果都应为布尔值,定义是否应允许访问。 以下列表显示了一个示例:use-expressions<http>trueaccess<intercept-url>Spring中文文档

<http>
	<intercept-url pattern="/admin*"
		access="hasRole('admin') and hasIpAddress('192.168.1.0/24')"/>
	...
</http>

在这里,我们定义了应用程序的区域(由 URL 模式定义)应仅对具有授予权限 () 且其 IP 地址与本地子网匹配的用户可用。 我们已经在上一节中看到了内置表达式。 表达式是特定于 Web 安全性的附加内置表达式。 它由类定义,在计算 Web 访问表达式时,该类的实例用作表达式根对象。 此对象还直接公开了名称下的对象,以便您可以直接在表达式中调用请求。 如果正在使用表达式,则会将 a 添加到命名空间使用的表达式中。 因此,如果您不使用命名空间并希望使用表达式,则必须将其中一个添加到配置中。adminadminhasRolehasIpAddressWebSecurityExpressionRootHttpServletRequestrequestWebExpressionVoterAccessDecisionManagerSpring中文文档

在 Web 安全表达式中引用 Bean

如果您希望扩展可用的表达式,您可以轻松引用您公开的任何 Spring Bean。 例如,假设您有一个名称包含以下方法签名的 Bean,则可以使用以下命令:webSecuritySpring中文文档

public class WebSecurity {
		public boolean check(Authentication authentication, HttpServletRequest request) {
				...
		}
}
class WebSecurity {
    fun check(authentication: Authentication?, request: HttpServletRequest?): Boolean {
        // ...
    }
}

然后,您可以按如下方式参考该方法:Spring中文文档

参考方法
http
    .authorizeHttpRequests(authorize -> authorize
        .requestMatchers("/user/**").access(new WebExpressionAuthorizationManager("@webSecurity.check(authentication,request)"))
        ...
    )
<http>
	<intercept-url pattern="/user/**"
		access="@webSecurity.check(authentication,request)"/>
	...
</http>
http {
    authorizeRequests {
        authorize("/user/**", "@webSecurity.check(authentication,request)")
    }
}

Web Security 表达式中的路径变量

有时,能够引用 URL 中的路径变量是件好事。 例如,考虑一个 RESTful 应用程序,该应用程序从 URL 路径中以 ./user/{userId}Spring中文文档

您可以通过将路径变量放在模式中来轻松引用路径变量。 例如,如果您有一个名称包含以下方法签名的 Bean,则可以使用以下命令:webSecuritySpring中文文档

public class WebSecurity {
		public boolean checkUserId(Authentication authentication, int id) {
				...
		}
}
class WebSecurity {
    fun checkUserId(authentication: Authentication?, id: Int): Boolean {
        // ...
    }
}

然后,您可以按如下方式参考该方法:Spring中文文档

路径变量
http
	.authorizeHttpRequests(authorize -> authorize
		.requestMatchers("/user/{userId}/**").access(new WebExpressionAuthorizationManager("@webSecurity.checkUserId(authentication,#userId)"))
		...
	);
<http>
	<intercept-url pattern="/user/{userId}/**"
		access="@webSecurity.checkUserId(authentication,#userId)"/>
	...
</http>
http {
    authorizeRequests {
        authorize("/user/{userId}/**", "@webSecurity.checkUserId(authentication,#userId)")
    }
}

在此配置中,匹配的 URL 将传入 path 变量(并将其转换为方法)。 例如,如果 URL 是 ,则传入的 ID 将是 。checkUserId/user/123/resource123Spring中文文档

方法安全表达式

方法安全性比简单的允许或拒绝规则要复杂一些。 Spring Security 3.0 引入了一些新的注解,以允许对表达式的使用提供全面支持。Spring中文文档

@Pre和@Post注释

有四个注释支持表达式属性,以允许调用前和调用后的授权检查,还支持筛选提交的集合参数或返回值。 它们是 、 、 和 。 它们的使用是通过命名空间元素启用的:@PreAuthorize@PreFilter@PostAuthorize@PostFilterglobal-method-securitySpring中文文档

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

使用@PreAuthorize和@PostAuthorize进行访问控制

最明显有用的注解是 ,它决定了方法是否真的可以被调用。 以下示例(来自“联系人”示例应用程序)使用批注:@PreAuthorize@PreAuthorizeSpring中文文档

@PreAuthorize("hasRole('USER')")
public void create(Contact contact);
@PreAuthorize("hasRole('USER')")
fun create(contact: Contact?)

这意味着仅允许具有该角色的用户访问。 显然,通过使用传统配置和所需角色的简单配置属性,可以很容易地实现同样的事情。 但是,请考虑以下示例:ROLE_USERSpring中文文档

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

在这里,我们实际上使用方法参数作为表达式的一部分来决定当前用户是否具有给定联系人的权限。 内置表达式通过应用程序上下文链接到 Spring Security ACL 模块中,正如我们在本节后面看到的那样。 您可以按名称访问任何方法参数作为表达式变量。adminhasPermission()Spring中文文档

Spring Security 可以通过多种方式解析方法参数。 Spring Security 用于发现参数名称。 默认情况下,对方法尝试以下选项。DefaultSecurityParameterNameDiscovererSpring中文文档

  • 如果 Spring Security 的注释存在于该方法的单个参数上,则使用该值。 这对于使用 JDK 8 之前的 JDK 编译的接口(不包含有关参数名称的任何信息)非常有用。 以下示例使用批注:@P@PSpring中文文档

    import org.springframework.security.access.method.P;
    
    ...
    
    @PreAuthorize("#c.name == authentication.name")
    public void doSomething(@P("c") Contact contact);
    import org.springframework.security.access.method.P
    
    ...
    
    @PreAuthorize("#c.name == authentication.name")
    fun doSomething(@P("c") contact: Contact?)

    在后台,这是通过使用 实现的,您可以自定义它以支持任何指定注释的 value 属性。AnnotationParameterNameDiscovererSpring中文文档

  • 如果 Spring Data 的注释存在于该方法的至少一个参数上,则使用该值。 这对于使用 JDK 8 之前的 JDK 编译的接口非常有用,这些接口不包含有关参数名称的任何信息。 以下示例使用批注:@Param@ParamSpring中文文档

    import org.springframework.data.repository.query.Param;
    
    ...
    
    @PreAuthorize("#n == authentication.name")
    Contact findContactByName(@Param("n") String name);
    import org.springframework.data.repository.query.Param
    
    ...
    
    @PreAuthorize("#n == authentication.name")
    fun findContactByName(@Param("n") name: String?): Contact?

    在后台,这是通过使用 实现的,您可以自定义它以支持任何指定注释的 value 属性。AnnotationParameterNameDiscovererSpring中文文档

  • 如果使用 JDK 8 编译带有参数的源代码,并且使用了 Spring 4+,则使用标准 JDK 反射 API 来发现参数名称。 这适用于类和接口。-parametersSpring中文文档

  • 最后,如果代码是使用调试符号编译的,则使用调试符号发现参数名称。 这不适用于接口,因为它们没有有关参数名称的调试信息。 对于接口,必须使用注释或 JDK 8 方法。Spring中文文档

任何 SpEL 功能在表达式中都可用,因此您还可以访问参数的属性。 例如,如果您希望特定方法仅允许访问其用户名与联系人用户名匹配的用户,则可以编写Spring中文文档

@PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);
@PreAuthorize("#contact.name == authentication.name")
fun doSomething(contact: Contact?)

在这里,我们访问另一个内置表达式,即存储在安全上下文中的表达式。 还可以使用表达式直接访问其属性。 该值通常是一个实例,因此可以使用表达式,例如 或 。authenticationAuthenticationprincipalprincipalUserDetailsprincipal.usernameprincipal.enabledSpring中文文档

使用 @PreFilter 和 @PostFilter 进行筛选

Spring Security 支持使用表达式过滤集合、数组、映射和流。 这通常是对方法的返回值执行的。 以下示例使用:@PostFilterSpring中文文档

@PreAuthorize("hasRole('USER')")
@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")
public List<Contact> getAll();
@PreAuthorize("hasRole('USER')")
@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")
fun getAll(): List<Contact?>

使用注解时,Spring Security 会遍历返回的集合或映射,并删除所提供的表达式为 false 的任何元素。 对于数组,将返回包含筛选元素的新数组实例。 引用集合中的当前对象。 使用映射时,它引用当前对象,允许您在表达式中使用 or。 您还可以在方法调用之前使用 进行筛选,尽管这是一个不太常见的要求。 语法是一样的。但是,如果有多个参数是集合类型,则必须使用此批注的属性按名称选择一个参数。@PostFilterfilterObjectMap.EntryfilterObject.keyfilterObject.value@PreFilterfilterTargetSpring中文文档

请注意,筛选显然不能替代调整数据检索查询。 如果要筛选大型集合并删除许多条目,则此操作可能效率低下。Spring中文文档

内置表达式

有一些特定于方法安全性的内置表达式,我们之前已经看到过这些表达式。 和 值非常简单,但表达式的使用需要仔细研究。filterTargetreturnValuehasPermission()Spring中文文档

PermissionEvaluator 接口

hasPermission()表达式被委托给 的实例。 它旨在连接表达式系统和 Spring Security 的 ACL 系统,允许您根据抽象权限指定对域对象的授权约束。 它对 ACL 模块没有显式依赖关系,因此如果需要,您可以将其换成替代实现。 该接口有两种方法:PermissionEvaluatorSpring中文文档

boolean hasPermission(Authentication authentication, Object targetDomainObject,
							Object permission);

boolean hasPermission(Authentication authentication, Serializable targetId,
							String targetType, Object permission);

这些方法直接映射到表达式的可用版本,但未提供第一个参数(对象)除外。 第一种用于已加载控制访问的域对象的情况。 然后,如果当前用户具有该对象的给定权限,则该表达式将返回。 第二个版本用于未加载对象但其标识符已知的情况。 还需要域对象的抽象“类型”说明符,以便加载正确的 ACL 权限。 传统上,这是对象的 Java 类,但不一定是,只要它与权限的加载方式一致即可。AuthenticationtrueSpring中文文档

若要使用表达式,必须在应用程序上下文中显式配置 a。 以下示例演示如何执行此操作:hasPermission()PermissionEvaluatorSpring中文文档

<security:global-method-security pre-post-annotations="enabled">
<security:expression-handler ref="expressionHandler"/>
</security:global-method-security>

<bean id="expressionHandler" class=
"org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
	<property name="permissionEvaluator" ref="myPermissionEvaluator"/>
</bean>

实现 的 bean 在哪里。 通常,这是 ACL 模块的实现,称为 . 有关详细信息,请参阅联系人示例应用程序配置。myPermissionEvaluatorPermissionEvaluatorAclPermissionEvaluatorSpring中文文档

方法安全元注释

您可以利用元注释来确保方法安全性,使代码更具可读性。 如果您发现在整个代码库中重复相同的复杂表达式,这将特别方便。 例如,请考虑以下事项:Spring中文文档

@PreAuthorize("#contact.name == authentication.name")

与其到处重复此操作,不如创建一个元注释:Spring中文文档

@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("#contact.name == authentication.name")
public @interface ContactPermission {}
@Retention(AnnotationRetention.RUNTIME)
@PreAuthorize("#contact.name == authentication.name")
annotation class ContactPermission

您可以将元注释用于任何 Spring Security 方法安全注释。 为了保持与规范的一致性,JSR-250 注释不支持元注释。Spring中文文档