对于最新的稳定版本,请使用 Spring Security 6.3.3! |
对于最新的稳定版本,请使用 Spring Security 6.3.3! |
以下步骤与有关执行授权方式的更改有关。
用于方法安全性AuthorizationManager
通过 AuthorizationManager
API 和直接使用 Spring AOP 简化了方法安全性。
如果您在进行这些更改时遇到问题,请注意,虽然已弃用,但在 6.0 中不会删除,允许您通过坚持使用旧注释来选择退出。@EnableGlobalMethodSecurity
将全局方法安全性替换为方法安全性
@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
,并且具有相同的值 .
这意味着它们在 Spring AOP Advisor 链中相对于彼此的顺序是不确定的。@EnableGlobalMethodSecurity
order
Integer.MAX_VALUE
这通常很好,因为大多数方法安全表达式不需要打开的事务即可正常运行;然而,从历史上看,有时有必要通过设定它们的值来确保一个先于另一个发生。order
@EnableMethodSecurity
没有值,因为它发布了多个拦截器。
事实上,它无法尝试向后兼容,因为它无法将所有拦截器设置为位于同一个 advisor 链位置。order
@EnableTransactionManagement
相反,拦截器的值基于偏移量 0。
拦截器的顺序为 100;, 200;等等。@EnableMethodSecurity
@PreFilter
@PostAuthorize
因此,如果在更新后发现由于没有打开的事务而导致方法安全表达式不起作用,请从以下内容更改事务注释定义:
-
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 表达式时,事务将打开。
使用 Custom 而不是子类化@Bean
DefaultMethodSecurityExpressionHandler
作为性能优化,引入了一种新方法,该方法采用 a 而不是 .MethodSecurityExpressionHandler
Supplier<Authentication>
Authentication
这允许 Spring Security 延迟查找 ,并在您使用而不是 .Authentication
@EnableMethodSecurity
@EnableGlobalMethodSecurity
但是,假设您的代码扩展并覆盖以返回自定义实例。
这将不再有效,因为 setup up calls 的 arrangement 改为。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;
}
}
发布 a 而不是MethodSecurityExpressionHandler
PermissionEvaluator
@EnableMethodSecurity
不会选取 .
这有助于保持其 API 简单。PermissionEvaluator
如果您有自定义 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
}
}
替换任何自定义 method-security sAccessDecisionManager
您的应用程序可能具有自定义的 AccessDecisionManager
或 AccessDecisionVoter
安排。
准备策略将取决于您每次安排的原因。
请继续阅读以找到最适合您情况的方案。
我使用UnanimousBased
如果您的应用程序对默认投票者使用 UnanimousBased
,则您可能不需要执行任何操作,因为 unanimousBased 是 @EnableMethodSecurity
的默认行为。
但是,如果您发现无法接受默认授权管理器,则可以使用 来编写自己的 arrangement。AuthorizationManagers.allOf
请注意,与 有一个不同之处,即如果所有代理都弃权,则授予授权。
如果必须在所有委托人弃权时拒绝授权,请实施一个复合 AuthorizationManager,该组合 AuthorizationManager
将委托集考虑在内。allOf
AuthorizationManager
完成此操作后,请按照参考手册中的详细信息添加自定义 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,该复合 AuthorizationManager
将 delegate 集考虑在内。AuthorizationManager
实施后,请按照参考手册中的详细信息添加自定义 AuthorizationManager
。AuthorizationManager
我使用自定义AccessDecisionVoter
您应该更改类以实现 AuthorizationManager
或创建适配器。
如果不知道自定义选民在做什么,就不可能推荐通用解决方案。
不过,通过示例,以下是调整 SecurityMetadataSource
和 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
这两个注释应该可以满足大多数需求,我们鼓励您迁移到其中一个或两个注释,因为 和 现在已弃用。AfterInvocationProvider
AfterInvocationManager
如果您已经实现了自己的 or ,您应该首先问问自己它试图做什么。
如果它尝试授权返回类型,请考虑实现 AuthorizationManager<MethodInvocationResult>
并使用 AfterMethodAuthorizationManagerInterceptor
。或者发布自定义 Bean 并使用 .AfterInvocationManager
AfterInvocationProvider
@PostAuthorize("@myBean.authorize(#root)")
如果它尝试过滤,请考虑发布自定义 bean 并使用 .
或者,如果需要,您可以实现自己的 ,查看 和 示例。@PostFilter("@mybean.authorize(#root)")
MethodInterceptor
PostFilterAuthorizationMethodInterceptor
PrePostMethodSecurityConfiguration
我使用RunAsManager
目前没有 RunAsManager
的替代项,但正在考虑一个替代项。
但是,如果需要,将 , 调整为 API 非常简单。RunAsManager
AuthorizationManager
以下是一些帮助您入门的伪代码:
-
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
通过 AuthorizationManager
API 和直接使用 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<?>>
要开始使用 ,您可以在 XML 中设置该属性,也可以在 Java 中发布 。AuthorizationManager
use-authorization-manager
AuthorizationManager<Message<?>>
@Bean
例如,以下应用程序配置:
-
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
例如,如果您的 extends 类名为 ,则: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 {
// ...
}
选择退出步骤
如果您遇到问题,请查看以下方案,以实现最佳选择退出行为:
我无法为所有请求声明授权规则
如果您在设置授权规则 时遇到问题,请改用 permitAll
,如下所示:anyRequest
denyAll
-
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,您可以继续使用 .
即使它已被弃用,也不会在 6.0 中删除。AbstractMessageSecurityWebSocketMessageBrokerConfigurer
对于 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>
如果您想禁用 CSRF 并且您使用的是 Java 配置,则迁移步骤略有不同。
您将自行覆盖适当的方法,而不是使用 。
有关此步骤的详细信息,请参阅参考手册。 |
用于请求安全性AuthorizationManager
如果您在进行这些更改时遇到问题,可以按照本节末尾的选择退出步骤进行操作。
确保所有请求都已定义授权规则
在 Spring Security 5.8 及更早版本中,默认情况下允许没有授权规则的请求。 默认情况下拒绝是一个更强的安全立场,因此需要为每个端点明确定义授权规则。 因此,在 6.0 中, Spring Security 默认拒绝任何缺少授权规则的请求。
准备此更改的最简单方法是引入适当的 anyRequest
规则作为最后一个授权规则。
建议使用 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
要选择使用 ,您可以分别对 Java 或 XML 使用 或 use-authorization-manager
。AuthorizationManager
authorizeHttpRequests
改变:
-
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
在 中没有等效的 DSL。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 更容易测试和维护。
因此,没有声明 SPEL 的方法。authorizeHttpRequests
String
相反,您可以实施自己的实现或使用 .AuthorizationManager
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)
}
}
然后,您可以使用 Spring Security 授权原语编写自己的授权原语,如下所示:AuthorizationManager
-
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 及更早版本仅对每个请求执行一次授权。
这意味着默认情况下,像 和 run after 这样的调度程序类型是不安全的。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>
此外,还应为所有 Dispatcher 类型注册 。
如果您使用的是 Spring Boot,则必须更改spring.security.filter.dispatcher-types
属性以包含所有调度程序类型:FilterChainProxy
-
application.properties
spring.security.filter.dispatcher-types=request,async,error,forward,include
如果你使用的是 AbstractSecurityWebApplicationInitializer
,你应该重写该方法并返回所有调度程序类型:getSecurityDispatcherTypes
-
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);
}
}
使用 Spring MVC 时允许FORWARD
如果使用 Spring MVC 解析视图名称,则需要允许请求。
这是因为当 Spring MVC 检测到视图名称和实际视图之间的 Map 时,它将执行对视图的转发。
正如我们在上一节中看到的,Spring Security 6.0 将默认对请求应用授权。FORWARD
FORWARD
请考虑以下常见配置:
-
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();
}
以及以下等效的 MVC 视图映射配置之一:
-
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");
}
}
无论使用哪种配置,当有请求时,Spring MVC 都将执行对视图的转发,该视图在默认配置下位于 path 下。
安全配置允许请求,但所有其他请求都将被拒绝,包括对 下的视图的请求。/login
login
src/main/resources/templates/login.html
/login
FORWARD
/templates/login.html
要解决此问题,您应该将 Spring Security 配置为允许请求:FORWARD
-
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>
替换任何自定义 filter-security sAccessDecisionManager
您的应用程序可能具有自定义的 AccessDecisionManager
或 AccessDecisionVoter
安排。
准备策略将取决于您每次安排的原因。
请继续阅读以找到最适合您情况的方案。
我使用UnanimousBased
如果您的应用程序使用 UnanimousBased
,您应该首先调整或替换任何 s,然后您可以构造一个这样的:AccessDecisionVoter
AuthorizationManager
-
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>
然后,将其连接到 DSL 中,如下所示:
-
Java
-
Kotlin
-
Xml
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization))
// ...
http {
authorizeHttpRequests {
authorize(anyRequest, requestAuthorization)
}
// ...
}
<http authorization-manager-ref="requestAuthorization"/>
|
我使用AffirmativeBased
如果您的应用程序使用 AffirmativeBased
,则可以构造等效的 AuthorizationManager
,如下所示:
-
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>
然后,将其连接到 DSL 中,如下所示:
-
Java
-
Kotlin
-
Xml
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization))
// ...
http {
authorizeHttpRequests {
authorize(anyRequest, requestAuthorization)
}
// ...
}
<http authorization-manager-ref="requestAuthorization"/>
|
我使用ConsensusBased
没有框架提供的 ConsensusBased
等效项。
在这种情况下,请实现一个复合 AuthorizationManager,该复合 AuthorizationManager
将 delegate 集考虑在内。AuthorizationManager
实施后,请按照参考手册中的详细信息添加自定义 AuthorizationManager
。AuthorizationManager
我使用自定义AccessDecisionVoter
您应该更改类以实现 AuthorizationManager
或创建适配器。
如果不知道自定义选民在做什么,就不可能推荐通用解决方案。
不过,通过示例,以下是调整 SecurityMetadataSource
和 AccessDecisionVoter
的情况:anyRequest().authenticated()
-
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
}
}
实施后,请按照参考手册中的详细信息添加自定义 AuthorizationManager
。AuthorizationManager
如果使用 Replace withhasRole
hasAuthority
GrantedAuthorityDefaults
目前,里面的方法不像 那样支持 bean。
因此,如果要更改角色的前缀,则需要使用 而不是 。hasRole
authorizeHttpRequests
GrantedAuthorityDefaults
authorizeRequests
GrantedAuthorityDefaults
hasAuthority
hasRole
例如,您必须从:
@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_");
}
自:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().hasAuthority("MYPREFIX_ADMIN")
);
return http.build();
}
这将在未来应该支持,有关更多详细信息,请参阅 gh-13227。
选择退出步骤
如果您遇到问题,请查看以下方案,以实现最佳选择退出行为:
我无法保护所有 Dispatcher 类型
如果您无法保护所有 Dispatcher 类型,请首先尝试声明哪些 Dispatcher 类型不应需要授权,如下所示:
-
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>
或者,如果这不起作用,那么您可以通过设置 和 来显式选择退出该行为:filter-all-dispatcher-types
filterAllDispatcherTypes
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>
或者,如果您仍在使用 或 ,请设置为 :authorizeRequests
use-authorization-manager="false"
oncePerRequest
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>
我无法为所有请求声明授权规则
如果您在设置授权规则 时遇到问题,请改用 permitAll
,如下所示:anyRequest
denyAll
-
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>
我无法迁移我的 SpEL 或我的AccessDecisionManager
如果您在使用 SpEL 时遇到问题,或者需要在 或 中继续使用其他一些功能,请尝试以下操作。AccessDecisionManager
<http>
authorizeRequests
首先,如果您仍然需要,欢迎您继续使用它。尽管它已被弃用,但 6.0 中并未将其删除。authorizeRequests
其次,如果您仍然需要您的自定义或有其他原因选择退出,请执行:access-decision-manager-ref
AuthorizationManager
<http use-authorization-manager="false">
<intercept-url pattern="/app/*" access="hasRole('APP')"/>
<!-- ... -->
</http>
您可能已经定义了一个您满意的规则,在这种情况下,可以跳过此步骤。 |
通过 IP 地址进行保护一开始就非常脆弱。
因此,没有计划将此支持移植到 。authorizeHttpRequests |
|
|