转载

Spring Security(17)——基于方法的权限控制

基于方法的权限控制

目录

1.1      intercept-methods 定义方法权限控制

1.2      使用 pointcut 定义方法权限控制

1.3      使用注解定义方法权限控制

1.3.1     JSR-250 注解

1.3.2     @Secured 注解

1.3.3     支持表达式的注解

1.4      方法权限控制的拦截器

1.4.1     MethodSecurityInterceptor

1.4.2     AspectJMethodSecurityInterceptor

之前介绍的都是基于 URL 的权限控制, Spring Security 同样支持对于方法的权限控制。可以通过 intercept-methods 对某个 bean 下面的方法进行权限控制,也可以通过 pointcut 对整个 Service 层的方法进行统一的权限控制,还可以通过注解定义对单独的某一个方法进行权限控制。

1.1      intercept-methods 定义方法权限控制

intercept-methods 是需要定义在 bean 元素下的,通过它可以定义对当前的 bean 的某些方法进行权限控制,具体方法是使用其下的子元素 protect 进行定义的。 protect 元素需要指定两个属性, access method method 表示需要拦截的方法名称,可以使用通配符, access 表示执行对应的方法需要拥有的权限,多个权限之间可以使用逗号分隔。

< bean id = "userService" class = "com.xxx.service.impl.UserServiceImpl" >

< security:intercept-methods >

< security:protect access = "ROLE_USER" method = "find*" />

< security:protect access = "ROLE_ADMIN" method = "add*" />

< security:protect access = "ROLE_ADMIN" method = "update*" />

< security:protect access = "ROLE_ADMIN" method = "delete*" />

</ security:intercept-methods >

</ bean >

在上面的配置中表示在执行 UserServiceImpl 的方法名以 find 开始的方法时需要当前用户拥有 ROLE_USER 的权限,在执行方法名以 add update delete 开始的方法时需要拥有 ROLE_ADMIN 的权限。当访问被拒绝时还是交由 ExceptionTranslationFilter 处理,这也就意味着如果用户未登录则会引导用户进行登录,否则默认将返回 403 错误码到客户端。

1.2      使用 pointcut 定义方法权限控制

基于 pointcut 的方法权限控制是通过 global-method-security 下的 protect-pointcut 来定义的。可以在 global-method-security 元素下定义多个 protect-pointcut 以对不同的 pointcut 使用不同的权限控制。

< security:global-method-security >

< security:protect-pointcut access = "ROLE_READ" expression = "execution(* com.elim.*..*Service.find*(..))" />

< security:protect-pointcut access = "ROLE_WRITE" expression = "execution(* com.elim.*..*Service.*(..))" />

</ security:global-method-security >

上面的定义表示我们在执行 com.elim 包或其子包下任意以 Service 结尾的类,其方法名以 find 开始的所有方法时都需要用户拥有 ROLE_READ 的权限,对于 com.elim 包或其子包下任意以 Service 结尾的类的其它方法在执行时都需要 ROLE_WRITE 的权限。需要注意的是对应的类需要是定义在 ApplicationContext 中的 bean 才行。此外同对于 URL 的权限控制一样,当定义多个 protect-pointcut 时更具有特性的应当先定义,因为在 pointcut 匹配的时候是按照声明顺序进行匹配的,一旦匹配上了后续的将不再进行匹配了。

1.3      使用注解定义方法权限控制

基于注解的方法权限控制也是需要通过 global-method-security 元素定义来进行启用的。 Spring Security 在方法的权限控制上支持三种类型的注解, JSR-250 注解、 @Secured 注解和支持表达式的注解。这三种注解默认都是没有启用的,需要单独通过 global-method-security 元素的对应属性进行启用。

1.3.1   JSR-250注解

要使用 JSR-250 注解,首先我们需要通过设置 global-method-security 元素的 jsr250-annotation=”enabled” 来启用基于 JSR-250 注解的支持,默认为 disabled

<security:global-method-security jsr250-annotations= "enabled" />

此外,还需要确保添加了 jsr250-api 到我们的类路径下。之后就可以在我们的 Service 方法上使用 JSR-250 注解进行权限控制了。

@Service

@RolesAllowed ( "ROLE_ADMIN" )

public  class UserServiceImpl implements UserService {

public  void addUser(User user) {

System. out .println( "addUser................" + user);

}

public  void updateUser(User user) {

System. out .println( "updateUser.............." + user);

}

@RolesAllowed ({ "ROLE_USER" , "ROLE_ADMIN" })

public User find( int id) {

System. out .println( "find user by id............." + id);

return  null ;

}

public  void delete( int id) {

System. out .println( "delete user by id................" );

}

@RolesAllowed ( "ROLE_USER" )

public List<User> findAll() {

System. out .println( "find all user..............." );

return  null ;

}

}

上面的代码表示执行 UserServiceImpl 里面所有的方法都需要角色 ROLE_ADMIN ,其中 findAll() 方法的执行需要 ROLE_USER 角色,而 find() 方法的执行对于 ROLE_USER 或者 ROLE_ADMIN 角色都可以。

顺便介绍一下 JSR-250 中对权限支持的注解。

RolesAllowed 表示访问对应方法时所应该具有的角色。其可以标注在类上,也可以标注在方法上,当标注在类上时表示其中所有方法的执行都需要对应的角色,当标注在方法上表示执行该方法时所需要的角色,当方法和类上都使用了 @RolesAllowed 进行标注,则方法上的 @RolesAllowed 将覆盖类上的 @RolesAllowed ,即方法上的 @RolesAllowed 将对当前方法起作用。 @RolesAllowed 的值是由角色名称组成的数组。

PermitAll 表示允许所有的角色进行访问,也就是说不进行权限控制。 @PermitAll 可以标注在方法上也可以标注在类上,当标注在方法上时则只对对应方法不进行权限控制,而标注在类上时表示对类里面所有的方法都不进行权限控制。( 1 )当 @PermitAll 标注在类上,而 @RolesAllowed 标注在方法上时则按照 @RolesAllowed 将覆盖 @PermitAll ,即需要 @RolesAllowed 对应的角色才能访问。( 2 )当 @RolesAllowed 标注在类上,而 @PermitAll 标注在方法上时则对应的方法也是不进行权限控制的。( 3 )当在方法上同时使用了 @PermitAll @RolesAllowed 时先定义的将发生作用,而都定义在类上时则是反过来的,即后定义的将发生作用(这个没多大的实际意义,实际应用中不会有这样的定义)。

DenyAll 是和 PermitAll 相反的,表示无论什么角色都不能访问。 @DenyAll 只能定义在方法上。你可能会有疑问使用 @DenyAll 标注的方法无论拥有什么权限都不能访问,那还定义它干啥呢?使用 @DenyAll 定义的方法只是在我们的权限控制中不能访问,脱离了权限控制还是可以访问的。

1.3.2  @Secured注解

@Secured 是由 Spring Security 定义的用来支持方法权限控制的注解。它的使用也是需要启用对应的支持才会生效的。通过设置 global-method-security 元素的 secured-annotations=”enabled” 可以启用 Spring Security 对使用 @Secured 注解标注的方法进行权限控制的支持,其值默认为 disabled

< security:global-method-security secured-annotations = "enabled" />

@Service

public  class UserServiceImpl implements UserService {

@Secured ( "ROLE_ADMIN" )

public  void addUser(User user) {

System. out .println( "addUser................" + user);

}

@Secured ( "ROLE_USER" )

public List<User> findAll() {

System. out .println( "find all user..............." );

return  null ;

}

}

在上面的代码中我们使用 @Secured 定义了只有拥有 ROLE_ADMIN 角色的用户才能调用方法 addUser() ,只有拥有 ROLE_USER 角色的用户才能调用方法 findAll()

1.3.3   支持表达式的注解

Spring Security 中定义了四个支持使用表达式的注解,分别是 @PreAuthorize @PostAuthorize @PreFilter @PostFilter 。其中前两者可以用来在方法调用前或者调用后进行权限检查,后两者可以用来对集合类型的参数或者返回值进行过滤。要使它们的定义能够对我们的方法的调用产生影响我们需要设置 global-method-security 元素的 pre-post-annotations=”enabled” ,默认为 disabled

< security:global-method-security pre-post-annotations = "disabled" />

使用 @PreAuthorize @PostAuthorize 进行访问控制

@PreAuthorize 可以用来控制一个方法是否能够被调用。

@Service

public  class UserServiceImpl implements UserService {

@PreAuthorize ( "hasRole('ROLE_ADMIN')" )

public  void addUser(User user) {

System. out .println( "addUser................" + user);

}

@PreAuthorize ( "hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')" )

public User find( int id) {

System. out .println( "find user by id............." + id);

return  null ;

}

}

在上面的代码中我们定义了只有拥有角色 ROLE_ADMIN 的用户才能访问 adduser() 方法,而访问 find() 方法需要有 ROLE_USER 角色或 ROLE_ADMIN 角色。使用表达式时我们还可以在表达式中使用方法参数。

public  class UserServiceImpl implements UserService {

/**

* 限制只能查询 Id 小于 10 的用户

*/

@PreAuthorize ( "#id<10" )

public User find( int id) {

System. out .println( "find user by id........." + id);

return  null ;

}

/**

* 限制只能查询自己的信息

*/

@PreAuthorize ( "principal.username.equals(#username)" )

public User find(String username) {

System. out .println( "find user by username......" + username);

return  null ;

}

/**

* 限制只能新增用户名称为 abc 的用户

*/

@PreAuthorize ( "#user.name.equals('abc')" )

public  void add(User user) {

System. out .println( "addUser............" + user);

}

}

在上面代码中我们定义了调用 find(int id) 方法时,只允许参数 id 小于 10 的调用;调用 find(String username) 时只允许 username 为当前用户的用户名;定义了调用 add() 方法时只有当参数 user name abc 时才可以调用。

有时候可能你会想在方法调用完之后进行权限检查,这种情况比较少,但是如果你有的话, Spring Security 也为我们提供了支持,通过 @PostAuthorize 可以达到这一效果。使用 @PostAuthorize 时我们可以使用内置的表达式 returnObject 表示方法的返回值。我们来看下面这一段示例代码。

@PostAuthorize ( "returnObject.id%2==0" )

public User find( int id) {

User user = new User();

user.setId(id);

return user;

}

上面这一段代码表示将在方法 find() 调用完成后进行权限检查,如果返回值的 id 是偶数则表示校验通过,否则表示校验失败,将抛出 AccessDeniedException        需要注意的是 @PostAuthorize 是在方法调用完成后进行权限检查,它不能控制方法是否能被调用,只能在方法调用完成后检查权限决定是否要抛出 AccessDeniedException

使用 @PreFilter @PostFilter 进行过滤

使用 @PreFilter @PostFilter 可以对集合类型的参数或返回值进行过滤。使用 @PreFilter @PostFilter 时, Spring Security 将移除使对应表达式的结果为 false 的元素。

@PostFilter ( "filterObject.id%2==0" )

public List<User> findAll() {

List<User> userList = new ArrayList<User>();

User user;

for ( int i=0; i<10; i++) {

user = new User();

user.setId(i);

userList.add(user);

}

return userList;

}

上述代码表示将对返回结果中 id 不为偶数的 user 进行移除。 filterObject 是使用 @PreFilter @PostFilter 时的一个内置表达式,表示集合中的当前对象。当 @PreFilter 标注的方法拥有多个集合类型的参数时,需要通过 @PreFilter filterTarget 属性指定当前 @PreFilter 是针对哪个参数进行过滤的。如下面代码就通过 filterTarget 指定了当前 @PreFilter 是用来过滤参数 ids 的。

@PreFilter (filterTarget= "ids" , value= "filterObject%2==0" )

public  void delete(List<Integer> ids, List<String> usernames) {

...

}

1.4      方法权限控制的拦截器

关于方法权限控制, Spring Security 提供了两类 AbstractSecurityInterceptor ,基于 AOP Alliance MethodSecurityInterceptor ,和基于 Aspectj 继承自 MethodSecurityInterceptor AspectJMethodSecurityInterceptor

1.4.1   MethodSecurityInterceptor

当我们在使用基于 NameSpace 进行方法保护的配置时, Spring Security 默认配置的就是 MethodSecurityInterceptor 。根据配置的不同,一个拦截器可能只是针对于一个 bean ,也可能是针对于多个 bean 的。 MethodSecurityInterceptor 使用一个 MethodSecurityMetadataSource 的实例来获取特定方法调用配置的 ConfigAttribute 。当我们在 ApplicationContext 配置文件中使用 intercept-methods 元素或 protect-point 元素定义需要保护的方法调用时, Spring Security 内部默认会使用一个 MapBasedMethodSecurityMetadataSource 来保存在这些元素上定义的配置信息,保存的 key 是对应的方法名(可以是含有通配符的)。类似的使用 JSR-250 注解时将使用 Jsr250MethodSecurityMetadataSource 解析配置属性;使用 @Secured 注解时将使用 SecuredAnnotationSecurityMetadataSource 解析配置属性;使用 pre-post-annotations 时将使用 PrePostAnnotationSecurityMetadataSource 解析配置属性。

MethodSecurityInterceptor 是实现了 MethodInterceptor 接口的,所以我们在使用 Spring Aop 时,可以自己配置一个 MethodSecurityInterceptor bean

<!-- 自定义 MethodSecurityInterceptor -->

< bean id = "methodSecurityInterceptor"

class = "org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor" >

< property name = "authenticationManager" ref = "authenticationManager" />

< property name = "accessDecisionManager" ref = "accessDecisionManager" />

< property name = "afterInvocationManager" ref = "afterInvocationManager" />

< property name = "securityMetadataSource" >

< security:method-security-metadata-source >

<!-- 指定需要受保护的方法和需要的权限 -->

< security:protect method = "com.xxx.service.UserService.find*"

access = "ROLE_USER" />

< security:protect method = "com.xxx.service.UserService.delete*"

access = "ROLE_ADMIN" />

</ security:method-security-metadata-source >

</ property >

</ bean >

定义了 MethodSecurityInterceptor 以后,我们需要类似 AOP 配置那样,配置哪些该 MethodInterceptor 需要拦截哪些方法的执行。这种可选配置是很多种的,因为我们这里只是拦截 UserService 中的具体方法,所以就采用基于 bean name 的自动代理。

<!-- 基于 bean 的拦截 -->

< bean class = "org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" >

< property name = "interceptorNames" >

< list >

< value > methodSecurityInterceptor </ value >

</ list >

</ property >

< property name = "beanNames" >

< list >

< value > userService </ value >

</ list >

</ property >

</ bean >

按照上面的配置,我们在访问 UserService find 方法时就需要 ROLE_USER 的权限,而访问 delete 方法时则需要 ROLE_ADMIN 权限。

1.4.2   AspectJMethodSecurityInterceptor

AspectJMethodSecurityInterceptor 是继承自 MethodSecurityInterceptor 的,不同的是 AspectJMethodSecurityInterceptor 是用来支持 AspectJ JointPoint 的,但在底层还是会把它封装成一个 MethodInvocation 进行调用。

(注:本文是基于 Spring Security3.1.6 所写)

正文到此结束
Loading...