• SpringSecurity基础:授权


    授权

    用户登录后,可能会根据用户当前是身份进行角色划分,比如我们最常用的QQ,一个QQ群里面,有群主、管理员和普通群成员三种角色,其中群主具有最高权限,群主可以管理整个群的任何板块,并且具有解散和升级群的资格,而管理员只有群主的一部分权限,只能用于日常管理,普通群成员则只能进行最基本的聊天操作。

    对于我们来说,用户的一个操作实际上就是在访问我们提供的接口(编写的对应访问路径的Servlet),比如登陆,就需要调用/login接口,退出登陆就要调用/logout接口,而我们之前的图书管理系统中,新增图书、删除图书,所有的操作都有着对应的Servlet来进行处理。因此,从我们开发者的角度来说,决定用户能否使用某个功能,只需要决定用户是否能够访问对应的Servlet即可。

    我们可以大致像下面这样进行划分:

    ●群主:/login、/logout、/chat、/edit、/delete、/upgrade
    ●管理员:/login、/logout、/chat、/edit
    ●普通群成员:/login、/logout、/chat

    也就是说,我们需要做的就是指定哪些请求可以由哪些用户发起。

    SpringSecurity为我们提供了两种授权方式:

    ●基于权限的授权:只要拥有某权限的用户,就可以访问某个路径
    ●基于角色的授权:根据用户属于哪个角色来决定是否可以访问某个路径

    两者只是概念上的不同,实际上使用起来效果差不多。这里我们就先演示以角色方式来进行授权。

    基于角色的授权

    现在我们希望创建两个角色,普通用户和管理员,普通用户只能访问index页面,而管理员可以访问任何页面。

    首先我们需要对数据库中的角色表进行一些修改,添加一个用户角色字段,并创建一个新的用户,Test用户的角色为user,而Admin用户的角色为admin。

    接着我们需要配置SpringSecurity,决定哪些角色可以访问哪些页面:

     

    1. http
    2. .authorizeRequests()
    3. .antMatchers("/static/**").permitAll()
    4. .antMatchers("/index").hasAnyRole("user", "admin") //index页面可以由user或admin访问
    5. .anyRequest().hasRole("admin") //除了上面以外的所有内容,只能是admin访问

    接着我们需要稍微修改一下验证逻辑,首先创建一个实体类用于表示数据库中的用户名、密码和角色:

    1. @Data
    2. public class AuthUser {
    3. String username;
    4. String password;
    5. String role;
    6. }

    接着修改一下Mapper:

    1. @Mapper
    2. public interface UserMapper {
    3. @Select("select * from users where username = #{username}")
    4. AuthUser getPasswordByUsername(String username);
    5. }

    最后再修改一下Service:

    1. @Override
    2. public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    3. AuthUser user = mapper.getPasswordByUsername(s);
    4. if(user == null)
    5. throw new UsernameNotFoundException("登录失败,用户名或密码错误!");
    6. return User
    7. .withUsername(user.getUsername())
    8. .password(user.getPassword())
    9. .roles(user.getRole())
    10. .build();
    11. }

    现在我们可以尝试登陆,接着访问一下/index/admin两个页面。

    基于权限的授权

    基于权限的授权与角色类似,需要以hasAnyAuthorityhasAuthority进行判断:

    .anyRequest().hasAnyAuthority("page:index")
    1. @Override
    2. public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    3. AuthUser user = mapper.getPasswordByUsername(s);
    4. if(user == null)
    5. throw new UsernameNotFoundException("登录失败,用户名或密码错误!");
    6. return User
    7. .withUsername(user.getUsername())
    8. .password(user.getPassword())
    9. .authorities("page:index")
    10. .build();
    11. }

    使用注解判断权限

    除了直接配置以外,我们还可以以注解形式直接配置,首先需要在配置类(注意这里是在Mvc的配置类上添加,因为这里只针对Controller进行过滤,所有的Controller是由Mvc配置类进行注册的,如果需要为Service或其他Bean也启用权限判断,则需要在Security的配置类上添加)上开启:

    1. @EnableGlobalMethodSecurity(prePostEnabled = true)
    2. public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    接着我们可以直接在需要添加权限验证的请求映射上添加注解:

     

    1. @PreAuthorize("hasRole('user')") //判断是否为user角色,只有此角色才可以访问
    2. @RequestMapping("/index")
    3. public String index(){
    4. return "index";
    5. }

    通过添加@PreAuthorize注解,在执行之前判断判断权限,如果没有对应的权限或是对应的角色,将无法访问页面。

    这里其实是使用了SpEL表达式,相当于可以执行一些逻辑再得到结果,而不是直接传值,官方文档地址:https://docs.spring.io/spring-framework/docs/5.2.13.RELEASE/spring-framework-reference/core.html#expressions,内容比较多,不是重点,这里就不再详细介绍了。

    同样的还有@PostAuthorize注解,但是它是在方法执行之后再进行拦截:

    1. @PostAuthorize("hasRole('user')")
    2. @RequestMapping("/index")
    3. public String index(){
    4. System.out.println("执行了");
    5. return "index";
    6. }

    除了Controller以外,只要是由Spring管理的Bean都可以使用注解形式来控制权限,只要不具备访问权限,那么就无法执行方法并且会返回403页面。

    1. @Service
    2. public class UserService {
    3. @PreAuthorize("hasAnyRole('user')")
    4. public void test(){
    5. System.out.println("成功执行");
    6. }
    7. }

    注意Service是由根容器进行注册,需要在Security配置类上添加@EnableGlobalMethodSecurity注解才可以生效。与具有相同功能的还有@Secure但是它不支持SpEL表达式的权限表示形式,并且需要添加"ROLE_"前缀,这里就不做演示了。

    我们还可以使用@PreFilter@PostFilter对集合类型的参数或返回值进行过滤。

    比如:

    1. @PreFilter("filterObject.equals('lbwnb')") //filterObject代表集合中每个元素,只要满足条件的元素才会留下
    2. public void test(List list){
    3. System.out.println("成功执行"+list);
    4. }
    1. @RequestMapping("/index")
    2. public String index(){
    3. List list = new LinkedList<>();
    4. list.add("lbwnb");
    5. list.add("yyds");
    6. service.test(list);
    7. return "index";
    8. }

    @PreFilter类似的@PostFilter这里就不做演示了,它用于处理返回值,使用方法是一样的。

    当有多个集合时,需要使用filterTarget进行指定:

    1. @PreFilter(value = "filterObject.equals('lbwnb')", filterTarget = "list2")
    2. public void test(List list, List list2){
    3. System.out.println("成功执行"+list);
    4. }

  • 相关阅读:
    如何优雅的开发?试试这个低代码项目
    【矩阵理论常见符号说明】
    C#:初识结构体、数组、冒泡排序。
    实验七:数组、字符串(2)——字符串
    java后端返回数据给前端时去除值为空或NULL的属性、忽略某些属性
    c++ double free错误解决
    第一章 赛前准备工作
    [附源码]计算机毕业设计JAVA户籍管理系统
    [暑假]操作系统概述笔记[学习方法篇]
    C#,文字排版的折行问题(Word-wrap problem)的算法与源代码
  • 原文地址:https://blog.csdn.net/Leon_Jinhai_Sun/article/details/126861314