对于最新的稳定版本,请使用 Spring Security 6.5.0spring-doc.cadn.net.cn

授权迁移

以下步骤与有关执行授权方式的更改有关。spring-doc.cadn.net.cn

AuthorizationManager针对 Method Security

如果您在进行这些更改时遇到问题,请注意@EnableGlobalMethodSecurity,虽然已弃用,但在 6.0 中不会删除,允许您通过坚持使用旧注释来选择退出。spring-doc.cadn.net.cn

全局方法安全性替换为方法安全性

@EnableGlobalMethodSecurity<global-method-security>已弃用,取而代之的是@EnableMethodSecurity<method-security>分别。 新的 annotation 和 XML 元素默认激活 Spring 的 pre-post 注释,并使用AuthorizationManager内部。spring-doc.cadn.net.cn

这意味着以下两个清单在功能上是等效的:spring-doc.cadn.net.cn

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
<global-method-security pre-post-enabled="true"/>
@EnableMethodSecurity
@EnableMethodSecurity
<method-security/>

对于不使用 pre-post 注释的应用程序,请确保将其关闭以避免激活不需要的行为。spring-doc.cadn.net.cn

例如,像这样的列表:spring-doc.cadn.net.cn

@EnableGlobalMethodSecurity(securedEnabled = true)
@EnableGlobalMethodSecurity(securedEnabled = true)
<global-method-security secured-enabled="true"/>

应更改为:spring-doc.cadn.net.cn

@EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
<method-security secured-enabled="true" pre-post-enabled="false"/>

更改order@EnableTransactionManagement

@EnableTransactionManagement@EnableGlobalMethodSecurity具有相同的orderInteger.MAX_VALUE. 这意味着它们在 Spring AOP Advisor 链中相对于彼此的顺序是不确定的。spring-doc.cadn.net.cn

这通常很好,因为大多数方法安全表达式不需要打开的事务即可正常运行;然而,从历史上看,有时有必要通过设置它们的order值。spring-doc.cadn.net.cn

@EnableMethodSecurity没有order值,因为它会发布多个拦截器。 事实上,它不能尝试向后兼容@EnableTransactionManagement因为它无法将所有拦截器设置为位于同一 advisor 链位置。spring-doc.cadn.net.cn

相反,@EnableMethodSecurity拦截器基于 0 的偏移量。 这@PreFilterInterceptor 的订单为 100;@PostAuthorize, 200;等等。spring-doc.cadn.net.cn

因此,如果在更新后发现由于没有打开的事务而导致方法安全表达式不起作用,请从以下内容更改事务注释定义:spring-doc.cadn.net.cn

@EnableTransactionManagement
@EnableTransactionManagement
<tx:annotation-driven ref="txManager"/>
@EnableTransactionManagement(order = 0)
@EnableTransactionManagement(order = 0)
<tx:annotation-driven ref="txManager" order="0"/>

这样,事务 AOP 建议将放在 Spring Security 的建议之前,并且在评估您的授权 SPEL 表达式时,事务将打开。spring-doc.cadn.net.cn

使用自定义@Bean而不是子类化DefaultMethodSecurityExpressionHandler

作为性能优化,引入了一种新方法MethodSecurityExpressionHandler这需要Supplier<Authentication>而不是Authentication.spring-doc.cadn.net.cn

这允许 Spring Security 延迟对Authentication,并在您使用@EnableMethodSecurity而不是@EnableGlobalMethodSecurity.spring-doc.cadn.net.cn

但是,假设您的代码扩展了DefaultMethodSecurityExpressionHandler和覆盖createSecurityExpressionRoot(Authentication, MethodInvocation)返回自定义SecurityExpressionRoot实例。 这将不再有效,因为@EnableMethodSecurity设置呼叫createEvaluationContext(Supplier<Authentication>, MethodInvocation)相反。spring-doc.cadn.net.cn

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

例如,假设您希望对@PostAuthorize("hasAuthority('ADMIN')"). 您可以创建自定义@Bean像这个:spring-doc.cadn.net.cn

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

然后在 Comments 中引用它,如下所示:spring-doc.cadn.net.cn

@PreAuthorize("@authz.isAdmin(#root)")
@PreAuthorize("@authz.isAdmin(#root)")

我还是更喜欢子类DefaultMethodSecurityExpressionHandler

如果必须继续子类化DefaultMethodSecurityExpressionHandler,您仍然可以这样做。 相反,请覆盖createEvaluationContext(Supplier<Authentication>, MethodInvocation)方法如下:spring-doc.cadn.net.cn

@Component
class MyExpressionHandler extends DefaultMethodSecurityExpressionHandler {
    @Override
    public EvaluationContext createEvaluationContext(
            Supplier<Authentication> authentication, MethodInvocation mi) {
		StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi);
        MySecurityExpressionRoot root = new MySecurityExpressionRoot(authentication, invocation);
	    root.setPermissionEvaluator(getPermissionEvaluator());
	    root.setTrustResolver(new AuthenticationTrustResolverImpl());
        root.setRoleHierarchy(getRoleHierarchy());
        context.setRootObject(root);
        return context;
    }
}
@Component
class MyExpressionHandler: DefaultMethodSecurityExpressionHandler {
    override fun createEvaluationContext(val authentication: Supplier<Authentication>,
        val mi: MethodInvocation): EvaluationContext {
		val context = super.createEvaluationContext(authentication, mi) as StandardEvaluationContext;
        val root = new MySecurityExpressionRoot(authentication, invocation);
	    root.setPermissionEvaluator(getPermissionEvaluator());
	    root.setTrustResolver(new AuthenticationTrustResolverImpl());
        root.setRoleHierarchy(getRoleHierarchy());
        context.setRootObject(root);
        return context;
    }
}

选择退出步骤

如果您需要选择退出这些更改,可以使用@EnableGlobalMethodSecurity而不是@EnableMethodSecurityspring-doc.cadn.net.cn

发布MethodSecurityExpressionHandler而不是PermissionEvaluator

@EnableMethodSecurity不会拾取PermissionEvaluator. 这有助于保持其 API 简单。spring-doc.cadn.net.cn

如果您有一个自定义的PermissionEvaluator @Bean,请将其从:spring-doc.cadn.net.cn

@Bean
static PermissionEvaluator permissionEvaluator() {
	// ... your evaluator
}
companion object {
	@Bean
	fun permissionEvaluator(): PermissionEvaluator {
		// ... your evaluator
	}
}
@Bean
static MethodSecurityExpressionHandler expressionHandler() {
	var expressionHandler = new DefaultMethodSecurityExpressionHandler();
	expressionHandler.setPermissionEvaluator(myPermissionEvaluator);
	return expressionHandler;
}
companion object {
	@Bean
	fun expressionHandler(): MethodSecurityExpressionHandler {
		val expressionHandler = DefaultMethodSecurityExpressionHandler
		expressionHandler.setPermissionEvaluator(myPermissionEvaluator)
		return expressionHandler
	}
}

替换任何自定义方法 - 安全性AccessDecisionManagers

您的应用程序可能具有自定义的AccessDecisionManagerAccessDecisionVoter安排。 准备策略将取决于您每次安排的原因。 请继续阅读以找到最适合您情况的方案。spring-doc.cadn.net.cn

我使用UnanimousBased

如果您的应用程序使用UnanimousBased对于默认投票者,您可能不需要执行任何作,因为基于 Unanimous 是默认行为@EnableMethodSecurity.spring-doc.cadn.net.cn

但是,如果您发现无法接受默认授权管理器,则可以使用AuthorizationManagers.allOf来编写您自己的编曲。spring-doc.cadn.net.cn

请注意,与allOf,即如果所有代表都弃权,则授予授权。 如果在所有代表都弃权时必须拒绝授权,请实施一个组合AuthorizationManager,它采用 delegateAuthorizationManagers 的 S 中。spring-doc.cadn.net.cn

完成此作后,请按照参考手册中的详细信息进行作添加自定义AuthorizationManager.spring-doc.cadn.net.cn

我使用AffirmativeBased

如果您的应用程序使用AffirmativeBased,则可以构造一个等效的AuthorizationManager这样:spring-doc.cadn.net.cn

AuthorizationManager<MethodInvocation> authorization = AuthorizationManagers.anyOf(
		// ... your list of authorization managers
)
val authorization = AuthorizationManagers.anyOf(
		// ... your list of authorization managers
)

实施AuthorizationManager,请按照参考手册中的详细信息进行作添加自定义AuthorizationManager.spring-doc.cadn.net.cn

我使用ConsensusBased

没有框架提供的等效ConsensusBased. 在这种情况下,请实现一个复合AuthorizationManager,它采用 delegateAuthorizationManagers 的 S 中。spring-doc.cadn.net.cn

实施AuthorizationManager,请按照参考手册中的详细信息进行作添加自定义AuthorizationManager.spring-doc.cadn.net.cn

我使用自定义AccessDecisionVoter

您应该更改要实现的类AuthorizationManager或创建适配器。spring-doc.cadn.net.cn

如果不知道自定义选民在做什么,就不可能推荐通用解决方案。 不过,通过示例,以下是 adaptingSecurityMetadataSourceAccessDecisionVoter@PreAuthorize将如下所示:spring-doc.cadn.net.cn

public final class PreAuthorizeAuthorizationManagerAdapter implements AuthorizationManager<MethodInvocation> {
    private final SecurityMetadataSource metadata;
    private final AccessDecisionVoter voter;

    public PreAuthorizeAuthorizationManagerAdapter(MethodSecurityExpressionHandler expressionHandler) {
        ExpressionBasedAnnotationAttributeFactory attributeFactory =
                new ExpressionBasedAnnotationAttributeFactory(expressionHandler);
        this.metadata = new PrePostAnnotationSecurityMetadataSource(attributeFactory);
        ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
        expressionAdvice.setExpressionHandler(expressionHandler);
        this.voter = new PreInvocationAuthorizationAdviceVoter(expressionAdvice);
    }

    public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
        List<ConfigAttribute> attributes = this.metadata.getAttributes(invocation, AopUtils.getTargetClass(invocation.getThis()));
        int decision = this.voter.vote(authentication.get(), invocation, attributes);
        if (decision == ACCESS_GRANTED) {
            return new AuthorizationDecision(true);
        }
        if (decision == ACCESS_DENIED) {
            return new AuthorizationDecision(false);
        }
        return null; // abstain
    }
}

实施AuthorizationManager,请按照参考手册中的详细信息进行作添加自定义AuthorizationManager.spring-doc.cadn.net.cn

我使用AfterInvocationManagerAfterInvocationProvider

AfterInvocationManagerAfterInvocationProvider对调用的结果做出授权决定。 例如,在方法调用的情况下,这些命令会做出有关方法返回值的授权决策。spring-doc.cadn.net.cn

在 Spring Security 3.0 中,授权决策被标准化为@PostAuthorize@PostFilter附注.@PostAuthorize用于确定是否允许返回整个返回值。@PostFilter用于从返回的集合、数组或流中筛选单个条目。spring-doc.cadn.net.cn

这两个 Comments 应该可以满足大多数需求,我们鼓励您迁移到其中一个或两个AfterInvocationProviderAfterInvocationManager现已弃用。spring-doc.cadn.net.cn

如果您已实现自己的AfterInvocationManagerAfterInvocationProvider,您应该首先问问自己它试图做什么。 如果它尝试授权返回类型,则考虑实施AuthorizationManager<MethodInvocationResult>并使用AfterMethodAuthorizationManagerInterceptor.或者发布自定义 Bean 并使用@PostAuthorize("@myBean.authorize(#root)").spring-doc.cadn.net.cn

如果它尝试过滤,则考虑发布自定义 Bean 并使用@PostFilter("@mybean.authorize(#root)"). 或者,如果需要,您可以实施自己的MethodInterceptor,看看PostFilterAuthorizationMethodInterceptorPrePostMethodSecurityConfiguration例如。spring-doc.cadn.net.cn

我使用RunAsManager

目前有无替代品RunAsManager尽管正在考虑中。spring-doc.cadn.net.cn

调整RunAsManager但是,将AuthorizationManagerAPI(如果需要)。spring-doc.cadn.net.cn

以下是一些帮助您入门的伪代码:spring-doc.cadn.net.cn

public final class RunAsAuthorizationManagerAdapter<T> implements AuthorizationManager<T> {
	private final RunAsManager runAs = new RunAsManagerImpl();
	private final SecurityMetadataSource metadata;
    private final AuthorizationManager<T> authorization;

    // ... constructor

    public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
		Supplier<Authentication> wrapped = (auth) -> {
			List<ConfigAttribute> attributes = this.metadata.getAttributes(object);
			return this.runAs.buildRunAs(auth, object, attributes);
		};
		return this.authorization.check(wrapped, object);
    }
}

实施AuthorizationManager,请按照参考手册中的详细信息进行作添加自定义AuthorizationManager.spring-doc.cadn.net.cn

检查AnnotationConfigurationExceptions

@EnableMethodSecurity<method-security>激活 Spring Security 的不可重复或其他不兼容的 Comments 的更严格执行。 如果在移动到任一之后,您会看到AnnotationConfigurationException,请按照异常消息中的说明清理应用程序的方法安全注释使用情况。spring-doc.cadn.net.cn

AuthorizationManager针对 Message Security

如果您在进行这些更改时遇到问题,可以按照本节末尾的选择退出步骤进行作。spring-doc.cadn.net.cn

确保所有消息都已定义授权规则

默认情况下,现已弃用的消息安全支持允许所有消息。新的支持具有更强的默认值,即拒绝所有消息。spring-doc.cadn.net.cn

为此,请确保为每个请求声明授权规则存在。spring-doc.cadn.net.cn

例如,应用程序配置如下:spring-doc.cadn.net.cn

@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
    messages
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN");
}
override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
    messages
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
}
<websocket-message-broker>
    <intercept-message pattern="/user/queue/errors" access="permitAll"/>
    <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
</websocket-message-broker>

应更改为:spring-doc.cadn.net.cn

@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
    messages
        .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        .anyMessage().denyAll();
}
override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
    messages
        .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        .anyMessage().denyAll()
}
<websocket-message-broker>
    <intercept-message type="CONNECT" access="permitAll"/>
    <intercept-message type="DISCONNECT" access="permitAll"/>
    <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
    <intercept-message pattern="/user/queue/errors" access="permitAll"/>
    <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
    <intercept-message pattern="/**" access="denyAll"/>
</websocket-message-broker>

@EnableWebSocketSecurity

如果您想禁用 CSRF 并且您使用的是 Java 配置,则迁移步骤略有不同。 而不是使用@EnableWebSocketSecurity中,您将覆盖WebSocketMessageBrokerConfigurer你自己。 有关此步骤的详细信息,请参阅参考手册spring-doc.cadn.net.cn

如果您使用的是 Java 配置,请添加@EnableWebSocketSecurity添加到您的应用程序中。spring-doc.cadn.net.cn

例如,您可以将其添加到 websocket 安全配置类中,如下所示:spring-doc.cadn.net.cn

@EnableWebSocketSecurity
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
	// ...
}
@EnableWebSocketSecurity
@Configuration
class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
	// ...
}

这将使MessageMatcherDelegatingAuthorizationManager.Builder可用于鼓励通过组合而不是扩展进行配置。spring-doc.cadn.net.cn

使用AuthorizationManager<Message<?>>实例

开始使用AuthorizationManager中,您可以设置use-authorization-manager属性,或者您可以发布AuthorizationManager<Message<?>> @Bean在 Java 中。spring-doc.cadn.net.cn

例如,以下应用程序配置:spring-doc.cadn.net.cn

@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
    messages
        .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        .anyMessage().denyAll();
}
override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
    messages
        .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        .anyMessage().denyAll()
}
<websocket-message-broker>
    <intercept-message type="CONNECT" access="permitAll"/>
    <intercept-message type="DISCONNECT" access="permitAll"/>
    <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
    <intercept-message pattern="/user/queue/errors" access="permitAll"/>
    <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
    <intercept-message pattern="/**" access="denyAll"/>
</websocket-message-broker>
@Bean
AuthorizationManager<Message<?>> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
    messages
        .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        .anyMessage().denyAll();
	return messages.build();
}
@Bean
fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?>> {
    messages
        .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        .anyMessage().denyAll()
    return messages.build()
}
<websocket-message-broker use-authorization-manager="true">
    <intercept-message type="CONNECT" access="permitAll"/>
    <intercept-message type="DISCONNECT" access="permitAll"/>
    <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
    <intercept-message pattern="/user/queue/errors" access="permitAll"/>
    <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
    <intercept-message pattern="/**" access="denyAll"/>
</websocket-message-broker>

停止实施AbstractSecurityWebSocketMessageBrokerConfigurer

如果您使用的是 Java 配置,则现在只需扩展WebSocketMessageBrokerConfigurer.spring-doc.cadn.net.cn

例如,如果扩展了AbstractSecurityWebSocketMessageBrokerConfigurer称为WebSocketSecurityConfig然后:spring-doc.cadn.net.cn

@EnableWebSocketSecurity
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
	// ...
}
@EnableWebSocketSecurity
@Configuration
class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
	// ...
}
@EnableWebSocketSecurity
@Configuration
public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
	// ...
}
@EnableWebSocketSecurity
@Configuration
class WebSocketSecurityConfig: WebSocketMessageBrokerConfigurer {
	// ...
}

选择退出步骤

如果您遇到问题,请查看以下方案,以实现最佳选择退出行为:spring-doc.cadn.net.cn

我无法为所有请求声明授权规则

如果您在设置anyRequest授权规则denyAll,请使用permitAll相反,像这样:spring-doc.cadn.net.cn

@Bean
AuthorizationManager<Message<?>> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
    messages
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        // ...
        .anyMessage().permitAll();
	return messages.build();
}
@Bean
fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?>> {
    messages
        .simpDestMatchers("/user/queue/errors").permitAll()
        .simpDestMatchers("/admin/**").hasRole("ADMIN")
        // ...
        .anyMessage().permitAll();
    return messages.build()
}
<websocket-message-broker use-authorization-manager="true">
    <intercept-message pattern="/user/queue/errors" access="permitAll"/>
    <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
    <!-- ... -->
    <intercept-message pattern="/**" access="permitAll"/>
</websocket-message-broker>

我无法让 CSRF 工作,需要其他一些AbstractSecurityWebSocketMessageBrokerConfigurer功能,或者我在使用AuthorizationManager

对于 Java,您可以继续使用AbstractMessageSecurityWebSocketMessageBrokerConfigurer. 即使它已被弃用,也不会在 6.0 中删除。spring-doc.cadn.net.cn

对于 XML,您可以选择退出AuthorizationManager通过设置use-authorization-manager="false":spring-doc.cadn.net.cn

XML 格式
<websocket-message-broker>
    <intercept-message pattern="/user/queue/errors" access="permitAll"/>
    <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
</websocket-message-broker>
XML 格式
<websocket-message-broker use-authorization-manager="false">
    <intercept-message pattern="/user/queue/errors" access="permitAll"/>
    <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
</websocket-message-broker>

AuthorizationManager针对 请求安全性

如果您在进行这些更改时遇到问题,可以按照本节末尾的选择退出步骤进行作。spring-doc.cadn.net.cn

确保所有请求都已定义授权规则

在 Spring Security 5.8 及更早版本中,默认情况下允许没有授权规则的请求。 默认情况下拒绝是一个更强的安全立场,因此需要为每个端点明确定义授权规则。 因此,在 6.0 中, Spring Security 默认拒绝任何缺少授权规则的请求。spring-doc.cadn.net.cn

为这一变化做准备的最简单方法是引入适当的anyRequestrule 作为最后一个授权规则。 建议是denyAll因为这是隐含的 6.0 默认值。spring-doc.cadn.net.cn

您可能已经有一个anyRequest规则定义,在这种情况下,可以跳过此步骤。spring-doc.cadn.net.cn

添加denyAll到最后看起来像是变化:spring-doc.cadn.net.cn

http
    .authorizeRequests((authorize) -> authorize
        .filterSecurityInterceptorOncePerRequest(true)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
    )
    // ...
http {
    authorizeRequests {
        filterSecurityInterceptorOncePerRequest = true
        authorize("/app/**", hasRole("APP"))
        // ...
    }
}
<http once-per-request="true">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
</http>
http
    .authorizeRequests((authorize) -> authorize
        .filterSecurityInterceptorOncePerRequest(true)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeRequests {
        filterSecurityInterceptorOncePerRequest = true
        authorize("/app/**", hasRole("APP"))
        // ...
        authorize(anyRequest, denyAll)
    }
}
<http once-per-request="true">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
    <intercept-url pattern="/**" access="denyAll"/>
</http>

如果您已经迁移到authorizeHttpRequests,建议的更改是相同的。spring-doc.cadn.net.cn

切换到AuthorizationManager

要选择使用AuthorizationManager,您可以使用authorizeHttpRequestsuse-authorization-manager分别用于 Java 或 XML。spring-doc.cadn.net.cn

http
    .authorizeRequests((authorize) -> authorize
        .filterSecurityInterceptorOncePerRequest(true)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeRequests {
        filterSecurityInterceptorOncePerRequest = true
        authorize("/app/**", hasRole("APP"))
        // ...
        authorize(anyRequest, denyAll)
    }
}
<http once-per-request="true">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
    <intercept-url pattern="/**" access="denyAll"/>
</http>
http
    .authorizeHttpRequests((authorize) -> authorize
        .shouldFilterAllDispatcherTypes(false)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeHttpRequests {
        shouldFilterAllDispatcherTypes = false
        authorize("/app/**", hasRole("APP"))
        // ...
        authorize(anyRequest, denyAll)
    }
}
<http filter-all-dispatcher-types="false" use-authorization-manager="true">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
    <intercept-url pattern="/**" access="denyAll"/>
</http>

迁移hasIpAddressaccess(AuthorizationManager)

hasIpAddressauthorizeHttpRequests.spring-doc.cadn.net.cn

因此,您需要将任何调用更改为hasIpAddress更改为使用AuthorizationManager.spring-doc.cadn.net.cn

首先,构造一个IpAddressMatcher这样:spring-doc.cadn.net.cn

Java
IpAddressMatcher hasIpAddress = new IpAddressMatcher("127.0.0.1");

然后从这里改变:spring-doc.cadn.net.cn

Java
http
    .authorizeRequests((authorize) -> authorize
        .mvcMatchers("/app/**").hasIpAddress("127.0.0.1")
        // ...
        .anyRequest().denyAll()
    )
    // ...
Java
http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/app/**").access((authentication, context) ->
            new AuthorizationDecision(hasIpAddress.matches(context.getRequest()))
        // ...
        .anyRequest().denyAll()
    )
    // ...
通过 IP 地址进行保护一开始就非常脆弱。 因此,没有计划将此支持移植到authorizeHttpRequests.

将 SPEL 表达式迁移到AuthorizationManager

对于授权规则,Java 往往比 SPEL 更容易测试和维护。 因此,authorizeHttpRequests没有用于声明String斯佩尔。spring-doc.cadn.net.cn

相反,您可以实施自己的AuthorizationManagerimplementation 或使用WebExpressionAuthorizationManager.spring-doc.cadn.net.cn

为了完整起见,将演示这两个选项。spring-doc.cadn.net.cn

首先,如果您有以下 SPEL:spring-doc.cadn.net.cn

http
    .authorizeRequests((authorize) -> authorize
        .filterSecurityInterceptorOncePerRequest(true)
        .mvcMatchers("/complicated/**").access("hasRole('ADMIN') || hasAuthority('SCOPE_read')")
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeRequests {
        filterSecurityInterceptorOncePerRequest = true
        authorize("/complicated/**", access("hasRole('ADMIN') || hasAuthority('SCOPE_read')"))
        // ...
        authorize(anyRequest, denyAll)
    }
}

然后你可以编写你自己的AuthorizationManager使用 Spring Security 授权原语,如下所示:spring-doc.cadn.net.cn

http
    .authorizeHttpRequests((authorize) -> authorize
        .shouldFilterAllDispatcherTypes(false)
        .mvcMatchers("/complicated/**").access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read"))
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeHttpRequests {
        shouldFilterAllDispatcherTypes = false
        authorize("/complicated/**", access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read"))
        // ...
        authorize(anyRequest, denyAll)
    }
}

或者您可以使用WebExpressionAuthorizationManager采用以下方式:spring-doc.cadn.net.cn

http
    .authorizeRequests((authorize) -> authorize
        .filterSecurityInterceptorOncePerRequest(true)
        .mvcMatchers("/complicated/**").access(
			new WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')")
        )
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeRequests {
        filterSecurityInterceptorOncePerRequest = true
        authorize("/complicated/**", access(
            WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')"))
        )
        // ...
        authorize(anyRequest, denyAll)
    }
}

切换以筛选所有 Dispatcher 类型

Spring Security 5.8 及更早版本仅对每个请求执行一次授权。 这意味着 Dispatcher 类型(如FORWARDINCLUDE那次之后的运行REQUEST默认情况下不安全。spring-doc.cadn.net.cn

建议 Spring Security 保护所有分派类型。 因此,在 6.0 中, Spring Security 更改了此默认值。spring-doc.cadn.net.cn

因此,最后,更改您的授权规则以筛选所有 Dispatcher 类型。spring-doc.cadn.net.cn

为此,您应该更改:spring-doc.cadn.net.cn

http
    .authorizeHttpRequests((authorize) -> authorize
        .shouldFilterAllDispatcherTypes(false)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeHttpRequests {
        shouldFilterAllDispatcherTypes = false
        authorize("/app/**", hasRole("APP"))
        // ...
        authorize(anyRequest, denyAll)
    }
}
<http filter-all-dispatcher-types="false" use-authorization-manager="true">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
    <intercept-url pattern="/**" access="denyAll"/>
</http>
http
    .authorizeHttpRequests((authorize) -> authorize
        .shouldFilterAllDispatcherTypes(true)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeHttpRequests {
        shouldFilterAllDispatcherTypes = true
        authorize("/app/**", hasRole("APP"))
        // ...
        authorize(anyRequest, denyAll)
    }
}
<http filter-all-dispatcher-types="true" use-authorization-manager="true">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
    <intercept-url pattern="/**" access="denyAll"/>
</http>

而且,FilterChainProxy也应为所有 Dispatcher 类型注册。 如果您使用的是 Spring Boot,您必须更改spring.security.filter.dispatcher-types属性要包含所有 Dispatcher 类型,请执行以下作:spring-doc.cadn.net.cn

spring.security.filter.dispatcher-types=request,async,error,forward,include

如果你是使用AbstractSecurityWebApplicationInitializer您应该覆盖getSecurityDispatcherTypes方法并返回所有 Dispatcher 类型:spring-doc.cadn.net.cn

import org.springframework.security.web.context.*;

public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

    @Override
    protected EnumSet<DispatcherType> getSecurityDispatcherTypes() {
        return EnumSet.of(DispatcherType.REQUEST, DispatcherType.ERROR, DispatcherType.ASYNC,
                DispatcherType.FORWARD, DispatcherType.INCLUDE);
    }

}

许可证FORWARD使用 Spring MVC 时

如果使用 Spring MVC 解析视图名称,则需要允许FORWARD请求。 这是因为当 Spring MVC 检测到视图名称和实际视图之间的 Map 时,它将执行对视图的转发。 正如我们在上一节中看到的,Spring Security 6.0 将对FORWARD请求。spring-doc.cadn.net.cn

请考虑以下常见配置:spring-doc.cadn.net.cn

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .shouldFilterAllDispatcherTypes(true)
            .requestMatchers("/").authenticated()
            .anyRequest().denyAll()
        )
        .formLogin((form) -> form
            .loginPage("/login")
            .permitAll()
        ));
    return http.build();
}

以及以下等效的 MVC 视图映射配置之一:spring-doc.cadn.net.cn

@Controller
public class MyController {

    @GetMapping("/login")
    public String login() {
        return "login";
    }

}
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
    }

}

无论使用哪种配置,当有对/login,Spring MVC 将执行 forward 到 viewlogin,在默认配置下,位于src/main/resources/templates/login.html路径。 安全配置允许对/login但所有其他请求都将被拒绝,包括FORWARD请求到/templates/login.html.spring-doc.cadn.net.cn

要解决此问题,您应该将 Spring Security 配置为允许FORWARD请求:spring-doc.cadn.net.cn

http
    .authorizeHttpRequests((authorize) -> authorize
        .shouldFilterAllDispatcherTypes(true)
        .dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeHttpRequests {
        shouldFilterAllDispatcherTypes = true
        authorize(DispatcherTypeRequestMatcher(DispatcherType.FORWARD), permitAll)
        authorize(anyRequest, denyAll)
    }
}
<http filter-all-dispatcher-types="true" use-authorization-manager="true">
    <intercept-url request-matcher-ref="forwardRequestMatcher" access="permitAll()" />
    <!-- ... -->
    <intercept-url pattern="/**" access="denyAll"/>
</http>

<bean name="forwardRequestMatcher" class="org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher">
    <constructor-arg value="FORWARD"/>
</bean>

替换任何自定义 filter-securityAccessDecisionManagers

您的应用程序可能具有自定义的AccessDecisionManagerAccessDecisionVoter安排。 准备策略将取决于您每次安排的原因。 请继续阅读以找到最适合您情况的方案。spring-doc.cadn.net.cn

我使用UnanimousBased

如果您的应用程序使用UnanimousBased,您应该首先调整或替换任何AccessDecisionVoters 的 S 中,然后您可以构造一个AuthorizationManager这样:spring-doc.cadn.net.cn

@Bean
AuthorizationManager<RequestAuthorizationContext> requestAuthorization() {
    PolicyAuthorizationManager policy = ...;
    LocalAuthorizationManager local = ...;
    return AuthorizationManagers.allOf(policy, local);
}
@Bean
fun requestAuthorization(): AuthorizationManager<RequestAuthorizationContext> {
    val policy: PolicyAuthorizationManager = ...
    val local: LocalAuthorizationManager = ...
    return AuthorizationManagers.allOf(policy, local)
}
<bean id="requestAuthorization" class="org.springframework.security.authorization.AuthorizationManagers"
        factory-method="allOf">
    <constructor-arg>
        <util:list>
            <bean class="my.PolicyAuthorizationManager"/>
            <bean class="my.LocalAuthorizationManager"/>
        </util:list>
    </constructor-arg>
</bean>

然后,将其连接到 DSL 中,如下所示:spring-doc.cadn.net.cn

http
    .authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization))
    // ...
http {
    authorizeHttpRequests {
        authorize(anyRequest, requestAuthorization)
    }
    // ...
}
<http authorization-manager-ref="requestAuthorization"/>

authorizeHttpRequests旨在使您可以应用自定义AuthorizationManager添加到任何 URL 模式。 有关更多详细信息,请参阅参考spring-doc.cadn.net.cn

我使用AffirmativeBased

如果您的应用程序使用AffirmativeBased,则可以构造一个等效的AuthorizationManager这样:spring-doc.cadn.net.cn

@Bean
AuthorizationManager<RequestAuthorizationContext> requestAuthorization() {
    PolicyAuthorizationManager policy = ...;
    LocalAuthorizationManager local = ...;
    return AuthorizationManagers.anyOf(policy, local);
}
@Bean
fun requestAuthorization(): AuthorizationManager<RequestAuthorizationContext> {
    val policy: PolicyAuthorizationManager = ...
    val local: LocalAuthorizationManager = ...
    return AuthorizationManagers.anyOf(policy, local)
}
<bean id="requestAuthorization" class="org.springframework.security.authorization.AuthorizationManagers"
        factory-method="anyOf">
    <constructor-arg>
        <util:list>
            <bean class="my.PolicyAuthorizationManager"/>
            <bean class="my.LocalAuthorizationManager"/>
        </util:list>
    </constructor-arg>
</bean>

然后,将其连接到 DSL 中,如下所示:spring-doc.cadn.net.cn

http
    .authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization))
    // ...
http {
    authorizeHttpRequests {
        authorize(anyRequest, requestAuthorization)
    }
    // ...
}
<http authorization-manager-ref="requestAuthorization"/>

authorizeHttpRequests旨在使您可以应用自定义AuthorizationManager添加到任何 URL 模式。 有关更多详细信息,请参阅参考spring-doc.cadn.net.cn

我使用ConsensusBased

没有框架提供的等效ConsensusBased. 在这种情况下,请实现一个复合AuthorizationManager,它采用 delegateAuthorizationManagers 的 S 中。spring-doc.cadn.net.cn

实施AuthorizationManager,请按照参考手册中的详细信息进行作添加自定义AuthorizationManager.spring-doc.cadn.net.cn

我使用自定义AccessDecisionVoter

您应该更改要实现的类AuthorizationManager或创建适配器。spring-doc.cadn.net.cn

如果不知道自定义选民在做什么,就不可能推荐通用解决方案。 不过,通过示例,以下是 adaptingSecurityMetadataSourceAccessDecisionVoteranyRequest().authenticated()将如下所示:spring-doc.cadn.net.cn

public final class AnyRequestAuthenticatedAuthorizationManagerAdapter implements AuthorizationManager<RequestAuthorizationContext> {
    private final SecurityMetadataSource metadata;
    private final AccessDecisionVoter voter;

    public PreAuthorizeAuthorizationManagerAdapter(SecurityExpressionHandler expressionHandler) {
        Map<RequestMatcher, List<ConfigAttribute>> requestMap = Collections.singletonMap(
                AnyRequestMatcher.INSTANCE, Collections.singletonList(new SecurityConfig("authenticated")));
        this.metadata = new DefaultFilterInvocationSecurityMetadataSource(requestMap);
        WebExpressionVoter voter = new WebExpressionVoter();
        voter.setExpressionHandler(expressionHandler);
        this.voter = voter;
    }

    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
        List<ConfigAttribute> attributes = this.metadata.getAttributes(context);
        int decision = this.voter.vote(authentication.get(), invocation, attributes);
        if (decision == ACCESS_GRANTED) {
            return new AuthorizationDecision(true);
        }
        if (decision == ACCESS_DENIED) {
            return new AuthorizationDecision(false);
        }
        return null; // abstain
    }
}

实施AuthorizationManager,请按照参考手册中的详细信息进行作添加自定义AuthorizationManager.spring-doc.cadn.net.cn

取代hasRolehasAuthority如果使用GrantedAuthorityDefaults

目前,hasRolemethod 中authorizeHttpRequests不支持GrantedAuthorityDefaultsbean 的authorizeRequests确实。 因此,如果您正在使用GrantedAuthorityDefaults要更改角色的前缀,您需要使用hasAuthority而不是hasRole.spring-doc.cadn.net.cn

例如,您必须从:spring-doc.cadn.net.cn

authorizeRequests 替换为自定义角色前缀
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeRequests((authorize) -> authorize
            .anyRequest().hasRole("ADMIN")
        );
    return http.build();
}

@Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults() {
    return new GrantedAuthorityDefaults("MYPREFIX_");
}
authorizeHttpRequests 中,带有 hasAuthority 和自定义角色前缀
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().hasAuthority("MYPREFIX_ADMIN")
        );
    return http.build();
}

这将在未来应该支持,有关更多详细信息,请参阅 gh-13227spring-doc.cadn.net.cn

选择退出步骤

如果您遇到问题,请查看以下方案,以实现最佳选择退出行为:spring-doc.cadn.net.cn

我无法保护所有 Dispatcher 类型

如果您无法保护所有 Dispatcher 类型,请首先尝试声明哪些 Dispatcher 类型不应需要授权,如下所示:spring-doc.cadn.net.cn

http
    .authorizeHttpRequests((authorize) -> authorize
        .shouldFilterAllDispatcherTypes(true)
        .dispatcherTypeMatchers(FORWARD, INCLUDE).permitAll()
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
        .anyRequest().denyAll()
    )
    // ...
http {
    authorizeHttpRequests {
        shouldFilterAllDispatcherTypes = true
        authorize(DispatcherTypeRequestMatcher(FORWARD, INCLUDE), permitAll)
        authorize("/app/**", hasRole("APP"))
        // ...
        authorize(anyRequest, denyAll)
    }
}
<http filter-all-dispatcher-types="true" use-authorization-manager="true">
    <intercept-url request-matcher-ref="dispatchers"/>
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
    <intercept-url pattern="/**" access="denyAll"/>
</http>

<bean id="dispatchers" class="org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher">
    <constructor-arg>
        <util:list value-type="javax.servlet.DispatcherType">
            <value>FORWARD</value>
            <value>INCLUDE</value>
        </util:list>
    </constructor-arg>
</bean>

或者,如果这不起作用,那么您可以通过设置filter-all-dispatcher-typesfilterAllDispatcherTypesfalse:spring-doc.cadn.net.cn

http
    .authorizeHttpRequests((authorize) -> authorize
        .filterAllDispatcherTypes(false)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
    )
    // ...
http {
    authorizeHttpRequests {
        filterAllDispatcherTypes = false
        authorize("/messages/**", hasRole("APP"))
        // ...
    }
}
<http filter-all-dispatcher-types="false" use-authorization-manager="true">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
</http>

或者,如果您仍在使用authorizeRequestsuse-authorization-manager="false"设置oncePerRequesttrue:spring-doc.cadn.net.cn

http
    .authorizeRequests((authorize) -> authorize
        .filterSecurityInterceptorOncePerRequest(true)
        .mvcMatchers("/app/**").hasRole("APP")
        // ...
    )
    // ...
http {
    authorizeRequests {
        filterSecurityInterceptorOncePerRequest = true
        authorize("/messages/**", hasRole("APP"))
        // ...
    }
}
<http once-per-request="true" use-authorization-manager="false">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
</http>

我无法为所有请求声明授权规则

如果您在设置anyRequest授权规则denyAll,请使用permitAll相反,像这样:spring-doc.cadn.net.cn

http
    .authorizeHttpReqeusts((authorize) -> authorize
        .mvcMatchers("/app/*").hasRole("APP")
        // ...
        .anyRequest().permitAll()
    )
http {
    authorizeHttpRequests {
        authorize("/app*", hasRole("APP"))
        // ...
        authorize(anyRequest, permitAll)
    }
}
<http>
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
    <intercept-url pattern="/**" access="permitAll"/>
</http>

我无法迁移我的 SpEL 或我的AccessDecisionManager

如果您在使用 SpEL 时遇到问题,AccessDecisionManager,或者您需要继续使用其他一些功能<http>authorizeRequests,请尝试以下作。spring-doc.cadn.net.cn

首先,如果您仍然需要authorizeRequests,欢迎您继续使用它。尽管它已被弃用,但 6.0 中并未将其删除。spring-doc.cadn.net.cn

其次,如果您仍然需要您的自定义access-decision-manager-ref或者有其他原因选择退出AuthorizationManager做:spring-doc.cadn.net.cn

XML 格式
<http use-authorization-manager="false">
    <intercept-url pattern="/app/*" access="hasRole('APP')"/>
    <!-- ... -->
</http>