# 方法安全性
从版本 2.0 开始 Spring,安全性大大提高了对向服务层方法添加安全性的支持。它提供了对 JSR-250 注释安全性的支持,以及框架的原始@Secured
注释。从 3.0 开始,你还可以使用 New基于表达式的注释。你可以对单个 Bean 应用安全性,使用intercept-methods
元素来装饰 Bean 声明,或者可以使用 AspectJ 样式的切入点在整个服务层中保护多个 bean。
# EnableMethodSecurity
在 Spring Security5.6 中,我们可以在任何@Configuration
实例上使用@EnableMethodSecurity
注释来启用基于注释的安全性。
这在许多方面改进了@启用全球方法安全
。@EnableMethodSecurity
:
使用简化的
AuthorizationManager
API,而不是元数据源、配置属性、决策管理器和投票者。这简化了重用和定制。支持基于直接 Bean 的配置,而不是要求扩展
GlobalMethodSecurityConfiguration
来定制 bean是使用本机 Spring AOP 构建的,删除了抽象,并允许你使用 Spring AOP 构建块来自定义
检查相互冲突的注释,以确保安全配置的明确性.
符合 JSR-250
默认情况下启用
@PreAuthorize
、@PostAuthorize
、@PreFilter
和@PostFilter
对于更早的版本,请阅读@enableGlobalMethodSecurity 的类似支持。 |
---|
例如,下面将启用 Spring Security 的@PreAuthorize
注释:
例 1。方法安全配置
爪哇
@EnableMethodSecurity
public class MethodSecurityConfig {
// ...
}
Kotlin
@EnableMethodSecurity
class MethodSecurityConfig {
// ...
}
XML
<sec:method-security/>
然后,向方法(在类或接口上)添加注释将相应地限制对该方法的访问。 Spring Security 的本机注释支持为该方法定义了一组属性。这些将传递给DefaultAuthorizationMethodInterceptorChain
,以便它做出实际的决定:
例 2。方法安全注释的使用
爪哇
public interface BankService {
@PreAuthorize("hasRole('USER')")
Account readAccount(Long id);
@PreAuthorize("hasRole('USER')")
List<Account> findAccounts();
@PreAuthorize("hasRole('TELLER')")
Account post(Account account, Double amount);
}
Kotlin
interface BankService {
@PreAuthorize("hasRole('USER')")
fun readAccount(id : Long) : Account
@PreAuthorize("hasRole('USER')")
fun findAccounts() : List<Account>
@PreAuthorize("hasRole('TELLER')")
fun post(account : Account, amount : Double) : Account
}
你可以使用以下方法启用对 Spring Security 的@Secured
注释的支持:
例 3。@Secured Configuration
爪哇
@EnableMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
// ...
}
Kotlin
@EnableMethodSecurity(securedEnabled = true)
class MethodSecurityConfig {
// ...
}
XML
<sec:method-security secured-enabled="true"/>
或 JSR-250 使用:
例 4。JSR-250 配置
爪哇
@EnableMethodSecurity(jsr250Enabled = true)
public class MethodSecurityConfig {
// ...
}
Kotlin
@EnableMethodSecurity(jsr250Enabled = true)
class MethodSecurityConfig {
// ...
}
XML
<sec:method-security jsr250-enabled="true"/>
# 自定义授权
Spring Security 的@PreAuthorize
、@PostAuthorize
、@PreFilter
和@PostFilter
提供了丰富的基于表达式的支持。
如果需要自定义表达式的处理方式,可以公开一个自定义MethodSecurityExpressionHandler
,如下所示:
例 5。自定义方法 SecurityExpressionHandler
爪哇
@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
handler.setTrustResolver(myCustomTrustResolver);
return handler;
}
Kotlin
companion object {
@Bean
fun methodSecurityExpressionHandler() : MethodSecurityExpressionHandler {
val handler = DefaultMethodSecurityExpressionHandler();
handler.setTrustResolver(myCustomTrustResolver);
return handler;
}
}
XML
<sec:method-security>
<sec:expression-handler ref="myExpressionHandler"/>
</sec:method-security>
<bean id="myExpressionHandler"
class="org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler">
<property name="trustResolver" ref="myCustomTrustResolver"/>
</bean>
我们使用MethodSecurityExpressionHandler 方法公开static ,以确保 Spring 在初始化 Spring Security 的方法 Security@Configuration 类之前发布它 |
---|
另外,对于基于角色的授权, Spring Security 添加了一个默认的ROLE_
前缀,该前缀在计算hasRole
之类的表达式时使用。
可以通过公开GrantedAuthorityDefaults
Bean 来将授权规则配置为使用不同的前缀,如下所示:
例 6。自定义方法 SecurityExpressionHandler
爪哇
@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("MYPREFIX_");
}
Kotlin
companion object {
@Bean
fun grantedAuthorityDefaults() : GrantedAuthorityDefaults {
return GrantedAuthorityDefaults("MYPREFIX_");
}
}
XML
<sec:method-security/>
<bean id="grantedAuthorityDefaults" class="org.springframework.security.config.core.GrantedAuthorityDefaults">
<constructor-arg value="MYPREFIX_"/>
</bean>
我们使用GrantedAuthorityDefaults 方法公开static ,以确保 Spring 在初始化 Spring Security 的方法 Security@Configuration 类之前发布它 |
---|
# 自定义授权管理器
方法授权是方法授权之前和方法授权之后的组合。
在方法被调用之前执行前方法授权。 如果该授权拒绝访问,则不调用该方法,并抛出一个 AccessDeniedException 后方法授权是在方法被调用之后,但在方法返回调用者之前执行的。 如果该授权拒绝访问,不返回该值,并抛出一个 AccessDeniedException |
---|
要重新创建添加@EnableMethodSecurity
所做的默认操作,你需要发布以下配置:
例 7。完整的 pre-post 方法安全配置
爪哇
@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor preFilterAuthorizationMethodInterceptor() {
return new PreFilterAuthorizationMethodInterceptor();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor preAuthorizeAuthorizationMethodInterceptor() {
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postAuthorizeAuthorizationMethodInterceptor() {
return AuthorizationManagerAfterMethodInterceptor.postAuthorize();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postFilterAuthorizationMethodInterceptor() {
return new PostFilterAuthorizationMethodInterceptor();
}
}
Kotlin
@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun preFilterAuthorizationMethodInterceptor() : Advisor {
return PreFilterAuthorizationMethodInterceptor();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun preAuthorizeAuthorizationMethodInterceptor() : Advisor {
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun postAuthorizeAuthorizationMethodInterceptor() : Advisor {
return AuthorizationManagerAfterMethodInterceptor.postAuthorize();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun postFilterAuthorizationMethodInterceptor() : Advisor {
return PostFilterAuthorizationMethodInterceptor();
}
}
XML
<sec:method-security pre-post-enabled="false"/>
<aop:config/>
<bean id="preFilterAuthorizationMethodInterceptor"
class="org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor"/>
<bean id="preAuthorizeAuthorizationMethodInterceptor"
class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor"
factory-method="preAuthorize"/>
<bean id="postAuthorizeAuthorizationMethodInterceptor"
class="org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor"
factory-method="postAuthorize"/>
<bean id="postFilterAuthorizationMethodInterceptor"
class="org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor"/>
请注意, Spring Security 的方法安全性是使用 Spring AOP 构建的。因此,拦截器是根据指定的顺序调用的。这可以通过在拦截器实例上调用setOrder
进行自定义,如下所示:
例 8。发布自定义顾问
爪哇
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postFilterAuthorizationMethodInterceptor() {
PostFilterAuthorizationMethodInterceptor interceptor = new PostFilterAuthorizationMethodInterceptor();
interceptor.setOrder(AuthorizationInterceptorOrders.POST_AUTHORIZE.getOrder() - 1);
return interceptor;
}
Kotlin
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun postFilterAuthorizationMethodInterceptor() : Advisor {
val interceptor = PostFilterAuthorizationMethodInterceptor();
interceptor.setOrder(AuthorizationInterceptorOrders.POST_AUTHORIZE.getOrder() - 1);
return interceptor;
}
XML
<bean id="postFilterAuthorizationMethodInterceptor"
class="org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor">
<property name="order"
value="#{T(org.springframework.security.authorization.method.AuthorizationInterceptorsOrder).POST_AUTHORIZE.getOrder() -1}"/>
</bean>
你可能希望在应用程序中仅支持@PreAuthorize
,在这种情况下,你可以执行以下操作:
例 9。仅 @preauthorize 配置
爪哇
@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor preAuthorize() {
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
}
}
Kotlin
@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun preAuthorize() : Advisor {
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize()
}
}
XML
<sec:method-security pre-post-enabled="false"/>
<aop:config/>
<bean id="preAuthorizeAuthorizationMethodInterceptor"
class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor"
factory-method="preAuthorize"/>
或者,你可能有一个要添加到列表中的自定义 before-methodAuthorizationManager
。
在这种情况下,你需要告知 Spring 安全性AuthorizationManager
以及你的授权管理器应用到哪些方法和类。
因此,可以将 Spring 安全性配置为在@PreAuthorize
和@PostAuthorize
之间调用AuthorizationManager
,就像这样:
例 10。顾问之前的自定义
爪哇
@EnableMethodSecurity
class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public Advisor customAuthorize() {
JdkRegexpMethodPointcut pattern = new JdkRegexpMethodPointcut();
pattern.setPattern("org.mycompany.myapp.service.*");
AuthorizationManager<MethodInvocation> rule = AuthorityAuthorizationManager.isAuthenticated();
AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(pattern, rule);
interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
return interceptor;
}
}
Kotlin
@EnableMethodSecurity
class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun customAuthorize() : Advisor {
val pattern = JdkRegexpMethodPointcut();
pattern.setPattern("org.mycompany.myapp.service.*");
val rule = AuthorityAuthorizationManager.isAuthenticated();
val interceptor = AuthorizationManagerBeforeMethodInterceptor(pattern, rule);
interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
return interceptor;
}
}
XML
<sec:method-security/>
<aop:config/>
<bean id="customAuthorize"
class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor">
<constructor-arg>
<bean class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value="org.mycompany.myapp.service.*"/>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.authorization.AuthorityAuthorizationManager"
factory-method="isAuthenticated"/>
</constructor-arg>
<property name="order"
value="#{T(org.springframework.security.authorization.method.AuthorizationInterceptorsOrder).PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1}"/>
</bean>
你可以使用AuthorizationInterceptorsOrder 中指定的顺序常量,将拦截器放置在 Spring 安全方法拦截器之间。 |
---|
对于后方法授权也可以这样做。后方法授权通常涉及分析返回值以验证访问。
例如,你可能有一个方法来确认请求的帐户实际上属于登录用户,如下所示:
例 11。@postauthorize 示例
爪哇
public interface BankService {
@PreAuthorize("hasRole('USER')")
@PostAuthorize("returnObject.owner == authentication.name")
Account readAccount(Long id);
}
Kotlin
interface BankService {
@PreAuthorize("hasRole('USER')")
@PostAuthorize("returnObject.owner == authentication.name")
fun readAccount(id : Long) : Account
}
你可以提供自己的AuthorizationMethodInterceptor
以自定义如何计算对返回值的访问。
例如,如果你有自己的自定义注释,那么可以这样配置它:
例 12。自定义后的顾问
爪哇
@EnableMethodSecurity
class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public Advisor customAuthorize(AuthorizationManager<MethodInvocationResult> rules) {
AnnotationMethodMatcher pattern = new AnnotationMethodMatcher(MySecurityAnnotation.class);
AuthorizationManagerAfterMethodInterceptor interceptor = new AuthorizationManagerAfterMethodInterceptor(pattern, rules);
interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
return interceptor;
}
}
Kotlin
@EnableMethodSecurity
class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun customAuthorize(rules : AuthorizationManager<MethodInvocationResult>) : Advisor {
val pattern = AnnotationMethodMatcher(MySecurityAnnotation::class.java);
val interceptor = AuthorizationManagerAfterMethodInterceptor(pattern, rules);
interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
return interceptor;
}
}
XML
<sec:method-security/>
<aop:config/>
<bean id="customAuthorize"
class="org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor">
<constructor-arg>
<bean class="org.springframework.aop.support.annotation.AnnotationMethodMatcher">
<constructor-arg value="#{T(org.mycompany.MySecurityAnnotation)}"/>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.authorization.AuthorityAuthorizationManager"
factory-method="isAuthenticated"/>
</constructor-arg>
<property name="order"
value="#{T(org.springframework.security.authorization.method.AuthorizationInterceptorsOrder).PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1}"/>
</bean>
它将在@PostAuthorize
拦截器之后被调用。
# EnableGlobalMethodSecurity
我们可以在任何@Configuration
实例上使用@EnableGlobalMethodSecurity
注释来启用基于注释的安全性。例如,下面将启用 Spring Security 的@Secured
注释。
爪哇
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
// ...
}
Kotlin
@EnableGlobalMethodSecurity(securedEnabled = true)
open class MethodSecurityConfig {
// ...
}
然后,向方法(在类或接口上)添加注释将相应地限制对该方法的访问。 Spring Security 的本机注释支持为该方法定义了一组属性。这些将被传递给 accessDecisionManager,以便它做出实际的决定:
爪哇
public interface BankService {
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();
@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}
Kotlin
interface BankService {
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
fun readAccount(id: Long): Account
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
fun findAccounts(): Array<Account>
@Secured("ROLE_TELLER")
fun post(account: Account, amount: Double): Account
}
可以使用以下工具启用对 JSR-250 注释的支持
爪哇
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class MethodSecurityConfig {
// ...
}
Kotlin
@EnableGlobalMethodSecurity(jsr250Enabled = true)
open class MethodSecurityConfig {
// ...
}
这些是基于标准的,允许应用简单的基于角色的约束,但不具有 Spring Security 的本机注释的功能。要使用新的基于表达式的语法,你可以使用
爪哇
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}
Kotlin
@EnableGlobalMethodSecurity(prePostEnabled = true)
open class MethodSecurityConfig {
// ...
}
与之对应的 爪哇 代码是
爪哇
public interface BankService {
@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);
@PreAuthorize("isAnonymous()")
public Account[] findAccounts();
@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}
Kotlin
interface BankService {
@PreAuthorize("isAnonymous()")
fun readAccount(id: Long): Account
@PreAuthorize("isAnonymous()")
fun findAccounts(): Array<Account>
@PreAuthorize("hasAuthority('ROLE_TELLER')")
fun post(account: Account, amount: Double): Account
}
# GlobalMethodSecurityConfiguration
有时,你可能需要执行比@EnableGlobalMethodSecurity
允许的注释更复杂的操作。对于这些实例,可以扩展GlobalMethodSecurityConfiguration
,以确保子类上存在@EnableGlobalMethodSecurity
注释。例如,如果希望提供自定义MethodSecurityExpressionHandler
,则可以使用以下配置:
爪哇
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
// ... create and return custom MethodSecurityExpressionHandler ...
return expressionHandler;
}
}
Kotlin
@EnableGlobalMethodSecurity(prePostEnabled = true)
open class MethodSecurityConfig : GlobalMethodSecurityConfiguration() {
override fun createExpressionHandler(): MethodSecurityExpressionHandler {
// ... create and return custom MethodSecurityExpressionHandler ...
return expressionHandler
}
}
有关可以重写的方法的其他信息,请参阅GlobalMethodSecurityConfiguration
爪哇doc。
# <global-method-security>元素
此元素用于在应用程序中启用基于注释的安全性(通过在元素上设置适当的属性),还用于将安全性切入点声明组合在一起,这些声明将在整个应用程序上下文中应用。你应该只声明一个<global-method-security>
元素。以下声明将支持 Spring Security 的@Secured
:
<global-method-security secured-annotations="enabled" />
然后,向方法(在类或接口上)添加注释将相应地限制对该方法的访问。 Spring Security 的本机注释支持为该方法定义了一组属性。这些将被传递到AccessDecisionManager
,以便它做出实际的决定:
Java
public interface BankService {
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();
@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}
Kotlin
interface BankService {
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
fun readAccount(id: Long): Account
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
fun findAccounts(): Array<Account>
@Secured("ROLE_TELLER")
fun post(account: Account, amount: Double): Account
}
可以使用以下工具启用对 JSR-250 注释的支持
<global-method-security jsr250-annotations="enabled" />
这些是基于标准的,允许应用简单的基于角色的约束,但不具有 Spring Security 的本机注释的功能。要使用新的基于表达式的语法,你可以使用
<global-method-security pre-post-annotations="enabled" />
与之对应的 Java 代码是
Java
public interface BankService {
@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);
@PreAuthorize("isAnonymous()")
public Account[] findAccounts();
@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}
Kotlin
interface BankService {
@PreAuthorize("isAnonymous()")
fun readAccount(id: Long): Account
@PreAuthorize("isAnonymous()")
fun findAccounts(): Array<Account>
@PreAuthorize("hasAuthority('ROLE_TELLER')")
fun post(account: Account, amount: Double): Account
}
如果你需要定义简单的规则,而不是根据用户的权限列表检查角色名称,那么基于表达式的注释是一个不错的选择。
注释的方法将只对定义为 Spring bean 的实例(在启用方法安全性的相同应用程序上下文中)进行保护。 如果你想保护不是由 Spring 创建的实例(例如,使用 new 操作符),那么你需要使用 AspectJ。 |
---|
你可以在同一个应用程序中启用多个类型的注释,但是对于任何接口或类只应使用一种类型,因为在其他情况下,行为不会被很好地定义。, 如果发现两个注释适用于特定的方法,然后只应用其中的一个。 |
---|
# 使用 protect-pointcut 添加安全切入点
protect-pointcut
的使用特别强大,因为它允许你只需简单的声明就可以将安全性应用于许多 bean。考虑以下示例:
<global-method-security>
<protect-pointcut expression="execution(* com.mycompany.*Service.*(..))"
access="ROLE_USER"/>
</global-method-security>
这将保护在应用程序上下文中声明的 bean 上的所有方法,这些 bean 的类在com.mycompany
包中,其类名以“service”结尾。只有具有ROLE_USER
角色的用户才能够调用这些方法。与 URL 匹配一样,最特定的匹配必须在切入点列表中排在第一位,因为将使用第一个匹配表达式。安全性注释优先于切入点。