当前位置: 首页 > 科技观察

想要控制好权限,这8个注解你一定要知道!

时间:2023-03-20 11:41:00 科技观察

朋友们都知道宋哥最近在做天津项目。项目中涉及到一个问题,就是数据权限过滤。很多朋友对这个问题特别着迷。希望松哥松哥能完成一篇文章说说,嗯,安排一下。在讲数据权限之前,我们有必要给大家介绍一下SpringSecurity中的权限注解。搞定后,再去TienChin项目的权限注解,你会发现很容易。一、SpringSecurity中的权限注解SpringSecurity支持多种权限注解。首先,我们需要通过@EnableGlobalMethodSecurity注解开启权限注解的使用,如下:@Configuration@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true,jsr250Enabled=true)publicclassSecurityConfigextendsWebSecurityConfigurerAdapter{}在这个注解中,我们设置一共有三个属性:prePostEnabled:表示开启SpringSecurity提供的四个权限注解,@PostAuthorize、@PostFilter、@PreAuthorize和@PreFilter,这四个注解支持Permission表达式,支持SpEL,功能丰富。securedEnabled:开启SpringSecurity提供的@Secured注解,不支持权限表达式。jsr250Enabled:启用JSR-250提供的注解,主要包括@DenyAll、@PermitAll和@RolesAllowed。这些注释不支持权限表达式。上述注解的含义如下:@PostAuthorize:在目标方法执行后进行权限验证。@PostFilter:过滤目标方法执行后方法的返回结果。@PreAuthorize:在目标方法执行之前进行权限验证。@PreFilter:在目标方法执行之前过滤方法参数。@Secured:访问目标方法必须有对应的角色。@DenyAll:拒绝所有访问。@PermitAll:允许所有访问。@RolesAllowed:访问目标方法必须有对应的角色。这就是全部,当然,一般来说,我们只需要设置prePostEnabled=true即可,也就是前四个注解基本可以满足大部分需求。此外,还有一种“老”的方法来配置基于方法的权限管理,就是通过XML文件来配置方法拦截规则。目前很少使用XML文件来配置SpringSecurity,所以这个方法我们就不做过多介绍了。介绍。感兴趣的小伙伴可以查看官网的相关介绍:https://docs.spring.io/spring-security/site/docs/5.4.0/reference/html5/#secure-object-impls。2.权限注解实践下面我们通过几个简单的案例来学习一下以上不同注解的用法。首先创建一个SpringBoot项目,引入Web和SpringSecurity依赖。项目创建完成后,添加如下配置文件:@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true,jsr250Enabled=true)publicclassSecurityConfig{}为了方便,我们将使用单元测试进行验证,所以这里不需要额外配置,只需通过@EnableGlobalMethodSecurity注解启用其他权限注解即可。接下来创建一个User类供后续使用:publicclassUser{privateIntegerid;私有字符串用户名;//省略getter/setter}准备工作完成后,我们来一一解释前面注解的用法。2.1@PreAuthorize@PreAuthorize注解可以在目标方法执行前对其进行安全验证。安全验证时,可以直接使用权限表达式。例如,可以定义如下方法:@ServicepublicclassHelloService{@PreAuthorize("hasRole('ADMIN')")publicStringhello(){return"hello";}}这里使用了权限表达式hasRole,表示执行该方法必须有ADMIN角色才能访问,否则无法访问。接下来,我们在单元测试中测试这个方法:@SpringBootTestclassBasedOnMethodApplicationTests{@AutowiredHelloServicehelloService;@Test@WithMockUser(roles="ADMIN")voidpreauthorizeTest01(){Stringhello=helloService.hello();assertNotNull(你好);assertEquals("你好",你好);}}通过@WithMockUser(roles="ADMIN")注解设置当前执行的用户角色为ADMIN,然后调用helloService中的方法进行测试。如果将用户角色设置为其他角色,单元测试将不会通过。当然这里除了hasRole表达式,还可以使用其他的权限表达式,甚至可以同时使用多个权限表达式,如下所示:@ServicepublicclassHelloService{@PreAuthorize("hasRole('ADMIN')和身份验证.name=='javaboy'")publicStringhello(){return"hello";}}表示访问者的名字必须是javaboy,并且需要有ADMIN角色才能访问这个方法。此时,使用如下代码进行测试:@SpringBootTestclassBasedOnMethodApplicationTests{@AutowiredHelloServicehelloService;@Test@WithMockUser(roles="ADMIN",username="javaboy")voidpreauthorizeTest01(){Stringhello=helloService.hello();assertNotNull(你好);assertEquals("你好",你好);}}在@PreAuthorize注解中,还可以使用#来引用方法的参数,并进行验证。例如下面的方法表示请求者的用户名必须等于方法参数名的值,方法才能执行:@PreAuthorize("authentication.name==#name")publicStringhello(Stringname){return"hello:"+name;}测试方法如下:@Test@WithMockUser(username="javaboy")voidpreauthorizeTest02(){Stringhello=helloService.hello("javaboy");assertNotNull(你好);assertEquals("hello:javaboy",hello);}当模拟的用户名和方法参数相等时,单元测试通过。2.2@PreFilter@PreFilter主要是对方法的请求参数进行过滤,其中包含一个内置对象filterObject来表示要过滤的参数。如果该方法只有一个参数,则内置的filterObject对象代表该参数;如果该方法有多个参数,则需要使用filterTarget指定filterObject代表哪个对象:@PreFilter(value="filterObject.id%2!=0",filterTarget="users")publicvoidaddUsers(Listusers,Integerother){System.out.println("users="+users);}上面代码的意思是过滤方法参数users,保留id为奇数的用户。然后用单元测试测试这个方法:@Test@WithMockUser(username="javaboy")voidpreFilterTest01(){Listusers=newArrayList<>();对于(inti=0;i<10;i++){users.add(newUser(i,"javaboy:"+i));}helloService.addUsers(users,99);}执行单元测试方法,addUsers方法只会打印出id为奇数的用户对象。2.3@PostAuthorize@PostAuthorize是在目标方法执行后进行权限验证。有的朋友可能会觉得奇怪,目标方法执行完之后做权限校验有什么意义呢?其实这个主要用在ACL权限模型中。目标方法执行后,通过@PostAuthorize注解验证目标方法的返回值是否满足相应的权限要求。但是,即使您的权限模型不是ACL,也没关系。您也可以使用此注释。不管怎样,记住它的作用:方法执行后,根据用户的权限信息过滤出需要返回给用户的数据。从技术上讲,@PostAuthorize注解中也可以使用权限表达式,但在实际开发中,权限表达式一般与@PreAuthorize注解结合使用。@PostAuthorize包含一个内置对象returnObject,代表方法的返回值。开发者可以验证返回值:@PostAuthorize("returnObject.id==1")publicUsergetUserById(Integerid){returnnewUser(id,"javaboy");}这意味着返回的用户对象的idmethod必须为1,调用才会顺利通过,否则会抛出异常。然后用单元测试测试这个方法:@Test@WithMockUser(username="javaboy")voidpostAuthorizeTest01(){Useruser=helloService.getUserById(1);assertNotNull(用户);assertEquals(1,user.getId());assertEquals("javaboy",user.getUsername());}如果传入的参数为1,则单元测试成功。2.4@PostFilter@PostFilter注解是在目标方法执行后过滤目标方法的返回结果。该注解包含一个内置对象filterObject,表示目标方法返回的集合/数组中的具体元素:@PostFilter("filterObject.id%2==0")publicListgetAll(){List<用户>users=newArrayList<>();对于(inti=0;i<10;i++){users.add(newUser(i,"javaboy:"+i));}returnusers;}这段代码表示getAll方法的返回值users集合中user对象的id必须为偶数。然后我们通过单元测试进行测试,代码如下:@Test@WithMockUser(roles="ADMIN")voidpostFilterTest01(){Listall=helloService.getAll();assertNotNull(全部);assertEquals(5,all.size());assertEquals(2,all.get(1).getId());}2.5@Secured@Secured注解也是SpringSecurity提供的权限注解。与前面四个注解不同的是,这个注解不支持权限表达式,只能做一些简单的权限描述。@Secured({"ROLE_ADMIN","ROLE_USER"})publicUsergetUserByUsername(Stringusername){returnnewUser(99,username);}此代码表示用户需要具有ROLE_ADMIN或ROLE_USER角色才能访问getUserByUsername方法.然后我们通过单元测试进行测试,代码如下:@Test@WithMockUser(roles="ADMIN")voidsecuredTest01(){Useruser=helloService.getUserByUsername("javaboy");assertNotNull(用户);assertEquals(99,user.getId());assertEquals("javaboy",user.getUsername());}注意角色不需要加ROLE_前缀,系统会自动加。2.6@DenyAll@DenyAll是JSR-250提供的方法注解。看名字就知道是拒绝所有访问:@DenyAllpublicStringdenyAll(){return"DenyAll";}然后我们通过单元测试来测试,代码如下:@Test@WithMockUser(username="javaboy")voiddenyAllTest01(){helloService.denyAll();}单元测试时,会抛出异常。2.7@PermitAll@PermitAll也是JSR-250提供的一种方法注解。看名字就知道是允许所有访问的:@PermitAllpublicStringpermitAll(){return"PermitAll";}然后我们通过单元测试来测试,代码如下:@Test@WithMockUser(username="javaboy")voidpermitAllTest01(){Strings=helloService.permitAll();assertNotNull(s);assertEquals("PermitAll",s);}2.8@RolesAllowed@RolesAllowed也是JSR-250提供的注解可以添加到方法或者类中。当添加到类中时,表示该注解对该类中的所有方法生效;如果类和方法有冲突,将以方法注释为准。我们来看一个简单的案例:@RolesAllowed({"ADMIN","USER"})publicStringrolesAllowed(){return"RolesAllowed";}这意味着访问rolesAllowed方法需要一个ADMIN或USER角色,然后我们通过单元测试来测试它,代码如下:@Test@WithMockUser(roles="ADMIN")voidrolesAllowedTest01(){Strings=helloService.rolesAllowed();assertNotNull(s);assertEquals("RolesAllowed",s);}这是Common方法权限注解。