百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

想要控制好权限,这八个注解你必须知道

lipiwang 2024-12-01 00:42 5 浏览 0 评论

小伙伴们知道松哥最近在做 TienChin 项目,项目里涉及到一个问题,那就是数据权限过滤,有不少小伙伴对这个问题觉得特别迷,希望松哥松哥能整一篇文章讲讲,好吧,安排。

在讲数据权限之前,我们有必要先和大家介绍一下 Spring Security 中的权限注解,把这个捋清楚了,再去看 TienChin 项目的权限注解,你就会发现非常容易了。

1. Spring Security 中的权限注解

Spring Security 中支持多种权限注解,首先我们需要通过 @EnableGlobalMethodSecurity 注解开启权限注解的使用,方式如下:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}

在这个注解中我们一共设置了三个属性:

  1. prePostEnabled:这个表示开启 Spring Security 提供的四个权限注解,@PostAuthorize@PostFilter@PreAuthorize 以及 @PreFilter,这四个注解支持权限表达式,支持 SpEL,功能比较丰富。
  2. securedEnabled:开启 Spring Security 提供的 @Secured 注解,该注解不支持权限表达式。
  3. jsr250Enabled:开启 JSR-250 提供的注解,主要包括@DenyAll@PermitAll 以及 @RolesAllowed 三个注解,这些注解也不支持权限表达式。

以上这些注解的含义分别如下:

  • @PostAuthorize:在目标方法执行之后进行权限校验。
  • @PostFilter:在目标方法执行之后对方法的返回结果进行过滤。
  • @PreAuthorize:在目标方法执行之前进行权限校验。
  • @PreFilter:在目标方法执行之前对方法参数进行过滤。
  • @Secured:访问目标方法必须具备相应的角色。
  • @DenyAll:拒绝所有访问。
  • @PermitAll:允许所有访问。
  • @RolesAllowed:访问目标方法必须具备相应的角色。

这是所有的,当然一般来说我们只要设置 prePostEnabled=true 就够用了,也就是前四个注解基本上就能满足大部分需求了。

另外还有一种比较“古老”的方法配置基于方法的权限管理,那就是通过 XML 文件配置方法拦截规则,目前已经很少有用 XML 文件来配置 Spring Security 了,所以对于这种方式我们不做过多介绍。感兴趣的小伙伴可以查看官网的相关介绍:https://docs.spring.io/spring-security/ site/docs/5.4.0/reference/html5/#secure-object-impls

2. 权限注解实践

接下来我们通过几个简单的案例来学习一下上面几种不同注解的用法。

首先创建一个 Spring Boot 项目,引入 Web 和 Spring Security 依赖,项目创建完成后,添加如下配置文件:

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig{
}

为了方便起见,我们将使用单元测试进行验证,所以这里就不进行额外的配置了,通过 @EnableGlobalMethodSecurity 注解开启其他权限注解的使用即可。

接下来创建一个 User 类以备后续使用:

public class User {
    private Integer id;
    private String username;

    //省略getter/setter
}

准备工作完成后,我们来逐个讲解一下前面注解的用法。

2.1 @PreAuthorize

@PreAuthorize 注解可以在目标方法执行之前对其进行安全校验,在安全校验时,可以直接使用权限表达式。例如可以定义如下方法:

@Service
public class HelloService {
    @PreAuthorize("hasRole('ADMIN')")
    public String hello() {
        return "hello";
    }
}

这里使用了权限表达式 hasRole,表示执行该方法必须具备 ADMIN 角色才可以访问,否则不可以访问。接下来我们在单元测试中来测试该方法:

@SpringBootTest
class BasedOnMethodApplicationTests {
    @Autowired
    HelloService helloService;
    @Test
    @WithMockUser(roles = "ADMIN")
    void preauthorizeTest01() {
        String hello = helloService.hello();
        assertNotNull(hello);
        assertEquals("hello", hello);
    }
}

通过 @WithMockUser(roles = "ADMIN") 注解设定当前执行的用户角色是 ADMIN,然后调用 helloService 中的方法进行测试即可。如果将用户角色设置为其他字符,那单元测试就不会通过。

当然,这里除了 hasRole 表达式之外,也可以使用其他权限表达式,甚至也可以同时使用多个权限表达式,如下所示:

@Service
public class HelloService {
    @PreAuthorize("hasRole('ADMIN') and authentication.name=='javaboy'")
    public String hello() {
        return "hello";
    }
}

表示访问者名称必须是 javaboy,而且还需要同时具备 ADMIN 角色,才可以访问该方法。此时通过如下代码对其进行测试:

@SpringBootTest
class BasedOnMethodApplicationTests {
    @Autowired
    HelloService helloService;
    @Test
    @WithMockUser(roles = "ADMIN",username = "javaboy")
    void preauthorizeTest01() {
        String hello = helloService.hello();
        assertNotNull(hello);
        assertEquals("hello", hello);
    }
}

@PreAuthorize 注解中,还可以通过 # 引用方法的参数,并对其进行校验,例如如下方法表示请求者的用户名必须等于方法参数 name 的值,方法才可以被执行:

@PreAuthorize("authentication.name==#name")
public String hello(String name) {
    return "hello:" + name;
}

测试方法如下:

@Test
@WithMockUser(username = "javaboy")
void preauthorizeTest02() {
    String hello = helloService.hello("javaboy");
    assertNotNull(hello);
    assertEquals("hello:javaboy", hello);
}

当模拟的用户名和方法参数相等时,单元测试就可以通过。

2.2 @PreFilter

@PreFilter 主要是对方法的请求参数进行过滤,它里边包含了一个内置对象 filterObject 表示要过滤的参数,如果方法只有一个参数,则内置的 filterObject 对象就代表该参数;如果方法有多个参数,则需要通过 filterTarget 来指定 filterObject 到底代表哪个对象:

@PreFilter(value = "filterObject.id%2!=0",filterTarget = "users")
public void addUsers(List<User> users, Integer other) {
    System.out.println("users = " + users);
}

上面代码表示对方法参数 users 进行过滤,将 id 为奇数的 user 保留。

然后通过单元测试对该方法进行测试:

@Test
@WithMockUser(username = "javaboy")
void preFilterTest01() {
    List<User> users = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        users.add(new User(i, "javaboy:" + i));
    }
    helloService.addUsers(users, 99);
}

执行单元测试方法,addUsers 方法中只会打印出 id 为奇数的 user 对象。

2.3 @PostAuthorize

@PostAuthorize 是在目标方法执行之后进行权限校验。可能有小伙伴会觉得奇怪,目标方法都执行完了才去做权限校验意义何在?其实这个主要是在 ACL 权限模型中会用到,目标方法执行完毕后,通过 @PostAuthorize 注解去校验目标方法的返回值是否满足相应的权限要求。

?

不过呢,即使你的权限模型不是 ACL,也没关系,也有可能用到这个注解,反正记得它的作用:方法执行完成后,根据用户的权限信息过滤出需要返回给用户的数据。

从技术角度来讲,@PostAuthorize 注解中也可以使用权限表达式,但是在实际开发中权限表达式一般都是结合 @PreAuthorize 注解一起使用的。@PostAuthorize 包含一个内置对象 returnObject,表示方法的返回值,开发者可以对返回值进行校验:

@PostAuthorize("returnObject.id==1")
public User getUserById(Integer id) {
    return new User(id, "javaboy");
}

这个表示方法返回的 user 对象的 id 必须为 1,调用才会顺利通过,否则就会抛出异常。

然后通过单元测试对该方法进行测试:

@Test
@WithMockUser(username = "javaboy")
void postAuthorizeTest01() {
    User user = helloService.getUserById(1);
    assertNotNull(user);
    assertEquals(1,user.getId());
    assertEquals("javaboy",user.getUsername());
}

如果调用时传入的参数为 1,单元测试就会顺利通过。

2.4 @PostFilter

@PostFilter 注解是在目标方法执行之后,对目标方法的返回结果进行过滤,该注解中包含了一个内置对象 filterObject,表示目标方法返回的集合/数组中的具体元素:

@PostFilter("filterObject.id%2==0")
public List<User> getAll() {
    List<User> users = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        users.add(new User(i, "javaboy:" + i));
    }
    return users;
}

这段代码表示 getAll 方法的返回值 users 集合中 user 对象的 id 必须为偶数。

然后我们通过单元测试对其进行测试,代码如下:

@Test
@WithMockUser(roles = "ADMIN")
void postFilterTest01() {
    List<User> all = helloService.getAll();
    assertNotNull(all);
    assertEquals(5, all.size());
    assertEquals(2,all.get(1).getId());
}

2.5 @Secured

@Secured 注解也是 Spring Security 提供的权限注解,不同于前面四个注解,该注解不支持权限表达式,只能做一些简单的权限描述。

@Secured({"ROLE_ADMIN","ROLE_USER"})
public User getUserByUsername(String username) {
    return new User(99, username);
}

这段代码表示用户需要具备 ROLE_ADMIN 或者 ROLE_USER 角色,才能访问 getUserByUsername 方法。

然后我们通过单元测试对其进行测试,代码如下:

@Test
@WithMockUser(roles = "ADMIN")
void securedTest01() {
    User user = helloService.getUserByUsername("javaboy");
    assertNotNull(user);
    assertEquals(99,user.getId());
    assertEquals("javaboy", user.getUsername());
}

注意,这里不需要给角色添加 ROLE_ 前缀,系统会自动添加。

2.6 @DenyAll

@DenyAllJSR-250 提供的方法注解,看名字就知道这是拒绝所有访问:

@DenyAll
public String denyAll() {
    return "DenyAll";
}

然后我们通过单元测试对其进行测试,代码如下:

@Test
@WithMockUser(username = "javaboy")
void denyAllTest01() {
    helloService.denyAll();
}

在单元测试过程中,就会抛出异常。

2.7 @PermitAll

@PermitAll 也是 JSR-250 提供的方法注解,看名字就知道这是允许所有访问:

@PermitAll
public String permitAll() {
    return "PermitAll";
}

然后我们通过单元测试对其进行测试,代码如下:

@Test
@WithMockUser(username = "javaboy")
void permitAllTest01() {
    String s = helloService.permitAll();
    assertNotNull(s);
    assertEquals("PermitAll", s);
}

2.8 @RolesAllowed

@RolesAllowed 也是 JSR-250 提供的注解,可以添加在方法上或者类上,当添加在类上时,表示该注解对类中的所有方法生效;如果类上和方法上都有该注解,并且起冲突,则以方法上的注解为准。我们来看一个简单的案例:

@RolesAllowed({"ADMIN","USER"})
public String rolesAllowed() {
    return "RolesAllowed";
}

这个表示访问 rolesAllowed 方法需要具备 ADMIN 或者 USER 角色,然后我们通过单元测试对其进行测试,代码如下:

@Test
@WithMockUser(roles = "ADMIN")
void rolesAllowedTest01() {
    String s = helloService.rolesAllowed();
    assertNotNull(s);
    assertEquals("RolesAllowed", s);
}

这就是常见的方法权限注解。

好啦,今天就先和小伙伴们介绍这么多,下面文章我再和小伙伴们介绍这些注解在 TienChin 项目中的应用。

原文链接:https://mp.weixin.qq.com/s/1NlWRwiBs8dl3Lu40haz5Q

相关推荐

Qwen上新AI前端工程师!一句话搞定HTML/CSS/JS,秒变React大神

梦晨发自凹非寺量子位|公众号QbitAIQwen上新“AI前端工程师”WebDev,一句话开发网页应用。三大件HTML,CSS,JavaScript一个工具全包了,定睛一看用的还是Reac...

程序员的 JavaScript 代码该如何让计算机搞懂?

出自程序员之手的JavaScript代码,该如何变成计算机所能理解的机器语言呢?本文将带你走进JavaScript引擎内部,一探究竟。作者|LydiaHallie译者|弯月,责编|...

JavaScript:如何优雅的创建数组?

在JavaScript里,有多种方式可以创建数组,下面为你详细介绍:1.使用数组字面量这是最常用的创建数组的方法,使用方括号[]来创建数组。//创建一个空数组letemptyArray...

Jquery 详细用法

1、jQuery介绍(1)jQuery是什么?是一个js框架,其主要思想是利用jQuery提供的选择器查找要操作的节点,然后将找到的节点封装成一个jQuery对象。封装成jQuery对象的目的有...

HTML页面基本结构和加载过程

大家好,我是皮皮。前言对于前端来说,HTML都是最基础的内容。今天,我们来了解一下HTML和网页有什么关系,以及与DOM有什么不同。通过本讲内容,你将掌握浏览器是怎么处理HTML内容的,...

【HarmonyOS Next之旅】兼容JS的类Web开发(一)

目录1->概述1.1->整体架构2->文件组织2.1->目录结构2.2->文件访问规则2.3->媒体文件格式3->js标签配置3....

JavaScript初学者指南

如果你刚接触JavaScript,想必已经被“modulebundlersvs.moduleloaders”、“Webpackvs.Browserify”和“AMDvs.Common...

前端图片延迟加载详细讲解

原文链接:http://www.gbtags.com/gb/share/6366.htm?原本是打算昨天昨天下午的时候就写一篇关于前端图片延迟加载的详细技术的博客的,没想到下午公司项目出现了一些问题...

selenium:操作滚动条的方法(8)

selenium支持几种操作滚动条的方法,主要介绍如下:使用ActionChains类模拟鼠标滚轮操作使用函数ActionChains.send_keys发送按键Keys.PAGE_DOWN往下滑动...

jQuery 获取和设置HTML元素

jQuery中包含更改和操作HTML元素和属性的强大方法。我们可以通过这些方法来获取HTML元素中的文本内容、元素内容(例如HTML标签)、属性值等。text()方法text()方法可以用...

JavaScript脚本如何断言select下拉框的元素内容?

使用JavaScript脚本断言select下拉框的元素内容,需要考虑页面元素是否加载成功,出错时打印等,主要实现功能功能需包括如下几点:1.等待下拉框元素加载完成(支持超时设置)2.获取下...

JavaScript图片或者div拖动拖动函数的实现

/**拖动图片封装html格式:<imglay-src="${item.Resourcesurl}"alt="${item.ResourcesName}"...

JavaScript代码怎样引入到HTML中?

JavaScript程序不能独立运行,它需要被嵌入HTML中,然后浏览器才能执行JavaScript代码。通过<script>标签将JavaScript代码引入到HTM...

当你在Vue.js中想要隐藏 `` 标签时,可以这样做:

在Vue.js里,要是你想要搞掉`<br>`(换行)标签的效果,通常有几种路子:1.使用CSS嗯,最简单的办法就是用CSS搞定,控制元素的样式,让<br>标签彻底不显示...

php手把手教你做网站(三十)上传图片生成缩略图

三种方法:按比例缩小、图片裁切、预览图片裁切不管使用哪一个都是建立在图片已经上传的基础上;预览裁切上传,如果预览的图片就是原始大小,可以预览裁切以后上传(这里是个假象,下边会说明);1、上传以后按比例...

取消回复欢迎 发表评论: