SpringSecurity中文文档(Servlet Method Security)

news2024/11/15 8:54:59

Method Security

除了在请求级别进行建模授权之外,Spring Security 还支持在方法级别进行建模。

您可以在应用程序中激活它,方法是使用@EnableMethodSecurity 注释任何@Configuration 类,或者将 < method-security > 添加到任何 XML 配置文件中,如下所示:

@EnableMethodSecurity

然后,您可以立即使用@PreAuthorize、@PostAuthorize、@PreFilter 和@PostFilter 对任何 Spring 管理的类或方法进行注释,以授权方法调用,包括输入参数和返回值。

SpringBootStarterSecurity 默认情况下不激活方法级授权。

Method Security 还支持许多其他用例,包括 AspectJ 支持、自定义注释和几个配置点。考虑学习以下用例:

  • Migrating from @EnableGlobalMethodSecurity
  • Understanding how method security works and reasons to use it
  • Comparing request-level and method-level authorization
  • Authorizing methods with @PreAuthorize and @PostAuthorize
  • Providing fallback values when authorization is denied
  • Filtering methods with @PreFilter and @PostFilter
  • Authorizing methods with JSR-250 annotations
  • Authorizing methods with AspectJ expressions
  • Integrating with AspectJ byte-code weaving
  • Coordinating with @Transactional and other AOP-based annotations
  • Customizing SpEL expression handling
  • Integrating with custom authorization systems

How Method Security Works

Spring Security 的方法授权支持在以下方面很方便:

  • 提取细粒度授权逻辑;例如,当方法参数和返回值有助于授权决策时。
  • 在服务层强制执行安全性
  • 在风格上更倾向于基于注释而非基于HttpSecurity的配置

由于Method Security是使用Spring AOP构建的,因此您可以访问它的所有表达能力,以便根据需要覆盖Spring Security的默认值。

如前所述,首先将@EnableMethodSecurity 添加到@Configuration 类或者在 Spring XML 配置文件中添加 < sec: method-security/> 。

这个注释和 XML 元素分别取代了@EnableGlobalMethodSecurity 和 < sec: global-method-security/> :

  • 使用简化的 AuthorizationManagerAPI 而不是元数据源、配置属性、决策管理器和选民。
  • 支持直接基于 bean 的配置,而不需要扩展 GlobalMethodSecurityConfiguration 来定制 bean
  • 使用原生 Spring AOP 构建,移除了抽象,并允许您使用 Spring AOP 构建块来定制
  • 检查冲突注解,以确保明确的安全配置
  • 符合 JSR-250
  • 默认启用 @PreAuthorize、@PostAuthorize、@PreFilter 和 @PostFilter

如果您正在使用@EnableGlobalMethodSecurity 或 < global-method-security/> ,那么现在不推荐使用它们,并鼓励您进行迁移。

方法授权是 before- and after-method授权的组合。考虑一个服务 bean,它的注释方式如下:

@Service
public class MyCustomerService {
    @PreAuthorize("hasAuthority('permission:read')")
    @PostAuthorize("returnObject.owner == authentication.name")
    public Customer readCustomer(String id) { ... }
}

当方法安全性被激活时,对 MyCustomerService # readCustomer 的给定调用可能类似于下面的内容:

methodsecurity

  1. Spring AOP 调用了其代理方法 readCustomer。在代理的其他顾问中,它调用了匹配 @PreAuthorize 切入点的方法 AuthorizationManagerBeforeMethodInterceptor。
  2. 拦截器调用了 PreAuthorizeAuthorizationManager#check。
  3. 授权管理器使用 MethodSecurityExpressionHandler 来解析注解的 SpEL 表达式,并从包含 Supplier 和 MethodInvocation 的 MethodSecurityExpressionRoot 构建相应的 EvaluationContext。
  4. 拦截器使用这个上下文来评估表达式;具体来说,它从 Supplier 中读取 Authentication 并检查它是否有权限:read 在其权限集合中。
  5. 如果评估通过,那么 Spring AOP 继续调用方法。
  6. 如果没有,拦截器发布一个 AuthorizationDeniedEvent 并抛出一个 AccessDeniedException,ExceptionTranslationFilter 捕获并返回一个 403 状态码到响应。
  7. 方法返回后,Spring AOP 调用了匹配 @PostAuthorize 切入点的方法 AuthorizationManagerAfterMethodInterceptor,其操作与上述相同,但使用 PostAuthorizeAuthorizationManager。
  8. 如果评估通过(在这种情况下,返回值属于已登录的用户),则处理继续正常。
  9. 如果没有,拦截器发布一个 AuthorizationDeniedEvent 并抛出一个 AccessDeniedException,ExceptionTranslationFilter 捕获并返回一个 403 状态码到响应。

如果没有在 HTTP 请求的上下文中调用该方法,则可能需要自己处理 AccessDeniedException

Multiple Annotations Are Computed In Series

如上所述,如果一个方法调用涉及多个 Method Security 注释,则每个注释一次处理一个。这意味着它们可以被集体看作是“被捆绑在一起”的。换句话说,对于要授权的调用,所有的注释检查都需要通过授权。

Repeated Annotations Are Not Supported

也就是说,不支持在同一方法上重复相同的注释。例如,不能在同一个方法上放置@PreAuthorize 两次。

取而代之的是,使用 SpEL 的布尔支持或其对委托到单独 bean 的支持。

Each Annotation Has Its Own Pointcut

每个注释都有自己的切入点实例,它从方法及其封闭类开始,在整个对象层次结构中查找该注释或其元注释对应物。

您可以在 AuthorizationMethodPointcut 中看到这方面的详细信息。

Each Annotation Has Its Own Method Interceptor

每个注解都有其自己的专用方法拦截器。这样做的原因是使事情更具可组合性。例如,如果需要,您可以禁用 Spring Security 的默认行为,只发布 @PostAuthorize 方法拦截器。

方法拦截器如下:

  • 对于 @PreAuthorize,Spring Security 使用 AuthorizationManagerBeforeMethodInterceptor#preAuthorize,进而使用 PreAuthorizeAuthorizationManager。
  • 对于 @PostAuthorize,Spring Security 使用 AuthorizationManagerBeforeMethodInterceptor#postAuthorize,进而使用 PostAuthorizeAuthorizationManager。
  • 对于 @PreFilter,Spring Security 使用 PreFilterAuthorizationMethodInterceptor。
  • 对于 @PostFilter,Spring Security 使用 PostFilterAuthorizationMethodInterceptor。
  • 对于 @Secured,Spring Security 使用 AuthorizationManagerBeforeMethodInterceptor#secured,进而使用 SecuredAuthorizationManager。
  • 对于 JSR-250 注解,Spring Security 使用 AuthorizationManagerBeforeMethodInterceptor#jsr250,进而使用 Jsr250AuthorizationManager。

总的来说,您可以将以下列表视为添加 @EnableMethodSecurity 时 Spring Security 发布的拦截器的代表性示例。

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor preAuthorizeMethodInterceptor() {
    return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor postAuthorizeMethodInterceptor() {
    return AuthorizationManagerAfterMethodInterceptor.postAuthorize();
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor preFilterMethodInterceptor() {
    return AuthorizationManagerBeforeMethodInterceptor.preFilter();
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor postFilterMethodInterceptor() {
    return AuthorizationManagerAfterMethodInterceptor.postFilter();
}

在复杂的 SpEL 表达式上优先授权

通常,引入一个复杂的 SpEL 表达式是很诱人的,如下所示:

@PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")

然而,您可以将权限:read 授予具有 ROLE_ADMIN 角色的人。一种方法是通过 RoleHierarchy 实现:

@Bean
static RoleHierarchy roleHierarchy() {
    return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > permission:read");
}

然后在 MethodSecurityExpressionHandler 实例中设置它。这样你就可以得到一个更简单的@PreAuthorize 表达式,如下所示:

@PreAuthorize("hasAuthority('permission:read')")

或者,在可能的情况下,在登录时将特定于应用程序的授权逻辑改编为授予的权限。

Comparing Request-level vs Method-level Authorization

您应该何时选择方法级授权而不是请求级授权?这很大程度上取决于个人喜好;然而,考虑以下每种方式的优点列表,以帮助您做出决定。

** request-level**request-levelmethod-level
authorization typecoarse-grainedfine-grained
configuration locationdeclared in a config classlocal to method declaration
configuration styleDSLAnnotations
authorization definitionsprogrammaticSpEL

主要的折衷似乎是您希望授权规则存在于何处。

Authorizing with Annotations

Spring Security 支持方法级授权的主要方式是通过可以添加到方法、类和接口的注释。

Authorizing Method Invocation with @PreAuthorize

当 Method Security 处于活动状态时,可以使用@PreAuthorize 注释对方法进行注释,如下所示:

@Component
public class BankService {
	@PreAuthorize("hasRole('ADMIN')")
	public Account readAccount(Long id) {
        // ... is only invoked if the `Authentication` has the `ROLE_ADMIN` authority
	}
}

这意味着只有当提供的表达式 hasRole (‘ ADMIN’)通过时才能调用该方法。

然后,您可以测试该类,以确认它正在执行授权规则,如下所示:

@Autowired
BankService bankService;

@WithMockUser(roles="ADMIN")
@Test
void readAccountWithAdminRoleThenInvokes() {
    Account account = this.bankService.readAccount("12345678");
    // ... assertions
}

@WithMockUser(roles="WRONG")
@Test
void readAccountWithWrongRoleThenAccessDenied() {
    assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(
        () -> this.bankService.readAccount("12345678"));
}

虽然@PreAuthorize 对于声明所需的权限非常有帮助,但是它也可以用来计算涉及方法参数的更复杂的表达式。

Authorization Method Results with @PostAuthorize

当 Method Security 处于活动状态时,可以使用@PostAuthorize 注释对方法进行注释,如下所示:

@Component
public class BankService {
	@PostAuthorize("returnObject.owner == authentication.name")
	public Account readAccount(Long id) {
        // ... is only returned if the `Account` belongs to the logged in user
	}
}

这意味着该方法只能在所提供的表达式 returObject.owner = = authentication.name 通过时才能返回值。ReturObject 表示要返回的 Account 对象。

然后,您可以测试该类,以确认它正在执行授权规则:

@Autowired
BankService bankService;

@WithMockUser(username="owner")
@Test
void readAccountWhenOwnedThenReturns() {
    Account account = this.bankService.readAccount("12345678");
    // ... assertions
}

@WithMockUser(username="wrong")
@Test
void readAccountWhenNotOwnedThenAccessDenied() {
    assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(
        () -> this.bankService.readAccount("12345678"));
}

@ PostAuthorize 也可以是一个元注释,在类或接口级别定义,并使用 SpEL Authorization Expressions。

@ PostAuthorize 在防御不安全的直接对象引用时特别有用。事实上,它可以定义为如下元注释:

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@PostAuthorize("returnObject.owner == authentication.name")
public @interface RequireOwnership {}

允许您以下列方式对服务进行注释:

@Component
public class BankService {
	@RequireOwnership
	public Account readAccount(Long id) {
        // ... is only returned if the `Account` belongs to the logged in user
	}
}

结果是,上述方法只有在其所有者属性与登录用户的名称匹配时才返回 Account。如果没有,SpringSecurity 将抛出 AccessDeniedException 并返回403状态代码。

Filtering Method Parameters with @PreFilter

当 Method Security 处于活动状态时,可以使用@PreFilter 注释对方法进行注释,如下所示:

@Component
public class BankService {
	@PreFilter("filterObject.owner == authentication.name")
	public Collection<Account> updateAccounts(Account... accounts) {
        // ... `accounts` will only contain the accounts owned by the logged-in user
        return updated;
	}
}

这意味着从表达式 filterObject.owner = = authentication.name 失败的帐户中过滤出任何值。FilterObject 表示帐户中的每个帐户,用于测试每个帐户。

然后,您可以用以下方式测试该类,以确认它正在执行授权规则:

@Autowired
BankService bankService;

@WithMockUser(username="owner")
@Test
void updateAccountsWhenOwnedThenReturns() {
    Account ownedBy = ...
    Account notOwnedBy = ...
    Collection<Account> updated = this.bankService.updateAccounts(ownedBy, notOwnedBy);
    assertThat(updated).containsOnly(ownedBy);
}

@ PreFilter 支持数组、集合、映射和流(只要流仍然打开)。

例如,上面的 updateAccount 声明的功能与以下其他四个声明的功能相同:

@PreFilter("filterObject.owner == authentication.name")
public Collection<Account> updateAccounts(Account[] accounts)

@PreFilter("filterObject.owner == authentication.name")
public Collection<Account> updateAccounts(Collection<Account> accounts)

@PreFilter("filterObject.value.owner == authentication.name")
public Collection<Account> updateAccounts(Map<String, Account> accounts)

@PreFilter("filterObject.owner == authentication.name")
public Collection<Account> updateAccounts(Stream<Account> accounts)

结果是,上面的方法只有 Account 实例,其所有者属性与登录用户的名称匹配。

Filtering Method Results with @PostFilter

当 Method Security 处于活动状态时,可以使用@PostFilter 注释对方法进行注释,如下所示:

@Component
public class BankService {
	@PostFilter("filterObject.owner == authentication.name")
	public Collection<Account> readAccounts(String... ids) {
        // ... the return value will be filtered to only contain the accounts owned by the logged-in user
        return accounts;
	}
}

这意味着从表达式 filterObject.owner = = authentication.name 失败的返回值中过滤掉任何值。FilterObject 表示帐户中的每个帐户,用于测试每个帐户。

然后,您可以像这样测试该类,以确认它正在执行授权规则:

@Autowired
BankService bankService;

@WithMockUser(username="owner")
@Test
void readAccountsWhenOwnedThenReturns() {
    Collection<Account> accounts = this.bankService.updateAccounts("owner", "not-owner");
    assertThat(accounts).hasSize(1);
    assertThat(accounts.get(0).getOwner()).isEqualTo("owner");
}

@ PostFilter 支持数组、集合、映射和流(只要流仍然打开)。

例如,上面的 readAccount 声明的功能与以下其他三个声明的功能相同:

@PostFilter("filterObject.owner == authentication.name")
public Account[] readAccounts(String... ids)

@PostFilter("filterObject.value.owner == authentication.name")
public Map<String, Account> readAccounts(String... ids)

@PostFilter("filterObject.owner == authentication.name")
public Stream<Account> readAccounts(String... ids)

结果是,上面的方法将返回 Account 实例,其所有者属性与登录用户的名称匹配。

Authorizing Method Invocation with @Secured

@Secured 是授权调用的遗留选项。@PreAuthorize 取而代之,建议使用。

要使用@Secred 注释,您应该首先更改您的 Method Security 声明,以启用它,如下所示:

@EnableMethodSecurity(securedEnabled = true)

这将导致 Spring Security 发布相应的方法拦截器,该拦截器授权使用@Secure 注释的方法、类和接口。

Authorizing Method Invocation with JSR-250 Annotations

如果您希望使用 JSR-250注释,SpringSecurity 也支持这一点。@ PreAuthorize 具有更强的表达能力,因此推荐使用。

要使用 JSR-250注释,您应该首先更改您的 Method Security 声明,以便像下面这样启用它们:

@EnableMethodSecurity(jsr250Enabled = true)

这将导致 Spring Security 发布相应的方法拦截器,该拦截器授权使用@RolesAllows、@PermitAll 和@DenyAll 注释的方法、类和接口。

Declaring Annotations at the Class or Interface Level

它还支持在类和接口级别使用 Method Security 注释。

如果在类层面是这样的:

@Controller
@PreAuthorize("hasAuthority('ROLE_USER')")
public class MyController {
    @GetMapping("/endpoint")
    public String endpoint() { ... }
}

那么所有的方法都继承了类级别的行为。

或者,如果它在类和方法级别都像下面这样声明:

@Controller
@PreAuthorize("hasAuthority('ROLE_USER')")
public class MyController {
    @GetMapping("/endpoint")
    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    public String endpoint() { ... }
}

然后,声明该注释的方法将覆盖类级注释。

对于接口也是如此,但是如果一个类从两个不同的接口继承了注释,那么启动就会失败。这是因为 SpringSecurity 无法判断您想要使用哪一个。

在这种情况下,可以通过向具体方法添加注释来解决歧义。

Using Meta Annotations

方法安全性支持元注释。这意味着您可以使用任何注释,并基于特定于应用程序的用例提高可读性。

例如,您可以将@PreAuthorize (“ hasRole (‘ ADMIN’)”)简化为@IsAdmin,如下所示:

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('ADMIN')")
public @interface IsAdmin {}

其结果是,在你的安全方法,你现在可以做以下代替:

@Component
public class BankService {
	@IsAdmin
	public Account readAccount(Long id) {
        // ... is only returned if the `Account` belongs to the logged in user
	}
}

这会产生更易读的方法定义。

Templating Meta-Annotation Expressions

您还可以选择使用元注释模板,它允许更强大的注释定义。

首先,发布以下 bean:

@Bean
static PrePostTemplateDefaults prePostTemplateDefaults() {
	return new PrePostTemplateDefaults();
}

现在,您可以创建像@Hasrole 这样功能更强大的程序,而不是@IsAdmin,如下所示:

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('{value}')")
public @interface HasRole {
	String value();
}

其结果是,在你的安全方法,你现在可以做以下代替:

@Component
public class BankService {
	@HasRole("ADMIN")
	public Account readAccount(Long id) {
        // ... is only returned if the `Account` belongs to the logged in user
	}
}

请注意,这也适用于方法变量和所有注释类型,但是您需要注意正确地处理引号,以便得到的 SpEL 表达式是正确的。

例如,考虑以下@HasAnyrole 注释:

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAnyRole({roles})")
public @interface HasAnyRole {
	String[] roles();
}

在这种情况下,您将注意到不应该在表达式中使用引号,而应该在参数值中使用,如下所示:

@Component
public class BankService {
	@HasAnyRole(roles = { "'USER'", "'ADMIN'" })
	public Account readAccount(Long id) {
        // ... is only returned if the `Account` belongs to the logged in user
	}
}

这样,一旦替换,表达式就变成@PreAuthorize (“ hasAnyrole (‘ USER’,‘ ADMIN’)”)。

Enabling Certain Annotations

您可以关闭@EnableMethodSecurity 的预配置并将其替换为您自己的配置。如果要自定义 AuthorizationManager 或 Pointcut,可以选择这样做。或者您可能只想启用特定的注释,比如@PostAuthorize。

你可以这样做:

Only @PostAuthorize Configuration

@Configuration
@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor postAuthorize() {
		return AuthorizationManagerAfterMethodInterceptor.postAuthorize();
	}
}

上面的代码片段通过首先禁用 Method Security 的预配置,然后发布@PostAuthorize 拦截器本身来实现这一点。

Authorizing with <intercept-methods>

虽然使用 Spring Security 的基于注释的支持是方法安全的首选,但是您也可以使用 XML 来声明 bean 授权规则。

如果需要在 XML 配置中声明它,可以像下面这样使用 < 拦截方法 > :

<bean class="org.mycompany.MyController">
    <intercept-methods>
        <protect method="get*" access="hasAuthority('read')"/>
        <protect method="*" access="hasAuthority('write')"/>
    </intercept-methods>
</bean>

Authorizing Methods Programmatically

正如您已经看到的,有几种方法可以使用 Method Security SpEL 表达式指定非常重要的授权规则。

有许多方法可以让您的逻辑基于 Java 而不是基于 SpEL。这使得可以访问整个 Java 语言,从而提高可测试性和流控制。

Using a Custom Bean in SpEL

以编程方式授权方法的第一种方法是两步进程。

首先,声明一个 bean,该 bean 具有一个接受 MethodSecurityExpressionOperations 实例的方法,如下所示:

@Component("authz")
public class AuthorizationLogic {
    public boolean decide(MethodSecurityExpressionOperations operations) {
        // ... authorization logic
    }
}

然后,按照以下方式在注释中引用该 bean:

@Controller
public class MyController {
    @PreAuthorize("@authz.decide(#root)")
    @GetMapping("/endpoint")
    public String endpoint() {
        // ...
    }
}

SpringSecurity 将为每个方法调用调用该 bean 上的给定方法。

这样做的好处是,所有的授权逻辑都在一个单独的类中,可以独立进行单元测试和验证是否正确。它还可以访问完整的 Java 语言。

如果您想要包含更多关于决策性质的信息,您可以像下面这样返回一个自定义 AuthorizationDecision:

@Component("authz")
public class AuthorizationLogic {
    public AuthorizationDecision decide(MethodSecurityExpressionOperations operations) {
        // ... authorization logic
        return new MyAuthorizationDecision(false, details);
    }
}

或引发自定义 AuthorizationDeniedException 实例。但是请注意,最好是返回一个对象,因为这样不会产生生成堆栈跟踪的开销。

然后,您可以在自定义如何处理授权结果时访问自定义详细信息。

Using a Custom Authorization Manager

以编程方式授权方法的第二种方法是创建自定义 AuthorizationManager。

首先,声明一个授权管理器实例,比如:

@Component
public class MyAuthorizationManager implements AuthorizationManager<MethodInvocation>, AuthorizationManager<MethodInvocationResult> {
    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
        // ... authorization logic
    }

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocationResult invocation) {
        // ... authorization logic
    }
}

然后,使用切入点发布方法拦截器,该切入点对应于您希望 AuthorizationManager 运行的时间。例如,您可以将@PreAuthorize 和@PostAuthorize 的工作方式替换为:

@Configuration
@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
    @Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor preAuthorize(MyAuthorizationManager manager) {
		return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor postAuthorize(MyAuthorizationManager manager) {
		return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
	}
}

Customizing Expression Handling

或者,第三,您可以自定义如何处理每个 SpEL 表达式。为此,可以公开自定义 MethodSecurityExpressionHandler,如下所示:

Custom MethodSecurityExpressionHandler

@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
	DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
	handler.setRoleHierarchy(roleHierarchy);
	return handler;
}

还可以继承 DefaultMessageSecurityExpressionHandler,以在默认值之外添加自己的自定义授权表达式。

Authorizing with AspectJ

Matching Methods with Custom Pointcuts

在 SpringAOP 上构建,您可以声明与注释无关的模式,类似于请求级授权。这具有集中方法级授权规则的潜在优势。

例如,您可以使用发布自己的 Advisor 或使用 < protected-pointcut > 将 AOP 表达式与服务层的授权规则匹配起来,如下所示:

import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor protectServicePointcut() {
    AspectJExpressionPointcut pattern = new AspectJExpressionPointcut()
    pattern.setExpression("execution(* com.mycompany.*Service.*(..))")
    return new AuthorizationManagerBeforeMethodInterceptor(pattern, hasRole("USER"))
}

Integrate with AspectJ Byte-weaving

通过使用 AspectJ 将 Spring Security 通知编织到 bean 的字节代码中,有时可以提高性能。

在设置 AspectJ 之后,您可以非常简单地在@EnableMethodSecurity 注释或 < method-security > 元素中声明您正在使用 AspectJ:

@EnableMethodSecurity(mode=AdviceMode.ASPECTJ)

结果将是 Spring Security 将其advisors 作为 AspectJ 通知发布,以便它们可以相应地编入其中。

Specifying Order

如前所述,每个注释都有一个 Spring AOP 方法拦截器,并且每个注释都在 Spring AOP advisor 链中有一个位置。

也就是说,@PreFilter 方法拦截器的顺序是100,@PreAuthorize 的顺序是200,依此类推。

值得注意的是,还有其他基于 AOP 的注释,如@EnableTransactionManagement,其顺序为 Integer.MAX _ VALUE。换句话说,默认情况下,它们位于顾问链的末尾。

有时候,在 SpringSecurity 之前执行其他通知是很有价值的。例如,如果您有一个带有@Transactional 和@PostAuthorize 注释的方法,您可能希望在@PostAuthorize 运行时事务仍然处于打开状态,以便 AccessDeniedException 将导致回滚。

要让@EnableTransactionManagement 在方法授权通知运行之前打开事务,可以像下面这样设置@EnableTransactionManagement 的顺序:

@EnableTransactionManagement(order = 0)

由于最早的方法拦截器(@PreFilter)被设置为100的顺序,如果设置为零,则意味着事务通知将在所有 Spring Security 通知之前运行。

Expressing Authorization with SpEL

您已经看到了几个使用 SpEL 的示例,因此现在让我们更深入地介绍一下 API。

SpringSecurity 将其所有授权字段和方法封装在一组根对象中。最通用的根对象称为 SecurityExpressionRoot,它是 MethodSecurityExpressionRoot 的基础。当准备计算授权表达式时,SpringSecurity 将此根对象提供给 MethodSecurityevalationContext。

Using Authorization Expression Fields and Methods

它提供的第一件事是增强了 SpEL 表达式的授权字段和方法集。以下是最常用方法的简要概述:

  • PermitAll-该方法不需要调用任何授权; 注意,在这种情况下,永远不会从会话检索 Authentication
  • DenyAll-在任何情况下都不允许使用该方法; 请注意,在这种情况下,永远不会从会话检索 Authentication
  • HasAuthority ——该方法要求 Authentication 具有与给定值匹配的 GrantedAuthority
  • HasRole-hasAuthority 的一个快捷方式,它将 ROLE _ 或其他配置为默认前缀的内容作为前缀
  • HasAnyAuthority ——该方法要求 Authentication 具有与任何给定值匹配的 GrantedAuthority
  • HasAnyrole-hasAnyAuthority 的一个快捷方式,它将 ROLE _ 或任何被配置为默认前缀的东西作为前缀
  • HasPermission-一个连接到您的 Permisonevalator 实例的钩子,用于执行对象级授权

以下是一些最常见的领域:

  • Authentication ——与此方法调用关联的 Authentication 实例
  • principal-与此方法调用相关联的 Authentication # getPrincipal

现在已经了解了模式、规则以及如何将它们配对在一起,您应该能够理解在这个更复杂的示例中发生了什么:

@Component
public class MyService {
    @PreAuthorize("denyAll")
    MyResource myDeprecatedMethod(...);

    @PreAuthorize("hasRole('ADMIN')")
    MyResource writeResource(...)

    @PreAuthorize("hasAuthority('db') and hasRole('ADMIN')")
    MyResource deleteResource(...)

    @PreAuthorize("principal.claims['aud'] == 'my-audience'")
    MyResource readResource(...);

	@PreAuthorize("@authz.check(authentication, #root)")
    MyResource shareResource(...);
}
  1. 由于任何原因,任何人都不能调用此方法
  2. 此方法只能由授予 ROLE _ ADMIN 权限的身份验证调用
  3. 此方法只能由授予 db 和 ROLE _ ADMIN 权限的身份验证调用
  4. 此方法只能由具有等于 “my-audience” 的 aud 声明的主体调用。
  5. 此方法只能在 bean authz 的检查方法返回 true 时调用。

Using Method Parameters

SpringSecurity 还支持包装任何注释了其方法安全性注释的对象。

实现这一点的最简单方法是使用@AuthorizeReturnObject 注释标记任何返回您希望授权的对象的方法。

例如,考虑以下 User 类:

public class User {
	private String name;
	private String email;

	public User(String name, String email) {
		this.name = name;
		this.email = email;
	}

	public String getName() {
		return this.name;
	}

    @PreAuthorize("hasAuthority('user:read')")
    public String getEmail() {
		return this.email;
    }
}

给定一个像这样的界面:

public class UserRepository {
	@AuthorizeReturnObject
    Optional<User> findByName(String name) {
		// ...
    }
}

然后,从 findById 返回的任何 User 都将像其他 Spring Security 保护的组件一样受到保护:

@Autowired
UserRepository users;

@Test
void getEmailWhenProxiedThenAuthorizes() {
    Optional<User> securedUser = users.findByName("name");
    assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> securedUser.get().getEmail());
}

Using @AuthorizeReturnObject at the class level

@AuthorizeReturnObject可以放置在类级别。但是请注意,这意味着 Spring Security 将尝试代理任何返回对象,包括 String、 Integer 和其他类型。这通常不是你想要做的.

如果您希望对方法返回值类型(如 int、 String、 Double 或这些类型的集合)的类或接口使用@AuthorizeReturnObject,那么您还应该发布适当的 AuthorizationAdvisorProxyFactory。目标访问者如下:

@Bean
static Customizer<AuthorizationAdvisorProxyFactory> skipValueTypes() {
    return (factory) -> factory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes());
}

Programmatically Proxying

还可以通过编程方式代理给定对象。

为此,您可以自动连接提供的 AuthorizationProxyFactory 实例,该实例基于您配置的方法安全拦截器。如果您使用的是@EnableMethodSecurity,那么这意味着它默认拥有@PreAuthorize、@PostAuthorize、@PreFilter 和@PostFilter 的拦截器。

您可以通过以下方式代理用户的实例:

@Autowired
AuthorizationProxyFactory proxyFactory;

@Test
void getEmailWhenProxiedThenAuthorizes() {
    User user = new User("name", "email");
    assertThat(user.getEmail()).isNotNull();
    User securedUser = proxyFactory.proxy(user);
    assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(securedUser::getEmail);
}

Manual Construction

如果需要不同于 Spring Security 默认值的东西,还可以定义自己的实例。

例如,如果像下面这样定义 AuthorizationProxyFactory 实例:

import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
import static org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.preAuthorize;
// ...

AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
// and if needing to skip value types
proxyFactory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes());

然后您可以将 User 的任何实例包装如下:

@Test
void getEmailWhenProxiedThenAuthorizes() {
	AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
    User user = new User("name", "email");
    assertThat(user.getEmail()).isNotNull();
    User securedUser = proxyFactory.proxy(user);
    assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(securedUser::getEmail);
}

Proxying Collections

AuthorizationProxyFactory 通过代理元素类型和通过代理值类型映射来支持 Java 集合、流、数组、可选项和迭代器。

这意味着在代理 List 对象时,以下代码也可以工作:

@Test
void getEmailWhenProxiedThenAuthorizes() {
	AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
    List<User> users = List.of(ada, albert, marie);
    List<User> securedUsers = proxyFactory.proxy(users);
	securedUsers.forEach((securedUser) ->
        assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(securedUser::getEmail));
}

Proxying Classes

在有限的情况下,代理 Class 本身可能是有价值的,AuthorizationProxyFactory 也支持这一点。这大致相当于在 Spring 框架中调用 ProxyFactory # getProxyClass 以支持创建代理。

当您需要提前构造代理类(如 SpringAOT)时,这种方法非常方便。

Support for All Method Security Annotations

AuthorizationProxyFactory 支持应用程序中启用的任何方法安全注释。它基于作为 bean 发布的 AuthorizationAdvisor 类。

因为@EnableMethodSecurity 默认发布@PreAuthorize、@PostAuthorize、@PreFilter 和@PostFilter 顾问,所以通常不需要做任何事情来激活该能力。

Custom Advice

如果您有也希望应用的安全性建议,您可以像下面这样发布您自己的 AuthorizationAdvisor:

@EnableMethodSecurity
class SecurityConfig {
    @Bean
    static AuthorizationAdvisor myAuthorizationAdvisor() {
        return new AuthorizationAdvisor();
    }
}

Spring Security 将把该顾问添加到 AuthorizationProxyFactory 在代理对象时添加的通知集中。

Working with Jackson

这个特性的一个强大用途是从控制器返回一个安全值,如下所示:

@RestController
public class UserController {
	@Autowired
    AuthorizationProxyFactory proxyFactory;

	@GetMapping
    User currentUser(@AuthenticationPrincipal User user) {
        return this.proxyFactory.proxy(user);
    }
}

但是,如果您正在使用 Jackson,这可能会导致一个序列化错误,如下所示:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Direct self-reference leading to cycle

这是由于 Jackson 使用 CGLIB 代理的方式。为了解决这个问题,在 User 类的顶部添加以下注释:

@JsonSerialize(as = User.class)
public class User {

}

最后,您需要发布一个自定义拦截器来捕获为每个字段抛出的 AccessDeniedException,您可以这样做:

@Component
public class AccessDeniedExceptionInterceptor implements AuthorizationAdvisor {
    private final AuthorizationAdvisor advisor = AuthorizationManagerBeforeMethodInterceptor.preAuthorize();

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		try {
			return invocation.proceed();
		} catch (AccessDeniedException ex) {
			return null;
		}
	}

	@Override
	public Pointcut getPointcut() {
		return this.advisor.getPointcut();
	}

	@Override
	public Advice getAdvice() {
		return this;
	}

	@Override
	public int getOrder() {
		return this.advisor.getOrder() - 1;
	}
}

然后,您将看到一个不同的基于用户授权级别的 JSON 序列化。如果他们没有user:read权限,那么他们会看到:

{
    "name" : "name",
    "email" : null
}

如果他们真的有这个权限,他们会看到:

{
    "name" : "name",
    "email" : "email"
}

Providing Fallback Values When Authorization is Denied

在某些情况下,您可能不希望在没有所需权限的情况下调用方法时引发 AuthorizationDeniedException。相反,您可能希望返回一个后处理的结果,比如一个被屏蔽的结果,或者在调用方法之前拒绝授权的情况下返回一个默认值。

Spring Security 通过使用@HandleAuthorizationDenied 支持处理在方法调用时被拒绝的授权。该处理程序适用于在@PreAuthorize 和@PostAuthorize 注释中发生的被拒绝的授权,以及从方法调用本身抛出的 AuthorizationDeniedException。

让我们考虑一下上一节的例子,但是我们不创建 AccessDeniedExceptionInterceptor 来将 AccessDeniedException 转换为 null 返回值,而是使用@HandleAuthorizationDenied 的 handlerClass 属性:

public class NullMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler {

    @Override
    public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
        return null;
    }

}

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public NullMethodAuthorizationDeniedHandler nullMethodAuthorizationDeniedHandler() {
        return new NullMethodAuthorizationDeniedHandler();
    }

}

public class User {
    // ...

    @PreAuthorize(value = "hasAuthority('user:read')")
    @HandleAuthorizationDenied(handlerClass = NullMethodAuthorizationDeniedHandler.class)
    public String getEmail() {
        return this.email;
    }
}
  1. 创建返回空值的 MethodAuthorizationDeniedHandler 的实现
  2. 将 NullMethodAuthorizationDeniedHandler 注册为 bean
  3. 用@HandleAuthorizationDenied 注释该方法,并将 NullMethodAuthorizationDeniedHandler 传递给 handlerClass 属性

然后您可以验证返回的是 null 值而不是 AccessDeniedException:

您还可以使用@Component 注释类,而不用创建@Bean 方法

@Autowired
UserRepository users;

@Test
void getEmailWhenProxiedThenNullEmail() {
    Optional<User> securedUser = users.findByName("name");
    assertThat(securedUser.get().getEmail()).isNull();
}

Using the Denied Result From the Method Invocation

在某些情况下,您可能希望返回从被拒绝的结果派生的安全结果。例如,如果用户未被授权查看电子邮件地址,您可能希望对原始电子邮件地址应用一些屏蔽,即 useremail@example.com 将成为 use * * * *@example.com。

对于这些场景,可以重写 MethodAuthorizationDeniedHandler 中的 handleDeniedInvocationResult,该 Handler 将 MethodInvocationResult 作为参数。让我们继续前面的例子,但是不返回 null,我们将返回一个电子邮件的屏蔽值:

public class EmailMaskingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler {

    @Override
    public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
        return "***";
    }

    @Override
    public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) {
        String email = (String) methodInvocationResult.getResult();
        return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*");
    }

}

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public EmailMaskingMethodAuthorizationDeniedHandler emailMaskingMethodAuthorizationDeniedHandler() {
        return new EmailMaskingMethodAuthorizationDeniedHandler();
    }

}

public class User {
    // ...

    @PostAuthorize(value = "hasAuthority('user:read')")
    @HandleAuthorizationDenied(handlerClass = EmailMaskingMethodAuthorizationDeniedHandler.class)
    public String getEmail() {
        return this.email;
    }
}
  1. 创建 MethodAuthorizationDeniedHandler 的实现,该实现返回未授权结果值的屏蔽值
  2. 将 EmailMaskingMethodAuthorizationDeniedHandler 注册为 bean
  3. 用@HandleAuthorizationDenied 注释该方法,并将 EmailMaskingMethodAuthorizationDeniedHandler 传递给 handlerClass 属性

然后您可以验证是否返回了一封蒙蔽的电子邮件,而不是一个 AccessDeniedException:

@Autowired
UserRepository users;

@Test
void getEmailWhenProxiedThenMaskedEmail() {
    Optional<User> securedUser = users.findByName("name");
    // email is useremail@example.com
    assertThat(securedUser.get().getEmail()).isEqualTo("use******@example.com");
}

在实现 MethodAuthorizationDeniedHandler 时,您可以选择返回的类型:

  • 空值。
  • 一个非空值,与方法的返回类型相关。
  • 抛出异常,通常是 AuthorizationDeniedException 的实例。这是默认行为。
  • 用于反应应用的 Mono 型。

注意,由于处理程序必须在应用程序上下文中注册为 bean,因此如果需要更复杂的逻辑,可以向它们注入依赖项。除此之外,您还可以使用 MethodInvation 或 MethodInvocationResult,以及 AuthorizationResult 获取与授权决策相关的更多细节。

Deciding What to Return Based on Available Parameters

考虑一个场景,不同的方法可能有多个掩码值,如果我们必须为每个方法创建一个处理程序,那么效率就会降低,尽管这样做完全没问题。在这种情况下,我们可以使用通过参数传递的信息来决定做什么。例如,我们可以创建一个自定义@Mask 注释和一个检测该注释的处理程序,以决定返回什么掩码值:

import org.springframework.core.annotation.AnnotationUtils;

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Mask {

    String value();

}

public class MaskAnnotationDeniedHandler implements MethodAuthorizationDeniedHandler {

    @Override
    public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
        Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class);
        return mask.value();
    }

}

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public MaskAnnotationDeniedHandler maskAnnotationDeniedHandler() {
        return new MaskAnnotationDeniedHandler();
    }

}

@Component
public class MyService {

    @PreAuthorize(value = "hasAuthority('user:read')")
    @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class)
    @Mask("***")
    public String foo() {
        return "foo";
    }

    @PreAuthorize(value = "hasAuthority('user:read')")
    @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class)
    @Mask("???")
    public String bar() {
        return "bar";
    }

}

现在,拒绝访问时的返回值将根据@Mask 注释来决定:

@Autowired
MyService myService;

@Test
void fooWhenDeniedThenReturnStars() {
    String value = this.myService.foo();
    assertThat(value).isEqualTo("***");
}

@Test
void barWhenDeniedThenReturnQuestionMarks() {
    String value = this.myService.foo();
    assertThat(value).isEqualTo("???");
}

Combining with Meta Annotation Support

还可以将@HandleAuthorizationDenied 与其他注释组合起来,以减少和简化方法中的注释。让我们考虑一下上一节中的例子,并将@HandleAuthorizationDenied 与@Mask 合并:

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class)
public @interface Mask {

    String value();

}

@Mask("***")
public String myMethod() {
    // ...
}

现在,当需要在方法中添加掩码行为时,您不必记得添加这两个注释。请务必阅读元注释支持部分了解更多关于用法的详细信息。

Migrating from @EnableGlobalMethodSecurity

如果使用@EnableGlobalMethodSecurity,则应该迁移到@EnableMethodSecurity。

Replace global method security with method security

如果使用@EnableGlobalMethodSecurity,则应该迁移到@EnableMethodSecurity。

Replace global method security with method security

@ EnableGlobalMethodSecurity 和 < global-method-security > 分别不推荐使用@EnableMethodSecurity 和 < method-security > 。默认情况下,新的注释和 XML 元素激活 Spring 的事后注释,并在内部使用 AuthorizationManager。

这意味着以下两个清单在功能上是等价的:

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableMethodSecurity

对于不使用 pre-post注释的应用程序,请确保将其关闭,以避免激活不必要的行为。

例如:

@EnableGlobalMethodSecurity(securedEnabled = true)

应该改为:

@EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)

Use a Custom @Bean instead of subclassing DefaultMethodSecurityExpressionHandler

作为性能优化,MethodSecurityExpressionHandler 引入了一个新方法,该方法采用 Supplier < Authentication > 而不是 Authentication。

这允许 Spring Security 延迟身份验证的查找,并且当您使用@EnableMethodSecurity 而不是@EnableGlobalMethodSecurity 时会自动利用这一点。

但是,假设您的代码扩展了 DefaultMethodSecurityExpressionHandler 并重写了 createSecurityExpressionRoot (Authentication,MethodInvation)以返回自定义的 SecurityExpressionRoot 实例。这将不再起作用,因为@EnableMethodSecurity 设置的安排改为调用 createevalationContext (Supplier < Authentication > ,MethodInvocation)。

令人高兴的是,这种级别的定制通常是不必要的。相反,您可以使用所需的授权方法创建自定义 bean。

或者,假设您想要一个@PostAuthorize (“ hasAuthority (‘ ADMIN’)”)的自定义计算。你可以创建一个像这样的自定义@Bean:

class MyAuthorizer {
	boolean isAdmin(MethodSecurityExpressionOperations root) {
		boolean decision = root.hasAuthority("ADMIN");
		// custom work ...
        return decision;
	}
}

然后在注释中引用如下:

@PreAuthorize("@authz.isAdmin(#root)")
I’d still prefer to subclass DefaultMethodSecurityExpressionHandler我仍然希望继承 DefaultMethodSecurityExpressionHandler 类

如果您必须继续子类化 DefaultMethodSecurityExpressionHandler,您仍然可以这样做:

@Component
class MyExpressionHandler extends DefaultMethodSecurityExpressionHandler {
    @Override
    public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) {
		StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi);
        MethodSecurityExpressionOperations delegate = (MethodSecurityExpressionOperations) context.getRootObject().getValue();
        MySecurityExpressionRoot root = new MySecurityExpressionRoot(delegate);
        context.setRootObject(root);
        return context;
    }
}

Further Reading

现在您已经保护了应用程序的请求,如果您还没有保护它的请求,请保护它。您还可以进一步阅读关于测试应用程序或将 Spring Security 与应用程序的其他方面(如数据层或跟踪和度量)集成的内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1909867.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RK3588开发笔记(四):基于定制的RK3588一体主板升级镜像

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/140288662 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

多次执行相同的push问题(如何解决)

下面这个问题如何解决 1.为什么会出现这个问题 原因&#xff1a;push是一个promise&#xff0c;promise需要传递成功和失败两个参数&#xff0c;我们的push中没有传递。 goSearch() {//路由传参//第一种&#xff1a;字符串形式// this.$router.push(/search/this.keyword&quo…

【Linux进阶】文件系统3——目录树,挂载

前言 在Windows 系统重新安装之前&#xff0c;你可能会事先考虑&#xff0c;到底系统盘C盘要有多大容量&#xff1f;而数据盘D盘又要给多大容量等&#xff0c;然后实际安装的时候&#xff0c;你会发现其实C盘之前会有个100MB的分区被独立出来&#xff0c;所以实际上你就会有三个…

ATA-5420前置微小信号放大器如何进行半导体测试

半导体测试是电子行业中至关重要的环节&#xff0c;它对于保证产品质量、提高生产效率起着至关重要的作用。在半导体测试过程中&#xff0c;我们需要采用一系列的方法和原理来确保芯片的可靠性和性能稳定性&#xff0c;而前置微小信号放大器在半导体测试中起着至关重要的作用。…

C++ Qt 自制开源科学计算器

C Qt 自制开源科学计算器 项目地址 软件下载地址 目录 0. 效果预览1. 数据库准备2. 按键&快捷键说明3. 颜色切换功能(初版)4. 未来开发展望5. 联系邮箱 0. 效果预览 普通计算模式效果如下&#xff1a; 科学计算模式效果如下&#xff1a; 更具体的功能演示视频见如下链接…

Python酷库之旅-第三方库Pandas(012)

目录 一、用法精讲 28、pandas.HDFStore.keys函数 28-1、语法 28-2、参数 28-3、功能 28-4、返回值 28-5、说明 28-6、用法 28-6-1、数据准备 28-6-2、代码示例 28-6-3、结果输出 29、pandas.HDFStore.groups函数 29-1、语法 29-2、参数 29-3、功能 29-4、返回…

9.2 栅格图层符号化单波段灰度渲染

文章目录 前言单波段灰度QGis设置为单波段灰度二次开发代码实现单波段灰度 总结 前言 介绍栅格图层数据渲染之单波段灰度显示说明&#xff1a;文章中的示例代码均来自开源项目qgis_cpp_api_apps 单波段灰度 以“3420C_2010_327_RGB_LATLNG.tif”数据为例&#xff0c;在QGis中…

论坛系统--测试报告(部分)

前言 逆水行舟&#xff0c;不进则退&#xff01;&#xff01;&#xff01; 目录 项目背景 接口测试 性能测试 压力测试 UI测试 项目背景 项目名称&#xff1a; 论坛系统 项目概述&#xff1a; 论坛系统是一个基于Spring Boot和MySQL的Web应用程序…

Nginx理论篇与相关网络协议

Nginx是什么&#xff1f; Nginx是一款由C语言编写的高性能、轻量级的web服务器&#xff0c;一个线程能处理多个请求&#xff0c;支持万级并发。 优势&#xff1a;I/O多路复用。 I/O是什么&#xff1f; I指的是输入&#xff08;Input&#xff09;,O是指输出&#xff08;Outp…

poi-tl、aspose实现word中表在每页携带表头表尾

实现word中表在每页携带表头表尾&#xff08;第一版&#xff09; word中的表格如果只有一页时表头表尾都很好处理&#xff0c;当中间内容足够多时&#xff0c;表尾只会出现在最后一页&#xff0c;表头也只会出现在第一页&#xff0c;之前想过用word自带的页眉页尾来处理但是&a…

【中项第三版】系统集成项目管理工程师 | 第 4 章 信息系统架构③ | 4.6

前言 第4章对应的内容选择题和案例分析都会进行考查&#xff0c;这一章节属于技术相关的内容&#xff0c;学习要以教材为准。本章分值预计在4-5分。 目录 4.6 网络架构 4.6.1 基本原则 4.6.2 局域网架构 4.6.3 广域网架构 4.6.4 移动通信网架构 4.6.5 软件定义网络 4.6…

云动态摘要 2024-07-09

给您带来云厂商的最新动态&#xff0c;最新产品资讯和最新优惠更新。 最新优惠与活动 数据库上云优选 阿里云 2024-07-04 RDS、PolarDB、Redis、MongoDB 全系产品新用户低至首年6折起&#xff01; [免费体验]智能助手ChatBI上线 腾讯云 2024-07-02 基于混元大模型打造&…

【面试】高频面试点:从源码角度一篇文章带你搞懂128陷阱!

要理解什么是“128陷阱”&#xff0c;首先来看一段代码&#xff1a; public static void main(String... strings) {Integer integer1 3;Integer integer2 3;if (integer1 integer2)System.out.println("integer1 integer2");elseSystem.out.println("inte…

07-7.3.2 平衡二叉树(AVL)

&#x1f44b; Hi, I’m Beast Cheng &#x1f440; I’m interested in photography, hiking, landscape… &#x1f331; I’m currently learning python, javascript, kotlin… &#x1f4eb; How to reach me --> 458290771qq.com 喜欢《数据结构》部分笔记的小伙伴可以…

【Linux】:服务器用户的登陆、删除、密码修改

用Xshell登录云服务器。 1.登录云服务器 先打开Xshell。弹出的界面点。 在终端上输入命令ssh usernameip_address&#xff0c;其中username为要登录的用户名&#xff0c;ip_address为Linux系统的IP地址或主机名。 然后输入密码进行登录。 具体如下&#xff1a; 找到新建会话…

提高项目效率必备:探索2024年10大最佳需求管理系统

本文将分享2024年10款高效需求管理工具&#xff1a;PingCode、Worktile、Tapd、禅道、Teambition、ClickUp、Tower、Asana、Jira 和 monday.com。 在快速变化的软件开发环境中&#xff0c;选择合适的需求管理工具变得至关重要。项目失败往往源于需求不明确或管理不善&#xff0…

linux权限深度解析——探索原理

前言&#xff1a;本节内容主要讲述的是linux权限相关的内容&#xff0c; linux的权限如果使用root账号是感受不到的&#xff0c; 所以我们要使用普通账号对本节相关内容进行学习&#xff0c;以及一些实验的测试。 然后&#xff0c; 通过linux权限的学习我们可以知道为什么有时候…

记一次 .NET某酒业业务系统 崩溃分析

一&#xff1a;背景 1. 讲故事 前些天有位朋友找到我&#xff0c;说他的程序每次关闭时就会自动崩溃&#xff0c;一直找不到原因让我帮忙看一下怎么回事&#xff0c;这位朋友应该是第二次找我了&#xff0c;分析了下 dump 还是挺经典的&#xff0c;拿出来给大家分享一下吧。 …

成都欣丰洪泰文化传媒有限公司电商服务领航者

在当今数字化浪潮中&#xff0c;电商行业正以前所未有的速度蓬勃发展。作为这片蓝海中的佼佼者&#xff0c;成都欣丰洪泰文化传媒有限公司凭借其专业的电商服务能力和对市场的敏锐洞察力&#xff0c;成为众多品牌信赖的合作伙伴。今天&#xff0c;就让我们一起走进成都欣丰洪泰…

大屏自适应容器组件 v-scale-screen

在vue中&#xff0c;v-scale-screen可用于大屏项目开发&#xff0c;实现屏幕自适应&#xff0c;可根据宽度自适应&#xff0c;高度自适应&#xff0c;和宽高等比例自适应&#xff0c;全屏自适应。 仓库地址&#xff1a;github国内地址&#xff1a;gitee 一、安装 npm instal…