此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.3! |
授权迁移
以下步骤与有关执行授权方式的更改有关。
用AuthorizationManager
针对 Method Security
方法安全性已通过这AuthorizationManager
应用程序接口以及直接使用 Spring AOP。
如果您在进行这些更改时遇到问题,请注意@EnableGlobalMethodSecurity
,虽然已弃用,但在 6.0 中不会删除,允许您通过坚持使用旧注释来选择退出。
将全局方法安全性替换为方法安全性
@EnableGlobalMethodSecurity
和<global-method-security>
已弃用,取而代之的是@EnableMethodSecurity
和<method-security>
分别。
新的 annotation 和 XML 元素默认激活 Spring 的 pre-post 注释,并使用AuthorizationManager
内部。
这意味着以下两个清单在功能上是等效的:
-
Java
-
Kotlin
-
Xml
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
<global-method-security pre-post-enabled="true"/>
和:
-
Java
-
Kotlin
-
Xml
@EnableMethodSecurity
@EnableMethodSecurity
<method-security/>
对于不使用 pre-post 注释的应用程序,请确保将其关闭以避免激活不需要的行为。
例如,像这样的列表:
-
Java
-
Kotlin
-
Xml
@EnableGlobalMethodSecurity(securedEnabled = true)
@EnableGlobalMethodSecurity(securedEnabled = true)
<global-method-security secured-enabled="true"/>
应更改为:
-
Java
-
Kotlin
-
Xml
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
<method-security secured-enabled="true" pre-post-enabled="false"/>
更改order
值@EnableTransactionManagement
@EnableTransactionManagement
和@EnableGlobalMethodSecurity
具有相同的order
值Integer.MAX_VALUE
.
这意味着它们在 Spring AOP Advisor 链中相对于彼此的顺序是不确定的。
这通常很好,因为大多数方法安全表达式不需要打开的事务即可正常运行;然而,从历史上看,有时有必要通过设置它们的order
值。
@EnableMethodSecurity
没有order
值,因为它会发布多个拦截器。
事实上,它不能尝试向后兼容@EnableTransactionManagement
因为它无法将所有拦截器设置为位于同一 advisor 链位置。
相反,@EnableMethodSecurity
拦截器基于 0 的偏移量。
这@PreFilter
Interceptor 的订单为 100;@PostAuthorize
, 200;等等。
因此,如果在更新后发现由于没有打开的事务而导致方法安全表达式不起作用,请从以下内容更改事务注释定义:
-
Java
-
Kotlin
-
Xml
@EnableTransactionManagement
@EnableTransactionManagement
<tx:annotation-driven ref="txManager"/>
自:
-
Java
-
Kotlin
-
Xml
@EnableTransactionManagement(order = 0)
@EnableTransactionManagement(order = 0)
<tx:annotation-driven ref="txManager" order="0"/>
这样,事务 AOP 建议将放在 Spring Security 的建议之前,并且在评估您的授权 SPEL 表达式时,事务将打开。
使用自定义@Bean
而不是子类化DefaultMethodSecurityExpressionHandler
作为性能优化,引入了一种新方法MethodSecurityExpressionHandler
这需要Supplier<Authentication>
而不是Authentication
.
这允许 Spring Security 延迟对Authentication
,并在您使用@EnableMethodSecurity
而不是@EnableGlobalMethodSecurity
.
但是,假设您的代码扩展了DefaultMethodSecurityExpressionHandler
和覆盖createSecurityExpressionRoot(Authentication, MethodInvocation)
返回自定义SecurityExpressionRoot
实例。
这将不再有效,因为@EnableMethodSecurity
设置呼叫createEvaluationContext(Supplier<Authentication>, MethodInvocation)
相反。
令人高兴的是,这种级别的定制通常是不必要的。 相反,您可以使用所需的授权方法创建自定义 Bean。
例如,假设您希望对@PostAuthorize("hasAuthority('ADMIN')")
.
您可以创建自定义@Bean
像这个:
-
Java
-
Kotlin
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 中引用它,如下所示:
-
Java
-
Kotlin
@PreAuthorize("@authz.isAdmin(#root)")
@PreAuthorize("@authz.isAdmin(#root)")
我还是更喜欢子类DefaultMethodSecurityExpressionHandler
如果必须继续子类化DefaultMethodSecurityExpressionHandler
,您仍然可以这样做。
相反,请覆盖createEvaluationContext(Supplier<Authentication>, MethodInvocation)
方法如下:
-
Java
-
Kotlin
@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;
}
}
发布MethodSecurityExpressionHandler
而不是PermissionEvaluator
@EnableMethodSecurity
不会拾取PermissionEvaluator
.
这有助于保持其 API 简单。
如果您有一个自定义的PermissionEvaluator
@Bean
,请将其从:
-
Java
-
Kotlin
@Bean
static PermissionEvaluator permissionEvaluator() {
// ... your evaluator
}
companion object {
@Bean
fun permissionEvaluator(): PermissionEvaluator {
// ... your evaluator
}
}
自:
-
Java
-
Kotlin
@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
}
}
替换任何自定义方法 - 安全性AccessDecisionManager
s
您的应用程序可能具有自定义的AccessDecisionManager
或AccessDecisionVoter
安排。
准备策略将取决于您每次安排的原因。
请继续阅读以找到最适合您情况的方案。
我使用UnanimousBased
如果您的应用程序使用UnanimousBased
对于默认投票者,您可能不需要执行任何作,因为基于 Unanimous 是默认行为@EnableMethodSecurity
.
但是,如果您发现无法接受默认授权管理器,则可以使用AuthorizationManagers.allOf
来编写您自己的编曲。
请注意,与allOf
,即如果所有代表都弃权,则授予授权。
如果在所有代表都弃权时必须拒绝授权,请实施一个组合AuthorizationManager
,它采用 delegateAuthorizationManager
s 的 S 中。
完成此作后,请按照参考手册中的详细信息进行作添加自定义AuthorizationManager
.
我使用AffirmativeBased
如果您的应用程序使用AffirmativeBased
,则可以构造一个等效的AuthorizationManager
这样:
-
Java
-
Kotlin
AuthorizationManager<MethodInvocation> authorization = AuthorizationManagers.anyOf(
// ... your list of authorization managers
)
val authorization = AuthorizationManagers.anyOf(
// ... your list of authorization managers
)
实施AuthorizationManager
,请按照参考手册中的详细信息进行作添加自定义AuthorizationManager
.
我使用ConsensusBased
没有框架提供的等效ConsensusBased
.
在这种情况下,请实现一个复合AuthorizationManager
,它采用 delegateAuthorizationManager
s 的 S 中。
实施AuthorizationManager
,请按照参考手册中的详细信息进行作添加自定义AuthorizationManager
.
我使用自定义AccessDecisionVoter
您应该更改要实现的类AuthorizationManager
或创建适配器。
如果不知道自定义选民在做什么,就不可能推荐通用解决方案。
不过,通过示例,以下是 adaptingSecurityMetadataSource
和AccessDecisionVoter
为@PreAuthorize
将如下所示:
-
Java
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
.
我使用AfterInvocationManager
或AfterInvocationProvider
AfterInvocationManager
和AfterInvocationProvider
对调用的结果做出授权决定。
例如,在方法调用的情况下,这些命令会做出有关方法返回值的授权决策。
在 Spring Security 3.0 中,授权决策被标准化为@PostAuthorize
和@PostFilter
附注.@PostAuthorize
用于确定是否允许返回整个返回值。@PostFilter
用于从返回的集合、数组或流中筛选单个条目。
这两个 Comments 应该可以满足大多数需求,我们鼓励您迁移到其中一个或两个AfterInvocationProvider
和AfterInvocationManager
现已弃用。
如果您已实现自己的AfterInvocationManager
或AfterInvocationProvider
,您应该首先问问自己它试图做什么。
如果它尝试授权返回类型,则考虑实施AuthorizationManager<MethodInvocationResult>
并使用AfterMethodAuthorizationManagerInterceptor
.或者发布自定义 Bean 并使用@PostAuthorize("@myBean.authorize(#root)")
.
如果它尝试过滤,则考虑发布自定义 Bean 并使用@PostFilter("@mybean.authorize(#root)")
.
或者,如果需要,您可以实施自己的MethodInterceptor
,看看PostFilterAuthorizationMethodInterceptor
和PrePostMethodSecurityConfiguration
例如。
我使用RunAsManager
目前有无替代品RunAsManager
尽管正在考虑中。
调整RunAsManager
但是,将AuthorizationManager
API(如果需要)。
以下是一些帮助您入门的伪代码:
-
Java
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
.
用AuthorizationManager
针对 Message Security
消息安全性已通过以下方式得到改进这AuthorizationManager
应用程序接口以及直接使用 Spring AOP。
如果您在进行这些更改时遇到问题,可以按照本节末尾的选择退出步骤进行作。
确保所有消息都已定义授权规则
为此,请确保为每个请求声明授权规则存在。
例如,应用程序配置如下:
-
Java
-
Kotlin
-
Xml
@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>
应更改为:
-
Java
-
Kotlin
-
Xml
@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 配置,则迁移步骤略有不同。
而不是使用 |
如果您使用的是 Java 配置,请添加@EnableWebSocketSecurity
添加到您的应用程序中。
例如,您可以将其添加到 websocket 安全配置类中,如下所示:
-
Java
-
Kotlin
@EnableWebSocketSecurity
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
// ...
}
@EnableWebSocketSecurity
@Configuration
class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
// ...
}
这将使MessageMatcherDelegatingAuthorizationManager.Builder
可用于鼓励通过组合而不是扩展进行配置。
使用AuthorizationManager<Message<?>>
实例
开始使用AuthorizationManager
中,您可以设置use-authorization-manager
属性,或者您可以发布AuthorizationManager<Message<?>>
@Bean
在 Java 中。
例如,以下应用程序配置:
-
Java
-
Kotlin
-
Xml
@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>
更改为:
-
Java
-
Kotlin
-
Xml
@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
.
例如,如果扩展了AbstractSecurityWebSocketMessageBrokerConfigurer
称为WebSocketSecurityConfig
然后:
-
Java
-
Kotlin
@EnableWebSocketSecurity
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
// ...
}
@EnableWebSocketSecurity
@Configuration
class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
// ...
}
更改为:
-
Java
-
Kotlin
@EnableWebSocketSecurity
@Configuration
public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
// ...
}
@EnableWebSocketSecurity
@Configuration
class WebSocketSecurityConfig: WebSocketMessageBrokerConfigurer {
// ...
}
这也意味着您需要使用MessageMatcherDelegationAuthorizationManager
以指定您的授权规则。
选择退出步骤
如果您遇到问题,请查看以下方案,以实现最佳选择退出行为:
我无法为所有请求声明授权规则
如果您在设置anyRequest
授权规则denyAll
,请使用permitAll
相反,像这样:
-
Java
-
Kotlin
-
Xml
@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 中删除。
对于 XML,您可以选择退出AuthorizationManager
通过设置use-authorization-manager="false"
:
<websocket-message-broker>
<intercept-message pattern="/user/queue/errors" access="permitAll"/>
<intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
</websocket-message-broker>
自:
<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 Security 5.8 及更早版本中,默认情况下允许没有授权规则的请求。 默认情况下拒绝是一个更强的安全立场,因此需要为每个端点明确定义授权规则。 因此,在 6.0 中, Spring Security 默认拒绝任何缺少授权规则的请求。
为这一变化做准备的最简单方法是引入适当的anyRequest
rule 作为最后一个授权规则。
建议是denyAll
因为这是隐含的 6.0 默认值。
您可能已经有一个 |
添加denyAll
到最后看起来像是变化:
-
Java
-
Kotlin
-
Xml
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>
自:
-
Java
-
Kotlin
-
Xml
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
,建议的更改是相同的。
切换到AuthorizationManager
要选择使用AuthorizationManager
,您可以使用authorizeHttpRequests
或use-authorization-manager
分别用于 Java 或 XML。
改变:
-
Java
-
Kotlin
-
Xml
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>
自:
-
Java
-
Kotlin
-
Xml
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>
迁移hasIpAddress
自access(AuthorizationManager)
hasIpAddress
在authorizeHttpRequests
.
因此,您需要将任何调用更改为hasIpAddress
更改为使用AuthorizationManager
.
首先,构造一个IpAddressMatcher
这样:
IpAddressMatcher hasIpAddress = new IpAddressMatcher("127.0.0.1");
然后从这里改变:
http
.authorizeRequests((authorize) -> authorize
.mvcMatchers("/app/**").hasIpAddress("127.0.0.1")
// ...
.anyRequest().denyAll()
)
// ...
到这个:
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
斯佩尔。
相反,您可以实施自己的AuthorizationManager
implementation 或使用WebExpressionAuthorizationManager
.
为了完整起见,将演示这两个选项。
首先,如果您有以下 SPEL:
-
Java
-
Kotlin
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 授权原语,如下所示:
-
Java
-
Kotlin
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
采用以下方式:
-
Java
-
Kotlin
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 类型(如FORWARD
和INCLUDE
那次之后的运行REQUEST
默认情况下不安全。
建议 Spring Security 保护所有分派类型。 因此,在 6.0 中, Spring Security 更改了此默认值。
因此,最后,更改您的授权规则以筛选所有 Dispatcher 类型。
为此,您应该更改:
-
Java
-
Kotlin
-
Xml
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>
自:
-
Java
-
Kotlin
-
Xml
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 类型,请执行以下作:
-
application.properties
spring.security.filter.dispatcher-types=request,async,error,forward,include
如果你是使用AbstractSecurityWebApplicationInitializer
您应该覆盖getSecurityDispatcherTypes
方法并返回所有 Dispatcher 类型:
-
Java
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.
Consider the following common configuration:
-
Java
@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:
-
Java
@Controller
public class MyController {
@GetMapping("/login")
public String login() {
return "login";
}
}
-
Java
@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
.
To fix this, you should configure Spring Security to permit FORWARD
requests:
-
Java
-
Kotlin
-
Xml
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 AccessDecisionManager
s
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.
I use UnanimousBased
If your application uses UnanimousBased
, you should first adapt or replace any AccessDecisionVoter
s and then you can construct an AuthorizationManager
like so:
-
Java
-
Kotlin
-
Xml
@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:
-
Java
-
Kotlin
-
Xml
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.
I use AffirmativeBased
If your application uses AffirmativeBased
, then you can construct an equivalent AuthorizationManager
, like so:
-
Java
-
Kotlin
-
Xml
@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:
-
Java
-
Kotlin
-
Xml
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.
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 AuthorizationManager
s into account.
Once you have implemented AuthorizationManager
, please follow the details in the reference manual for adding a custom AuthorizationManager
.
I use a custom AccessDecisionVoter
You should either change the class to implement AuthorizationManager
or create an adapter.
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:
-
Java
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
.
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
.
For example, you will have to change from:
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_");
}
to:
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.
Opt-out Steps
In case you had trouble, take a look at these scenarios for optimal opt out behavior:
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:
-
Java
-
Kotlin
-
Xml
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
:
-
Java
-
Kotlin
-
Xml
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
:
-
Java
-
Kotlin
-
Xml
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:
-
Java
-
Kotlin
-
Xml
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.
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.
Second, if you still need your custom access-decision-manager-ref
or have some other reason to opt out of AuthorizationManager
, do:
Xml
<http use-authorization-manager="false">
<intercept-url pattern="/app/*" access="hasRole('APP')"/>
<!-- ... -->
</http>