对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

概述

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

常用内置表达式

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

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

hasRole(String role)Spring中文文档

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

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

默认情况下,如果提供的角色不以“ROLE_”开头,则将添加该角色。 这可以通过修改 on 来自定义。defaultRolePrefixDefaultWebSecurityExpressionHandlerSpring中文文档

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

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

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

默认情况下,如果提供的角色不以“ROLE_”开头,则将添加该角色。 这可以通过修改 on 来自定义。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中文文档

默认情况下,如果提供的角色不以“ROLE_”开头,则将添加该角色。 这可以通过修改 on 来自定义。defaultRolePrefixDefaultWebSecurityExpressionHandlerSpring中文文档

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

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

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

默认情况下,如果提供的角色不以“ROLE_”开头,则将添加该角色。 这可以通过修改 on 来自定义。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 将期望元素的属性包含 Spring EL 表达式。 表达式的计算结果应为布尔值,定义是否应允许访问。 例如:use-expressions<http>trueaccess<intercept-url>Spring中文文档

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

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

在 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 路径中按 id 查找用户。/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 变量(并将其转换为 checkUserId 方法)。 例如,如果 URL 是 ,则传入的 ID 将是 。/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进行访问控制

最明显有用的注释是它决定了是否可以实际调用方法。 例如(来自“联系人”示例应用程序)@PreAuthorizeSpring中文文档

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

这意味着只有角色为“ROLE_USER”的用户才能访问。 显然,使用传统配置和所需角色的简单配置属性可以很容易地实现同样的事情。 但是呢:Spring中文文档

@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 模块中,如下所示。 您可以按名称访问任何方法参数作为表达式变量。hasPermission()Spring中文文档

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

  • 如果 Spring Security 的注释存在于该方法的单个参数上,则将使用该值。 这对于使用 JDK 8 之前的 JDK 编译的接口非常有用,这些接口不包含有关参数名称的任何信息。 例如:@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 编译的接口非常有用,这些接口不包含有关参数名称的任何信息。 例如:@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 编译带有 -parameters 参数的源代码,并且使用了 Spring 4+,则使用标准 JDK 反射 API 来发现参数名称。 这适用于类和接口。Spring中文文档

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

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

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

在这里,我们将访问另一个内置表达式,该表达式存储在安全上下文中。 您还可以使用表达式 直接访问其“principal”属性。 该值通常是一个实例,因此您可以使用类似 or 的表达式。authenticationAuthenticationprincipalUserDetailsprincipal.usernameprincipal.enabledSpring中文文档

不太常见的是,您可能希望在调用该方法后执行访问控制检查。 这可以使用注释来实现。 若要访问方法的返回值,请使用表达式中的内置名称。@PostAuthorizereturnObjectSpring中文文档

使用 @PreFilter 和 @PostFilter 进行筛选

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

@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 的任何元素。 对于数组,将返回包含筛选元素的新数组实例。 该名称引用集合中的当前对象。 如果使用地图,它将引用允许使用或在表达中的当前对象。 您还可以在方法调用之前使用 进行筛选,尽管这是一个不太常见的要求。 语法是一样的,但是如果有多个参数是集合类型,则必须使用此注释的属性按名称选择一个。@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);

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

若要使用表达式,必须在应用程序上下文中显式配置 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中文文档