安全顶部

我刚宣布了新的学习春天安全课程,包括全部材料专注于春季安全的新OAuth2堆栈5:

>>查看课程

1.介绍

简单地,Spring Security在方法级别支持授权语义。

通常,我们可以通过例如限制哪些角色能够执行特定方法的角色来保护我们的服务层 - 并使用专用的方法级安全测试支持来测试它。

在本文中,我们首先要查看一些安全注释的使用。然后,我们将专注于使用不同的策略测试我们的方法安全性。

进一步阅读:

春季表达语言指南

本文将探索Spring表达式语言(SpEL),这是一种强大的表达式语言,支持在运行时查询和操作对象图。

具有Spring Security的自定义安全性表达式

创建具有Spring Security的新的自定义安全性表达的指南,然后使用具有前后授权注释的新表达式。

2.启用方法安全性

首先,要使用Spring方法安全性,我们需要添加Spring-Security-Config依赖:

<依赖项>  org.springframework.security   Spring-Security-Config  

我们可以在上面找到它的最新版本Maven Central.

如果我们想使用春天启动,我们可以使用春天启动 - 启动器 - 安全依赖包括Spring-Security-Config

<依赖项>  org.springframework.boot   Spring-Boot-Starter-Security  

再次,可以找到最新版本Maven Central.

接下来,我们需要启用全局方法安全性:

@configuration @enableglobalmethodsecurity(prepostenabled = true,securedenabled = true,jsr250enabled = true)公共类方法ecurityconfig扩展了globalmethodsecurityConfiguration {}
  • prepostenabled.属性启用Spring Security Pre / Post Annotations
  • ecuredEnabled.财产确定是否存在@Secured.应启用注释
  • JSR250Enabled.属性允许我们使用@RoleAllowed注解

我们将在下一节中详细介绍这些注释。金宝搏官网188be

3.应用方法安全

3.1。使用@Secured.注解

@Secured.注释用于指定方法上的角色列表。因此,如果她有至少一个指定的角色,则用户只能访问该方法。

让我们定义一个getusername.方法:

@secured(“角色_Viewer”)公钥字符串getUsername(){securityContext SecurityContext = SecurityContextholter.getContext();return securitycontext.getauthentication()。getName();}

在这里,这是@Secured(“角色_Viewer”)注释定义只有具有角色的用户角色_Viewer.能否执行getusername.方法。

对象中定义一个角色列表@Secured.注解:

@Secured({“角色_Viewer”,“角色_Editor”})公共布尔IsvalidUserName(String Username){return userrererepository.isvalidUserName(用户名);}

在这种情况下,配置指出,如果用户要么角色_Viewer.或者角色_ELIEDITOR.,该用户可以调用IsValidUsername.方法。

@Secured.注释不支持Spring表达语言(Spel)。

3.2。使用@RoleAllowed注解

@RoleAllowed类的等效注释@Secured.注解

基本上,我们可以用@RoleAllowed注释的类似方式@Secured.。因此,我们可以重新定义getusername.IsValidUsername.方法:

@ROOLEALLEALED(“角色_VIEWER”)public string getusername2(){// ...} @ROOLEALLEDED({“角色_VIEWER”,“角色_EDITOR”})公共布尔ISValiduserName2(字符串用户名){// ...}

同样,只有具有角色的用户角色_Viewer.可以执行getUsername2

同样,用户能够调用isvalidusername2.除非她至少有一个角色_Viewer.或者Roler_Editor.角色。

3.3。使用@Preauthorize.@PostAuthorize注释

两个都@Preauthorize.@PostAuthorize注解提供了基于表达式的访问控制。因此,可以使用谓词使用Spel(春季表达语言)

@Preauthorize.注释在进入方法之前检查给定的表达式, 然而,@PostAuthorize注释在执行方法后验证它,可以改变结果

现在,让我们宣布一个getusernameinuppercase.方法如下:

@preacuthorize(“hasrole('role_viewer')”)public string getusernameinuppercase(){return getusername()。touppercase();}

@preacuthorize(“hasrole('角色_viewer')”)具有与之相同的含义@Secured(“角色_Viewer”)我们在上一节中使用的。随意发现更多安全表达式的详细信息在前面的文章中

因此,注释@Secured({“rom_viewer”,“角色_Editor”})可以替换@preacuthorize(“hasrole('角色_viewer')或hasrole('role_editor')”):

@preauthorize(“hasrole('rome_viewer')或hasrole('role_editor')”)public boolean isvalidusername3(字符串用户名){// ...}

而且,实际上,我们可以将方法参数作为表达式的一部分使用

@PreAuthorize("#username == authentication.principal.username") public String getMyRoles(String username){//…}

在这里,用户可以调用Getmyroles.方法的参数值用户名与当前主体的用户名相同。

值得注意的是@Preauthorize.表达式可以替换@PostAuthorize

让我们重写Getmyroles.

@postauthorize(“#username ==身份验证.principal.username”)public string getmyroles2(字符串用户名){// ...}

然而,在前面的示例中,授权将在目标方法执行之后延迟。

此外,@PostAuthorize注释提供了访问方法结果的能力

@PostAuthorize(“returnObject。用户名== authentication.principal.nickName") public CustomUser loadUserDetail(String username) { return userRoleRepository.loadUserByUserName(username); }

在这个例子中,loadUserDetail方法只能成功执行用户名回归定制是否等于当前身份验证主体的昵称。

在本节中,我们主要使用简单的春季表达式。对于更复杂的情景,我们可以创建自定义安全表达式

3.4。使用@prefilter.@Postfilter.注释

春天安全提供@prefilter.注释在执行该方法之前过滤集合参数

@prefilter(“filterObject!= Authentication.principal.Username”)公共字符串JoinUserName(list  username){return username.stream()。收集(收集器.Joining(“;”));}

在本例中,除了经过身份验证的用户外,我们将连接所有用户名。

这里,在我们的表达式中,我们使用名称filterObject.表示集合中的当前对象

但是,如果该方法具有多个参数,这是一个集合类型,我们需要使用filterTarget属性要指定我们要过滤的哪个参数:

@prefilter(value =“filterObject!= Authentication.principal.Username”,filtertarget =“用户名”)公共字符串JojuserNamesandRoles(list 用户名,列表角色){return usernames.stream()。收集(收藏家。加入(“;”))+“:”+ roles.stream()。收集(收集器.Joining(“;”));}

此外,我们还可以通过使用来过滤返回的方法集合@Postfilter.注解

@PostFilter(“过滤器object!= Authentication.principal.username”)公用列表 getallusernamesexceptcurrent(){return userrereEpository.getAlluserNames();}

在这种情况下,名称filterObject.指返回集合中的当前对象。

使用该配置,Spring Security将遍历返回的列表,并删除与主体用户名匹配的任何值。

Spring Security - @PreFilter和@PostFilter文章更详细地描述了两个注释。

3.5。方法安全元注释

我们通常在我们使用相同的安全配置保护不同方法的情况下找到自己。

在这种情况下,我们可以定义安全元注释:

@target(mextorytype.method)@retention(RetentionPolicy.Runtime)@preauthorize(“hasrole('查看器')”)public @interface iSviewer {}

接下来,我们可以直接使用@isViewer注释来保护我们的方法:

@isviewer public string getusername4(){// ...}

安全性元注释是一个很好的主意,因为他们添加了更多的语义和从安全框架中解耦了我们的业务逻辑。

3.6。类级别的安全注释

如果我们发现自己在一类中的每个方法使用相同的安全注释,我们可以考虑将该注释放在课堂级别:

@service @preacurethorize(“hasrole('角色_admin')”)公共类systemservice {public string getsystemyear(){// ...} public string getsystemdate(){// ...}}

在上面的例子中,安全规则hasRole(“ROLE_ADMIN”)将适用于两者getsystemyear.getSystemDate方法。

3.7。方法上的多个安全注释

我们还可以在一个方法上使用多个安全注释:

@produthorize(“#username == authentication.principal.username”)@postaututorize(“returnobject.username == authentication.principal.nickname”)公共companyuser securedloaduserdetail(String Username){return userrererepository.loaduserbyusername(用户名);}

因此,春天将在执行之前和之后验证授权securedloaduserdetail.方法。

4.重要的注意事项

我们希望提醒方法安全性:

  • 默认情况下,Spring AOP代理用于应用方法安全性 -如果在同一类别内的另一种方法调用安全方法A,则完全忽略A中的安全性。这意味着方法a将在没有任何安全检查的情况下执行。这同样适用于私人方法
  • 春天SecurityContext.是线装书,默认情况下,安全上下文不会传播到子线程。要了解更多信息,我们可以参考Spring安全上下文传播文章

5.安全试验方法

5.1。配置

用junit测试春天安全性,我们需要春天安全测试依赖:

<依赖> < groupId > org.springframework。安全< / groupId > < artifactId > spring-security-test < / artifactId > < / >的依赖

我们不需要指定依赖版本,因为我们正在使用Spring Boot插件。我们可以找到此依赖的最新版本Maven Central.

接下来,让我们通过指定运行器和ApplicationContext.配置:

@runwith(springrunner.class)@contextconfiguration公共类方法安全integrationtest {// ...}

5.2。测试用户名和角色

现在我们的配置已准备就绪,让我们尝试测试我们的getusername.我们用的方法@Secured(“角色_Viewer”)注解

@secured(“角色_Viewer”)公钥字符串getUsername(){securityContext SecurityContext = SecurityContextholter.getContext();return securitycontext.getauthentication()。getName();}

既然我们使用@Secured.注释,它要求用户进行身份验证才能调用该方法。否则,我们会得到AuthenticationCredentialsNotFoundException。

因此,我们需要提供用户来测试我们的安全方法。为实现这一目标,我们用来装饰测试方法@withmockuser.并提供用户和角色:

@test @withmockuser(username =“John”,角色= {“Viewer”})public voidedroleviewer_whencallgetusername_thenreturnusername(){string username = userroloerervice.getusername();assertequal(“约翰”,用户名);}

我们提供了一个已验证的用户,其用户名是约翰谁的角色是角色_Viewer.。如果我们没有说明用户名或者角色,默认的用户名用户和默认值角色角色_User.

注意,没有必要添加角色_在此处的前缀,Spring Security将自动添加该前缀。

如果我们不想用这个前缀,我们可以考虑使用权威代替角色。

例如,我们声明agetusernameinlowercase.方法:

@preacuthorize(“hasauthority('sys_admin')”)public string getusernamelc(){return getusername()。tolowercase();}

我们可以使用权威来验证这一点:

@test @withmockuser(Username =“John”,Airstrities = {“sys_admin”})public void gotauthoritysysadmin_whencallgetusernamelc_thenreturnusername(){string username = userroluerervice.getusernameinlowercase();assertequals(“约翰”,用户名);}

方便,如果我们想在许多测试用例中使用同一个用户,我们可以声明@withmockuser.测试类的注释

@runwith(springrunner.class)@contextconfiguration @withmockuser(username =“John”,角色= {“Viewer”})公共类MockUserAtClassLevelIntegrationTest {// ...}

如果我们想将我们的测试运行为匿名用户,我们可以使用@withanononamoususer.注解:

@Test(expected = AccessDeniedException.class) @WithAnonymousUser public void givenAnomynousUser_whenCallGetUsername_thenAccessDenied() {userRoleService.getUsername();}

在上面的例子中,我们期望AccessDeniedException.因为匿名用户没有被授予角色角色_Viewer.或权威SYS_ADMIN

5.3。使用自定义测试userdetailsservice.

对于大多数应用程序,通常使用自定义类作为身份验证主体。在这种情况下,自定义类需要实现org.springframework.security.core.userdetails。userdetails.接口。

在本文中,我们宣布了一个定制延长现有实施的班级userdetails.,这是org.springframework.security.core.userdetails。用户:

公共类Codatuser扩展用户{私有字符串昵称;// getter和setter}

让我们用这个例子@PostAuthorize第三节注释:

@PostAuthorize(“returnObject。用户名== authentication.principal.nickName") public CustomUser loadUserDetail(String username) { return userRoleRepository.loadUserByUserName(username); }

在这种情况下,该方法只会成功执行用户名回归定制是否等于当前身份验证主体的昵称

如果我们想测试该方法,我们可以提供实施userdetailsservice.可以载入我们的定制基于用户名

@Test @WithUserDetails(value =“John”,userdetailsservicebeanname =“userdetailservice”)public void whenjohn_callloaduserdetail_thenok(){companyuser user = userservice.loaduserdetail(“简”);assertequals(“简”,user.getnickname());}

在这里,这是@withuserdetails.注释表示我们将使用userdetailsservice.初始化经过身份验证的用户。对象引用该服务userDetailsServiceBeanName财产userdetailsservice.出于测试目的,可能是一个真实的实现,也可能是一个虚假的实现。

此外,该服务将使用属性的值价值作为加载的用户名userdetails.

方便地,我们也可以用一个装饰@withuserdetails.在课程级别的注释,类似于我们所做的@withmockuser.注解

5.4。使用Meta注释测试

我们经常发现自己在各种测试中一遍又一遍地重新使用相同的用户/角色。

对于这些情况,创建一个元注释

回到前面的例子@withmockuser(username =“John”,角色= {“查看器”}),我们可以声明元注释为:

@Retention(RetentionPolicy.RUNTIME) @WithMockUser(value = "john", roles = "VIEWER") public @interface WithMockJohnViewer {}

然后我们可以简单地使用@withmockjohnviewer.在我们的测试中:

@Test @WithMockJohnViewer public void givenMockedJohnViewer_whenCallGetUsername_thenReturnUsername() {String userName = userRoleService.getUsername();assertequal(“约翰”,用户名);}

同样,我们可以使用元注释来创建特定于领域的用户@withuserdetails.

六,结论

在本教程中,我们已经探讨了在Spring Security中使用方法安全性的各种选项。

我们还介绍了一些轻松测试方法安全性的技术,并学习了如何在不同的测试中重用mock用户。

可以找到本教程的所有示例在Github

安全底

我刚宣布了新的学习春天安全课程,包括全部材料专注于春季安全的新OAuth2堆栈5:

>>查看课程
对这篇文章的评论关闭!