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

授权迁移

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

用于方法安全性AuthorizationManager

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

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

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

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

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

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

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

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

应更改为:spring-doc.cn

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

更改 中的值order@EnableTransactionManagement

@EnableTransactionManagement,并且具有相同的值 . 这意味着它们在 Spring AOP Advisor 链中相对于彼此的顺序是不确定的。@EnableGlobalMethodSecurityorderInteger.MAX_VALUEspring-doc.cn

这通常很好,因为大多数方法安全表达式不需要打开的事务即可正常运行;然而,从历史上看,有时有必要通过设定它们的值来确保一个先于另一个发生。orderspring-doc.cn

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

相反,拦截器的值基于偏移量 0。 拦截器的顺序为 100;, 200;等等。@EnableMethodSecurity@PreFilter@PostAuthorizespring-doc.cn

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

使用 Custom 而不是子类化@BeanDefaultMethodSecurityExpressionHandler

作为性能优化,引入了一种新方法,该方法采用 a 而不是 .MethodSecurityExpressionHandlerSupplier<Authentication>Authenticationspring-doc.cn

这允许 Spring Security 延迟查找 ,并在您使用而不是 .Authentication@EnableMethodSecurity@EnableGlobalMethodSecurityspring-doc.cn

但是,假设您的代码扩展并覆盖以返回自定义实例。 这将不再有效,因为 setup up calls 的 arrangement 改为。DefaultMethodSecurityExpressionHandlercreateSecurityExpressionRoot(Authentication, MethodInvocation)SecurityExpressionRoot@EnableMethodSecuritycreateEvaluationContext(Supplier<Authentication>, MethodInvocation)spring-doc.cn

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

例如,假设您希望对 . 您可以创建如下所示的自定义:@PostAuthorize("hasAuthority('ADMIN')")@Beanspring-doc.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.cn

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

我还是更喜欢子类DefaultMethodSecurityExpressionHandler

如果必须继续子类化 ,您仍然可以这样做。 相反,请覆盖该方法,如下所示:DefaultMethodSecurityExpressionHandlercreateEvaluationContext(Supplier<Authentication>, MethodInvocation)spring-doc.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.cn

发布 a 而不是MethodSecurityExpressionHandlerPermissionEvaluator

@EnableMethodSecurity不会选取 . 这有助于保持其 API 简单。PermissionEvaluatorspring-doc.cn

如果您有自定义 PermissionEvaluator ,请将其从:@Beanspring-doc.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
	}
}

替换任何自定义 method-security sAccessDecisionManager

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

我使用UnanimousBased

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

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

请注意,与 有一个不同之处,即如果所有代理都弃权,则授予授权。 如果必须在所有委托人弃权时拒绝授权,请实施一个复合 AuthorizationManager,该组合 AuthorizationManager 将委托集考虑在内。allOfAuthorizationManagerspring-doc.cn

完成此操作后,请按照参考手册中的详细信息添加自定义 AuthorizationManagerspring-doc.cn

我使用AffirmativeBased

如果您的应用程序使用 AffirmativeBased,则可以构造等效的 AuthorizationManager,如下所示:spring-doc.cn

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

实施后,请按照参考手册中的详细信息添加自定义 AuthorizationManagerAuthorizationManagerspring-doc.cn

我使用ConsensusBased

没有框架提供的 ConsensusBased 等效项。 在这种情况下,请实现一个复合 AuthorizationManager,该复合 AuthorizationManager 将 delegate 集考虑在内。AuthorizationManagerspring-doc.cn

实施后,请按照参考手册中的详细信息添加自定义 AuthorizationManagerAuthorizationManagerspring-doc.cn

我使用自定义AccessDecisionVoter

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

如果不知道自定义选民在做什么,就不可能推荐通用解决方案。 不过,通过示例,以下是调整 SecurityMetadataSourceAccessDecisionVoter 的情况:@PreAuthorizespring-doc.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
    }
}

实施后,请按照参考手册中的详细信息添加自定义 AuthorizationManagerAuthorizationManagerspring-doc.cn

我使用 或AfterInvocationManagerAfterInvocationProvider

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

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

这两个注释应该可以满足大多数需求,我们鼓励您迁移到其中一个或两个注释,因为 和 现在已弃用。AfterInvocationProviderAfterInvocationManagerspring-doc.cn

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

如果它尝试过滤,请考虑发布自定义 bean 并使用 . 或者,如果需要,您可以实现自己的 ,查看 和 示例。@PostFilter("@mybean.authorize(#root)")MethodInterceptorPostFilterAuthorizationMethodInterceptorPrePostMethodSecurityConfigurationspring-doc.cn

我使用RunAsManager

目前没有 RunAsManager 的替代项,但正在考虑一个替代项。spring-doc.cn

但是,如果需要,将 , 调整为 API 非常简单。RunAsManagerAuthorizationManagerspring-doc.cn

以下是一些帮助您入门的伪代码:spring-doc.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);
    }
}

实施后,请按照参考手册中的详细信息添加自定义 AuthorizationManagerAuthorizationManagerspring-doc.cn

检查 sAnnotationConfigurationException

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

用于消息安全性AuthorizationManager

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

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

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

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

例如,应用程序配置如下:spring-doc.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.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 配置,则迁移步骤略有不同。 您将自行覆盖适当的方法,而不是使用 。 有关此步骤的详细信息,请参阅参考手册@EnableWebSocketSecurityWebSocketMessageBrokerConfigurerspring-doc.cn

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

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

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

这将使 的原型实例 可用 鼓励通过组合而不是扩展进行配置。MessageMatcherDelegatingAuthorizationManager.Builderspring-doc.cn

使用实例AuthorizationManager<Message<?>>

要开始使用 ,您可以在 XML 中设置该属性,也可以在 Java 中发布 。AuthorizationManageruse-authorization-managerAuthorizationManager<Message<?>>@Beanspring-doc.cn

例如,以下应用程序配置:spring-doc.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>

更改为:spring-doc.cn

@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 配置,则现在只需扩展 .WebSocketMessageBrokerConfigurerspring-doc.cn

例如,如果您的 extends 类名为 ,则:AbstractSecurityWebSocketMessageBrokerConfigurerWebSocketSecurityConfigspring-doc.cn

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

更改为:spring-doc.cn

@EnableWebSocketSecurity
@Configuration
public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
	// ...
}
@EnableWebSocketSecurity
@Configuration
class WebSocketSecurityConfig: WebSocketMessageBrokerConfigurer {
	// ...
}

选择退出步骤

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

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

如果您在设置授权规则 时遇到问题,请改用 permitAll,如下所示:anyRequestdenyAllspring-doc.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 正常工作,需要一些其他功能,或者遇到问题AbstractSecurityWebSocketMessageBrokerConfigurerAuthorizationManager

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

对于 XML,您可以通过设置 来选择退出 :AuthorizationManageruse-authorization-manager="false"spring-doc.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.cn

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

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

准备此更改的最简单方法是引入适当的 anyRequest 规则作为最后一个授权规则。 建议使用 denyAll,因为这是隐含的 6.0 默认值。spring-doc.cn

您可能已经定义了一个您满意的规则,在这种情况下,可以跳过此步骤。anyRequestspring-doc.cn

添加到末尾看起来像是更改:denyAllspring-doc.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>

如果您已迁移到 ,则建议的更改是相同的。authorizeHttpRequestsspring-doc.cn

切换到AuthorizationManager

要选择使用 ,您可以分别对 Java 或 XML 使用 或 use-authorization-managerAuthorizationManagerauthorizeHttpRequestsspring-doc.cn

改变:spring-doc.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)

hasIpAddress在 中没有等效的 DSL。authorizeHttpRequestsspring-doc.cn

因此,您需要将任何被调用的 更改为使用 .hasIpAddressAuthorizationManagerspring-doc.cn

首先,构造一个这样的:IpAddressMatcherspring-doc.cn

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

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

Java
http
    .authorizeRequests((authorize) -> authorize
        .mvcMatchers("/app/**").hasIpAddress("127.0.0.1")
        // ...
        .anyRequest().denyAll()
    )
    // ...

到这个:spring-doc.cn

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

将 SPEL 表达式迁移到AuthorizationManager

对于授权规则,Java 往往比 SPEL 更容易测试和维护。 因此,没有声明 SPEL 的方法。authorizeHttpRequestsStringspring-doc.cn

相反,您可以实施自己的实现或使用 .AuthorizationManagerWebExpressionAuthorizationManagerspring-doc.cn

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

首先,如果您有以下 SPEL:spring-doc.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)
    }
}

然后,您可以使用 Spring Security 授权原语编写自己的授权原语,如下所示:AuthorizationManagerspring-doc.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)
    }
}

或者您可以通过以下方式使用:WebExpressionAuthorizationManagerspring-doc.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 及更早版本仅对每个请求执行一次授权。 这意味着默认情况下,像 和 run after 这样的调度程序类型是不安全的。FORWARDINCLUDEREQUESTspring-doc.cn

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

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

为此,您应该更改:spring-doc.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>

此外,还应为所有 Dispatcher 类型注册 。 如果您使用的是 Spring Boot,则必须更改spring.security.filter.dispatcher-types属性以包含所有调度程序类型:FilterChainProxyspring-doc.cn

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

如果你使用的是 AbstractSecurityWebApplicationInitializer你应该重写该方法并返回所有调度程序类型:getSecurityDispatcherTypesspring-doc.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);
    }

}

使用 Spring MVC 时允许FORWARD

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

请考虑以下常见配置:spring-doc.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.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");
    }

}

无论使用哪种配置,当有请求时,Spring MVC 都将执行对视图的转发,该视图在默认配置下位于 path 下。 安全配置允许请求,但所有其他请求都将被拒绝,包括对 下的视图的请求。/loginloginsrc/main/resources/templates/login.html/loginFORWARD/templates/login.htmlspring-doc.cn

要解决此问题,您应该将 Spring Security 配置为允许请求:FORWARDspring-doc.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-security sAccessDecisionManager

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

我使用UnanimousBased

如果您的应用程序使用 UnanimousBased,您应该首先调整或替换任何 s,然后您可以构造一个这样的:AccessDecisionVoterAuthorizationManagerspring-doc.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.cn

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

authorizeHttpRequests旨在使您可以将自定义应用于任何 URL 模式。 有关更多详细信息,请参阅参考AuthorizationManagerspring-doc.cn

我使用AffirmativeBased

如果您的应用程序使用 AffirmativeBased,则可以构造等效的 AuthorizationManager,如下所示:spring-doc.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.cn

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

authorizeHttpRequests旨在使您可以将自定义应用于任何 URL 模式。 有关更多详细信息,请参阅参考AuthorizationManagerspring-doc.cn

我使用ConsensusBased

没有框架提供的 ConsensusBased 等效项。 在这种情况下,请实现一个复合 AuthorizationManager,该复合 AuthorizationManager 将 delegate 集考虑在内。AuthorizationManagerspring-doc.cn

实施后,请按照参考手册中的详细信息添加自定义 AuthorizationManagerAuthorizationManagerspring-doc.cn

我使用自定义AccessDecisionVoter

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

如果不知道自定义选民在做什么,就不可能推荐通用解决方案。 不过,通过示例,以下是调整 SecurityMetadataSourceAccessDecisionVoter 的情况:anyRequest().authenticated()spring-doc.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
    }
}

实施后,请按照参考手册中的详细信息添加自定义 AuthorizationManagerAuthorizationManagerspring-doc.cn

如果使用 Replace withhasRolehasAuthorityGrantedAuthorityDefaults

目前,里面的方法不像 那样支持 bean。 因此,如果要更改角色的前缀,则需要使用 而不是 。hasRoleauthorizeHttpRequestsGrantedAuthorityDefaultsauthorizeRequestsGrantedAuthorityDefaultshasAuthorityhasRolespring-doc.cn

例如,您必须从:spring-doc.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.cn

选择退出步骤

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

我无法保护所有 Dispatcher 类型

如果您无法保护所有 Dispatcher 类型,请首先尝试声明哪些 Dispatcher 类型不应需要授权,如下所示:spring-doc.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-typesfilterAllDispatcherTypesfalsespring-doc.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"oncePerRequesttruespring-doc.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>

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

如果您在设置授权规则 时遇到问题,请改用 permitAll,如下所示:anyRequestdenyAllspring-doc.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>authorizeRequestsspring-doc.cn

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

其次,如果您仍然需要您的自定义或有其他原因选择退出,请执行:access-decision-manager-refAuthorizationManagerspring-doc.cn

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