Spring Security方法级别授权使用介绍
1.简介
简而言之,Spring Security支持方法级别的授权语义。
通常,我们可以通过限制哪些角色能够执行特定方法来保护我们的服务层 - 并使用专用的方法级安全测试支持对其进行测试。
在本文中,我们将首先回顾一些安全注释的使用。然后,我们将专注于使用不同的策略测试我们的方法安全性。
2.启用方法级别的安全授权配置
首先,要使用Spring Method Security,我们需要添加spring-security-config依赖项:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
如果我们想使用Spring Boot,我们可以使用包含spring-security-config的spring-boot-starter-security依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
接下来,我们需要启用全局方法级别授权安全性:
@Configuration
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class MethodSecurityConfig
extends GlobalMethodSecurityConfiguration {
}
- prePostEnabled属性启用Spring Security前/后注释
- securedEnabled属性确定是否应启用@Secured注释
- jsr250Enabled属性允许我们使用@RoleAllowed注释
我们将在下一节中详细探讨这些注释。
3.应用方法级别安全性
3.1。使用@Secured Annotation
@Secured注释用于指定方法上的角色列表。因此,如果用户至少具有一个指定的角色,则用户能访问该方法。
我们定义一个getUsername方法:
@Secured("ROLE_VIEWER")
public String getUsername() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return securityContext.getAuthentication().getName();
}
这里,@ Secure(“ROLE_VIEWER”)注释定义只有具有ROLE_VIEWER角色的用户才能执行getUsername方法。
此外,我们可以在@Secured注释中定义角色列表:
@Secured({ "ROLE_VIEWER", "ROLE_EDITOR" })
public boolean isValidUsername(String username) {
return userRoleRepository.isValidUsername(username);
}
在这种情况下,配置指出如果用户具有ROLE_VIEWER或ROLE_EDITOR,则该用户可以调用isValidUsername方法。
@Secured注释不支持Spring Expression Language(SpEL)。
3.2。使用@RoleAllowed注释
@RoleAllowed注释是JSR-250对@Secured注释的等效注释。
基本上,我们可以像@Secured一样使用@RoleAllowed注释。因此,我们可以重新定义getUsername和isValidUsername方法:
@RolesAllowed("ROLE_VIEWER")
public String getUsername2() {
//...
}
@RolesAllowed({ "ROLE_VIEWER", "ROLE_EDITOR" })
public boolean isValidUsername2(String username) {
//...
}
同样,只有具有角色ROLE_VIEWER的用户才能执行getUsername2。
同样,只有当用户至少具有ROLE_VIEWER或ROLER_EDITOR角色之一时,用户才能调用isValidUsername2。
3.3。使用@PreAuthorize和@PostAuthorize注释
@PreAuthorize和@PostAuthorize注释都提供基于表达式的访问控制。因此,可以使用SpEL(Spring Expression Language)编写。
@PreAuthorize注释在进入方法之前检查给定的表达式,而@PostAuthorize注释在执行方法后验证它并且可能改变结果。
现在,让我们声明一个getUsernameInUpperCase方法,如下所示:
@PreAuthorize("hasRole('ROLE_VIEWER')")
public String getUsernameInUpperCase() {
return getUsername().toUpperCase();
}
@PreAuthorize(“hasRole('ROLE_VIEWER')”)与我们在上一节中使用的@Secured(“ROLE_VIEWER”)具有相同的含义。您可以在以前的文章中发现更多安全表达式详细信息。
因此,注释@Secured({“ROLE_VIEWER”,“ROLE_EDITOR”})可以替换为@PreAuthorize(“hasRole('ROLE_VIEWER')或hasRole('ROLE_EDITOR')”):
@PreAuthorize("hasRole('ROLE_VIEWER') or hasRole('ROLE_EDITOR')")
public boolean isValidUsername3(String username) {
//...
}
而且,我们实际上可以使用method参数作为表达式的一部分:
@PreAuthorize("#username == authentication.principal.username")
public String getMyRoles(String username) {
//...
}
这里,只有当参数username的值与当前主体的用户名相同时,用户才能调用getMyRoles方法。
值得注意的是,@ PreAuthorize表达式可以替换为@PostAuthorize表达式。
让我们重写getMyRoles:
@PostAuthorize("#username == authentication.principal.username")
public String getMyRoles2(String username) {
//...
}
但是,在上一个示例中,授权将在执行目标方法后延迟。
此外,@ PostAuthorize注释提供了访问方法结果的能力:
@PostAuthorize
("returnObject.username == authentication.principal.nickName")
public CustomUser loadUserDetail(String username) {
return userRoleRepository.loadUserByUserName(username);
}
在此示例中,如果返回的CustomUser的用户名等于当前身份验证主体的昵称,则loadUserDetail方法会成功执行。
3.4。使用@PreFilter和@PostFilter注释
Spring Security提供了@PreFilter注释来在执行方法之前过滤集合参数:
@PreFilter("filterObject != authentication.principal.username")
public String joinUsernames(List<String> usernames) {
return usernames.stream().collect(Collectors.joining(";"));
}
在此示例中,我们将过滤除经过身份验证的用户名以外的所有用户名。
这里,我们的表达式使用名称filterObject来表示集合中的当前对象。
但是,如果该方法有多个参数是集合类型,我们需要使用filterTarget属性来指定我们要过滤的参数:
@PreFilter
(value = "filterObject != authentication.principal.username",
filterTarget = "usernames")
public String joinUsernamesAndRoles(
List<String> usernames, List<String> roles) {
return usernames.stream().collect(Collectors.joining(";"))
+ ":" + roles.stream().collect(Collectors.joining(";"));
}
此外,我们还可以使用@PostFilter注释过滤返回的方法集合:
@PostFilter("filterObject != authentication.principal.username")
public List<String> getAllUsernamesExceptCurrent() {
return userRoleRepository.getAllUsernames();
}
在这种情况下,名称filterObject引用返回集合中的当前对象。
使用该配置,Spring Security将遍历返回的列表并删除与主体用户名匹配的任何值。
3.5。Method Security元注释
我们发现经常有使用相同安全配置保护不同方法的情况。
在这种情况下,我们可以定义一个Security元注释:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('VIEWER')")
public @interface IsViewer {
}
接下来,我们可以直接使用@IsViewer注释来保护我们的方法:
Security元注释是一个好主意,因为它们添加了更多语义并将我们的业务逻辑与安全框架分离。
3.6。类级别Security注释
如果我们发现对一个类中的每个方法使用相同的Security注释,我们可以考虑将该注释放在类级别:
@Service
@PreAuthorize("hasRole('ROLE_ADMIN')")
public class SystemService {
public String getSystemYear(){
//...
}
public String getSystemDate(){
//...
}
}
在上面的示例中,安全规则hasRole('ROLE_ADMIN')将应用于getSystemYear和getSystemDate方法。
3.7。方法上有的多重Security注释
我们还可以在一个方法上使用多个Security注释:
@PreAuthorize("#username == authentication.principal.username")
@PostAuthorize("returnObject.username == authentication.principal.nickName")
public CustomUser securedLoadUserDetail(String username) {
return userRoleRepository.loadUserByUserName(username);
}
因此,Spring将在执行securedLoadUserDetail方法之前和之后验证授权。
4.重要考虑因素
我们想提醒两点方法Security:
- 默认情况下,Spring AOP代理用于应用方法安全性 - 如果安全方法A由同一类中的另一个方法调用,则A中的安全性将被完全忽略。这意味着方法A将在没有任何安全检查的情况下执行,这同样适用于私有方法
- Spring SecurityContext是线程绑定的 - 默认情况下,安全上下文不会传播到子线程
5.测试方法Security
5.1。配置
要使用JUnit测试Spring Security,我们需要spring-security-test依赖项:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
</dependency>
我们不需要指定依赖版本,因为我们使用的是Spring Boot插件。
接下来,让我们通过指定runner和ApplicationContext配置来配置一个简单的Spring Integration测试:
@RunWith(SpringRunner.class)
@ContextConfiguration
public class TestMethodSecurity {
// ...
}
5.2。测试用户名和角色
现在我们的配置准备好了,让我们尝试测试我的getUsername方法,该方法由注释@Secured(“ROLE_VIEWER”)保护:
@Secured("ROLE_VIEWER")
public String getUsername() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return securityContext.getAuthentication().getName();
}
由于我们在这里使用@Secured注释,因此需要对用户进行身份验证以调用该方法。否则,我们将获得AuthenticationCredentialsNotFoundException。
因此,我们需要为用户提供测试我们的安全方法。为此,我们使用@WithMockUser修饰测试方法并提供用户和角色:
@Test
@WithMockUser(username = "john", roles = { "VIEWER" })
public void givenRoleViewer_whenCallGetUsername_thenReturnUsername() {
String userName = userRoleService.getUsername();
assertEquals("john", userName);
}
我们提供了一个经过身份验证的用户,其用户名是john,其角色是ROLE_VIEWER。如果我们不指定用户名或角色,则默认用户名为user,默认角色为ROLE_USER。
请注意,此处不必添加ROLE_前缀,Spring Security将自动添加该前缀。
如果我们不想拥有该前缀,我们可以考虑使用权限而不是角色。
例如,让我们声明一个getUsernameInLowerCase方法:
@PreAuthorize("hasAuthority('SYS_ADMIN')")
public String getUsernameLC(){
return getUsername().toLowerCase();
}
我们可以使用权限测试:
@Test
@WithMockUser(username = "JOHN", authorities = { "SYS_ADMIN" })
public void givenAuthoritySysAdmin_whenCallGetUsernameLC_thenReturnUsername() {
String username = userRoleService.getUsernameInLowerCase();
assertEquals("john", username);
}
如果我们想在许多测试用例中使用相同的用户,我们可以在测试类中声明@WithMockUser注释:
@RunWith(SpringRunner.class)
@ContextConfiguration
@WithMockUser(username = "john", roles = { "VIEWER" })
public class TestWithMockUserAtClassLevel {
//...
}
如果我们想以匿名用户身份运行我们的测试,我们可以使用@WithAnonymousUser注释:
@Test(expected = AccessDeniedException.class)
@WithAnonymousUser
public void givenAnomynousUser_whenCallGetUsername_thenAccessDenied() {
userRoleService.getUsername();
}
在上面的示例中,我们期望AccessDeniedException,因为匿名用户未被授予角色ROLE_VIEWER或权限SYS_ADMIN。
5.3。使用Custom UserDetailsService进行测试
对于大多数应用程序,通常使用自定义类作为身份验证主体。在这种情况下,自定义类需要实现org.springframework.security.core.userdetails.UserDetails接口。
在本文中,我们声明了一个CustomUser类,它扩展了UserDetails的现有实现,即org.springframework.security.core.userdetails.User:
public class CustomUser extends User {
private String nickName;
// getter and setter
}
让我们在第3节中使用@PostAuthorize注释取回示例:
@PostAuthorize("returnObject.username == authentication.principal.nickName")
public CustomUser loadUserDetail(String username) {
return userRoleRepository.loadUserByUserName(username);
}
在这种情况下,只有返回的CustomUser的用户名等于当前身份验证主体的昵称时,该方法才会成功执行。
如果我们想测试该方法,我们可以提供UserDetailsService的实现,它可以根据用户名加载我们的CustomUser:
@Test
@WithUserDetails(
value = "john",
userDetailsServiceBeanName = "userDetailService")
public void whenJohn_callLoadUserDetail_thenOK() {
CustomUser user = userService.loadUserDetail("jane");
assertEquals("jane", user.getNickName());
}
这里,@ WithUserDetails注释声明我们将使用UserDetailsService来初始化我们经过身份验证的用户。该服务由userDetailsServiceBeanName属性引用。这个UserDetailsService可能是一个真正的实现,或者用于测试目的。
此外,该服务将使用属性值的值作为加载UserDetails的用户名。
方便的是,我们也可以在类级别使用@WithUserDetails注释进行修饰,类似于我们对@WithMockUser注释所做的操作。
5.4。使用Meta注释进行测试
我们经常发现自己在各种测试中一遍又一遍地重复使用相同的用户/角色。
对于这些情况,创建元注释很方便。
修改前面的示例@WithMockUser(username =“john”,roles = {“VIEWER”}),我们可以将元注释声明为:
@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(value = "john", roles = "VIEWER")
public @interface WithMockJohnViewer { }
然后我们可以在测试中简单地使用@WithMockJohnViewer:
@Test
@WithMockJohnViewer
public void givenMockedJohnViewer_whenCallGetUsername_thenReturnUsername() {
String userName = userRoleService.getUsername();
assertEquals("john", userName);
}
同样,我们可以使用元注释来使用@WithUserDetails创建特定于域的用户。
六,结论
在本教程中,我们探讨了在Spring Security中使用Method Security的各种选项。
我们还经历了一些技术来轻松测试方法安全性,并学习如何在不同的测试中重用模拟用户。
可以在Github上找到本教程的所有示例。
Spring Security方法级别授权使用介绍的更多相关文章
- Spring Security 与 OAuth2(介绍)
https://www.jianshu.com/p/68f22f9a00ee Spring Security 与 OAuth2(介绍) 林塬 2018.01.23 11:14* 字数 3097 阅读 ...
- spring security 方法权限使用
前面我们讲过了使用<security:intercept-url>配置url的权限访问,下面我们讲解一下基于方法的权限使用默认情况下, Spring Security 并不启用方法级的安全 ...
- spring security方法一 自定义数据库表结构
Spring Security默认提供的表结构太过简单了,其实就算默认提供的表结构很复杂,也无法满足所有企业内部对用户信息和权限信息管理的要求.基本上每个企业内部都有一套自己的用户信息管理结构,同时也 ...
- spring security 11种过滤器介绍
1.HttpSessionContextIntegrationFilter 位于过滤器顶端,第一个起作用的过滤器. 用途一,在执行其他过滤器之前,率先判断用户的session中是否已经存在一个Secu ...
- Spring Security核心概念介绍
Spring Security是一个强大的java应用安全管理库,特别适合用作后台管理系统.这个库涉及的模块和概念有一定的复杂度,而大家平时学习Spring的时候也不会涉及:这里基于官方的参考文档,把 ...
- Spring Security 与 OAuth2 介绍
个人 OAuth2 全部文章 Spring Security 与 OAuth2(介绍):https://www.jianshu.com/p/68f22f9a00ee Spring Security 与 ...
- 朱晔和你聊Spring系列S1E10:强大且复杂的Spring Security(含OAuth2三角色+三模式完整例子)
Spring Security功能多,组件抽象程度高,配置方式多样,导致了Spring Security强大且复杂的特性.Spring Security的学习成本几乎是Spring家族中最高的,Spr ...
- Spring Security实现RBAC权限管理
Spring Security实现RBAC权限管理 一.简介 在企业应用中,认证和授权是非常重要的一部分内容,业界最出名的两个框架就是大名鼎鼎的 Shiro和Spring Security.由于Spr ...
- Spring Security 解析(一) —— 授权过程
Spring Security 解析(一) -- 授权过程 在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决定先把Spring Security .S ...
随机推荐
- SQL 常用语句收集
1.UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值 2.SELECT * FROM TableA INNER JOIN TableB ON TableA.name = T ...
- 【转】 Pro Android学习笔记(三七):Fragment(2):基础小例子
目录(?)[-] 小例子运行效果 Pre-step一点准备 Step 1Activity的布局 小例子运行效果 这是一个书名和书简介的例子.运行如下图.Activity由左右两个Fragment组成, ...
- 【转】 Pro Android学习笔记(三二):Menu(3):Context菜单
目录(?)[-] 什么是Context menu 注册View带有Context menu 填Context菜单内容 Context菜单点击触发 什么是Context menu 在桌面电脑,我们都很熟 ...
- AI-Info-Micron-Menu:About Micron
ylbtech-AI-Info-Micron-Menu:About Micron 将数据带入生活 美光科技的存储技术帮助将海量数据化为宝贵见解,重新定义世界使用信息的方式. 1.返回顶部 1. 公司简 ...
- mysql软文
常用的MySQL复杂查询语句写法 http://www.blogjava.net/bolo/archive/2015/02/02/422649.html mysql sql常用语句大全 http: ...
- tcpdump网络数据抓包
tcpdump,就是:dump the traffic on a network,根据使用者的定义对网络上的数据包进行截获的包分析工具. tcpdump可以将网络中传送的数据包的“头”完全截获下来提供 ...
- CSS之BFC详解
What:了解该知识点的概念,本质以及有关牵扯到的相关知识概念 BFC这个东西说常见的话你可能不觉得,但是你肯定会常用,也许你在用的时候也没想到BFC这东西.网上也有很多写这些东西的文章,但是自己写一 ...
- mac上如何查看gif
今天生成了一个gif,结果用mac自带的图片预览功能打开,图片被切成一张一张的,不是动图效果了.原以为还得下第三方看图软件,后来百度下发现mac本身也可以打开. 方法一: 鼠标右击图片,选择“快速查看 ...
- AngularJs(Part 7)--Build-in Directives
Directives In AngularJS, we can use a variety of naming conventions to reference directives . In the ...
- 树莓派 Learning 002 装机后的必要操作 --- 05 给树莓派搭建“x86 + pi”环境 -- 安装**32位运行库** -- 解决`E:未发现软件包 xxx` 问题
树莓派 装机后的必要操作 - 给树莓派搭建"x86 + pi"环境 – 安装32位运行库 – 解决E:未发现软件包 xxx 问题 我的树莓派型号:Raspberry Pi 2 Mo ...