此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.3spring-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 {
	// ...
}

这也意味着您需要使用MessageMatcherDelegationAuthorizationManager以指定您的授权规则。spring-doc.cadn.net.cn

选择退出步骤

如果您遇到问题,请查看以下方案,以实现最佳选择退出行为: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);
    }

}

Permit FORWARD when using Spring MVC

If you are using Spring MVC to resolve view names, you will need to permit FORWARD requests. This is because when Spring MVC detects a mapping between view name and the actual views, it will perform a forward to the view. As we saw on the previous section, Spring Security 6.0 will apply authorization to FORWARD requests by default.spring-doc.cadn.net.cn

Consider the following common configuration: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();
}

and one of the following equivalents MVC view mapping configurations: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");
    }

}

With either configuration, when there is a request to /login, Spring MVC will perform a forward to the view login, which, with the default configuration, is under src/main/resources/templates/login.html path. The security configuration permits requests to /login but every other request will be denied, including the FORWARD request to the view under /templates/login.html.spring-doc.cadn.net.cn

To fix this, you should configure Spring Security to permit FORWARD requests: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>

Replace any custom filter-security AccessDecisionManagers

Your application may have a custom AccessDecisionManager or AccessDecisionVoter arrangement. The preparation strategy will depend on your reason for each arrangement. Read on to find the best match for your situation.spring-doc.cadn.net.cn

I use UnanimousBased

If your application uses UnanimousBased, you should first adapt or replace any AccessDecisionVoters and then you can construct an AuthorizationManager like so: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>

then, wire it into the DSL like so:spring-doc.cadn.net.cn

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

authorizeHttpRequests is designed so that you can apply a custom AuthorizationManager to any url pattern. See the reference for more details.spring-doc.cadn.net.cn

I use AffirmativeBased

If your application uses AffirmativeBased, then you can construct an equivalent AuthorizationManager, like so: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>

then, wire it into the DSL like so:spring-doc.cadn.net.cn

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

authorizeHttpRequests is designed so that you can apply a custom AuthorizationManager to any url pattern. See the reference for more details.spring-doc.cadn.net.cn

I use ConsensusBased

There is no framework-provided equivalent for ConsensusBased. In that case, please implement a composite AuthorizationManager that takes the set of delegate AuthorizationManagers into account.spring-doc.cadn.net.cn

Once you have implemented AuthorizationManager, please follow the details in the reference manual for adding a custom AuthorizationManager.spring-doc.cadn.net.cn

I use a custom AccessDecisionVoter

You should either change the class to implement AuthorizationManager or create an adapter.spring-doc.cadn.net.cn

Without knowing what your custom voter is doing, it is impossible to recommend a general-purpose solution. By way of example, though, here is what adapting SecurityMetadataSource and AccessDecisionVoter for anyRequest().authenticated() would look like: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
    }
}

Once you have implemented AuthorizationManager, please follow the details in the reference manual for adding a custom AuthorizationManager.spring-doc.cadn.net.cn

Replace hasRole with hasAuthority if using GrantedAuthorityDefaults

Currently, the hasRole method inside authorizeHttpRequests does not support the GrantedAuthorityDefaults bean like the authorizeRequests does. Therefore, if you are using GrantedAuthorityDefaults to change the prefix of your roles, you will need to use hasAuthority instead of hasRole.spring-doc.cadn.net.cn

For example, you will have to change from:spring-doc.cadn.net.cn

authorizeRequests with custom role prefix
@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 with hasAuthority and custom role prefix
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().hasAuthority("MYPREFIX_ADMIN")
        );
    return http.build();
}

This should be supported in the future, see gh-13227 for more details.spring-doc.cadn.net.cn

Opt-out Steps

In case you had trouble, take a look at these scenarios for optimal opt out behavior:spring-doc.cadn.net.cn

I cannot secure all dispatcher types

If you cannot secure all dispatcher types, first try and declare which dispatcher types should not require authorization like so: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>

Or, if that doesn’t work, then you can explicitly opt out of the behavior by setting filter-all-dispatcher-types and filterAllDispatcherTypes to false: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>

or, if you are still using authorizeRequests or use-authorization-manager="false", set oncePerRequest to true: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>

I cannot declare an authorization rule for all requests

If you are having trouble setting an anyRequest authorization rule of denyAll, please use permitAll instead, like so: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>

I cannot migrate my SpEL or my AccessDecisionManager

If you are having trouble with SpEL, AccessDecisionManager, or there is some other feature that you are needing to keep using in <http> or authorizeRequests, try the following.spring-doc.cadn.net.cn

First, if you still need authorizeRequests, you are welcome to keep using it. Even though it is deprecated, it is not removed in 6.0.spring-doc.cadn.net.cn

Second, if you still need your custom access-decision-manager-ref or have some other reason to opt out of AuthorizationManager, do:spring-doc.cadn.net.cn

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