Spring Security(10)权限处理

重点记忆:

1.spring Security的权限配置是在user类的getAuthorities方法中实现的
2.权限注解@EnableGlobalMethodSecurity(prePostEnabled=true)
/开启权限注解 //prePostEnabled = true 属性表示开启 Spring Security 中的 @PreAuthorize、@PostAuthorize、@PreFilter 以及 @PostFilter 四个注解

一.项目配置

1.1 数据库

权限表:用户和管理员权限
在这里插入图片描述

用户表
在这里插入图片描述

角色表:管理员和用户
在这里插入图片描述

用户和角色中间表
在这里插入图片描述

角色和权限中间表
在这里插入图片描述

权限表

1.2pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.qfedu</groupId>
    <artifactId>security06</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>security06</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

1.3application.properties

spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.url=jdbc:mysql:///day07db?serverTimezone=Asia/Shanghai

二.SecurityConfig和User配置权限

2.1 User

package com.huang.demo.model;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;

/**
 * 定义用户对象,需要实现 UserDetails 接口,对于 Spring Security 框架而言,所有的用户对象都是一个 UserDetails 的实例
 *
 * 如实实现接口中的方法就可以了
 */
public class User implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private Boolean enabled;
    private Set<String> permissions;

    public Set<String> getPermissions() {
        return permissions;
    }

    public void setPermissions(Set<String> permissions) {
        this.permissions = permissions;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setEnabled(Boolean enabled) {
        this.enabled = enabled;
    }

    /**
     * 这个方法用来返回当前用户的角色/权限信息
     *
     * 在 Spring Security 中,无论是用户角色,还是用户权限,都是从这个方法返回的
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            for (String permission : permissions) {
                authorities.add(new SimpleGrantedAuthority(permission));
            }
            return authorities;
    }

    /**
     * 获取用户密码
     * @return
     */
    @Override
    public String getPassword() {
        return password;
    }

    /**
     * 获取用户名
     * @return
     */
    @Override
    public String getUsername() {
        return username;
    }

    /**
     * 账户是否没有过期
     *
     * 正常来说,数据库中应该也有一个描述账户是否过期的字段
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 账户是否没有被锁定
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 密码是否没有过期
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 账户是否可用
     * @return
     */
    @Override
    public boolean isEnabled() {
        return enabled;
    }
}

注意:

这个方法用来返回当前用户的角色或者权限信息,跟shiro不同的是,spring security分的很明白,能不能访问接口,终究是根据权限来进行判定,而角色只是业务层面的一种分类,而权限是底层获取的判断的值

   /**
     * 这个方法用来返回当前用户的角色/权限信息
     *
     * 在 Spring Security 中,无论是用户角色,还是用户权限,都是从这个方法返回的
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            for (String permission : permissions) {
                authorities.add(new SimpleGrantedAuthority(permission));
            }
            return authorities;
    }

2.2 SecurityConfig

package com.huang.demo.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.huang.demo.model.RespBean;
import com.huang.demo.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.SecurityFilterChain;

import java.io.PrintWriter;

@Configuration
public class SecurityConfig {

    /**
     * admin 继承 user,user 能做的事情,admin 都能做
     * @return
     */
    @Bean
    RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("admin > user");
        return roleHierarchy;
    }

    /**
     * 自己手动配置安全过滤器链
     *
     * @return
     */
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //开始认证
        http.authorizeRequests()
                //具备 user 权限则可以访问 /user/** 格式的路径
                            .antMatchers("/user/**").hasAuthority("user")
                            .antMatchers("/admin/**").hasAuthority("admin")
                //所有的请求,类似于 shiro 中的 /**
                .anyRequest()
                //必须要认证之后才能访问,类似于 shiro 中的 authc
                .authenticated()
                .and()
                //开始配置登录表单
                .formLogin()
                //配置处理登录请求的接口,本质上其实就是配置过滤器的拦截规则,将来的登录请求就会在过滤器中被处理
                .loginProcessingUrl("/doLogin")
                //配置登录表单中用户名的 key
                .usernameParameter("username")
                //配置登录表单中用户密码
                .passwordParameter("password")
                //登录成功处理器
                //req:当前请求对象
                //resp:当前响应对象
                //auth:当前认证成功的用户信息
                .successHandler((req, resp, auth) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    User principal = (User) auth.getPrincipal();
                    principal.setPassword(null);
                    RespBean respBean = RespBean.ok("登录成功", principal);
                    String s = new ObjectMapper().writeValueAsString(respBean);
                    resp.getWriter().write(s);
                })
                //登录失败的回调
                .failureHandler((req, resp, e) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    //登录失败可能会有多种原因
                    RespBean respBean = RespBean.error("登录失败");
                    if (e instanceof BadCredentialsException) {
                        respBean.setMsg("用户名或者密码输入错误,登录失败");
                    } else if (e instanceof UsernameNotFoundException) {
                        //默认情况下,这个分支是不会进来的,Spring Security 自动隐藏了了这个异常,如果系统中发生了 UsernameNotFoundException 会被自动转为 BadCredentialsException 异常然后抛出来
                    } else if (e instanceof LockedException) {
                        //如果 com.qfedu.security02.model.User.isAccountNonLocked 方法返回 false,就会进入到这里来
                        respBean.setMsg("账户被锁定,登录失败");
                    } else if (e instanceof AccountExpiredException) {
                        //com.qfedu.security02.model.User.isAccountNonExpired
                        respBean.setMsg("账户过期,登录失败");
                    } else if (e instanceof CredentialsExpiredException) {
                        respBean.setMsg("密码过期,登录失败");
                    } else if (e instanceof DisabledException) {
                        respBean.setMsg("账户被禁用,登录失败");
                    }
                    ObjectMapper om = new ObjectMapper();
                    String s = om.writeValueAsString(respBean);
                    PrintWriter out = resp.getWriter();
                    out.write(s);
                })
                .and()
                //关闭 csrf 防御机制,这个 disable 方法本质上就是从 Spring Security 的过滤器链上移除掉 csrf 过滤器
                .csrf().disable()
                .exceptionHandling()
                //如果用户未登录就访问某一个页面,就会触发当前方法
                .authenticationEntryPoint((req, resp, authException) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    RespBean respBean = RespBean.error("尚未登录,请登录");
                    String s = new ObjectMapper().writeValueAsString(respBean);
                    resp.getWriter().write(s);
                });
        return http.build();
    }
}

关键配置

   /**
     * admin 继承 user,user 能做的事情,admin 都能做
     * @return
     */
    @Bean
    RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("admin > user");
        return roleHierarchy;
    }

2.3.测试

测试zhangsan用户,是admin,继承了user的所有接口

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

测试 用户lisi ,是user,自然只能访问user的所有接口

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三. 权限注解—通过权限评估器PermissionEvaluator

3.1 更改SecurityConfig

第一步 把SecurityConfig里面的角色继承方法roleHierarchy给注释掉,
第二步骤权限访问路径注释掉
第三步加上权限注解@EnableGlobalMethodSecurity(prePostEnabled=true)

/开启权限注解
//prePostEnabled = true 属性表示开启 Spring Security 中的 @PreAuthorize、@PostAuthorize、@PreFilter 以及 @PostFilter 四个注解

即SecurityConfig写成

package com.huang.demo.config;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.huang.demo.model.RespBean;
import com.huang.demo.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.SecurityFilterChain;

import java.io.PrintWriter;

@Configuration
//开启权限注解
//prePostEnabled = true 属性表示开启 Spring Security 中的 @PreAuthorize、@PostAuthorize、@PreFilter 以及 @PostFilter 四个注解
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

/*    *//**
     * admin 继承 user,user 能做的事情,admin 都能做
     * @return
     *//*
    @Bean
    RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("admin > user");
        return roleHierarchy;
    }*/

    /**
     * 自己手动配置安全过滤器链
     *
     * @return
     */
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //开始认证
        http.authorizeRequests()
               /* //具备 user 权限则可以访问 /user/** 格式的路径
                            .antMatchers("/user/**").hasAuthority("user")
                            .antMatchers("/admin/**").hasAuthority("admin")*/
                //所有的请求,类似于 shiro 中的 /**
                .anyRequest()
                //必须要认证之后才能访问,类似于 shiro 中的 authc
                .authenticated()
                .and()
                //开始配置登录表单
                .formLogin()
                //配置处理登录请求的接口,本质上其实就是配置过滤器的拦截规则,将来的登录请求就会在过滤器中被处理
                .loginProcessingUrl("/doLogin")
                //配置登录表单中用户名的 key
                .usernameParameter("username")
                //配置登录表单中用户密码
                .passwordParameter("password")
                //登录成功处理器
                //req:当前请求对象
                //resp:当前响应对象
                //auth:当前认证成功的用户信息
                .successHandler((req, resp, auth) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    User principal = (User) auth.getPrincipal();
                    principal.setPassword(null);
                    RespBean respBean = RespBean.ok("登录成功", principal);
                    String s = new ObjectMapper().writeValueAsString(respBean);
                    resp.getWriter().write(s);
                })
                //登录失败的回调
                .failureHandler((req, resp, e) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    //登录失败可能会有多种原因
                    RespBean respBean = RespBean.error("登录失败");
                    if (e instanceof BadCredentialsException) {
                        respBean.setMsg("用户名或者密码输入错误,登录失败");
                    } else if (e instanceof UsernameNotFoundException) {
                        //默认情况下,这个分支是不会进来的,Spring Security 自动隐藏了了这个异常,如果系统中发生了 UsernameNotFoundException 会被自动转为 BadCredentialsException 异常然后抛出来
                    } else if (e instanceof LockedException) {
                        //如果 com.qfedu.security02.model.User.isAccountNonLocked 方法返回 false,就会进入到这里来
                        respBean.setMsg("账户被锁定,登录失败");
                    } else if (e instanceof AccountExpiredException) {
                        //com.qfedu.security02.model.User.isAccountNonExpired
                        respBean.setMsg("账户过期,登录失败");
                    } else if (e instanceof CredentialsExpiredException) {
                        respBean.setMsg("密码过期,登录失败");
                    } else if (e instanceof DisabledException) {
                        respBean.setMsg("账户被禁用,登录失败");
                    }
                    ObjectMapper om = new ObjectMapper();
                    String s = om.writeValueAsString(respBean);
                    PrintWriter out = resp.getWriter();
                    out.write(s);
                })
                .and()
                //关闭 csrf 防御机制,这个 disable 方法本质上就是从 Spring Security 的过滤器链上移除掉 csrf 过滤器
                .csrf().disable()
                .exceptionHandling()
                //如果用户未登录就访问某一个页面,就会触发当前方法
                .authenticationEntryPoint((req, resp, authException) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    RespBean respBean = RespBean.error("尚未登录,请登录");
                    String s = new ObjectMapper().writeValueAsString(respBean);
                    resp.getWriter().write(s);
                });
        return http.build();
    }
}

3.2配置权限评估器MyPermissionEvaluator

配置这个工具类是为了把User里面获取用户权限的方法getAuthorities抽离出来。

package com.huang.demo.config;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import java.io.Serializable;
import java.util.Collection;


//处理了获取权限的问题
@Component
public class MyPermissionEvaluator implements PermissionEvaluator {
    private AntPathMatcher pathMatcher = new AntPathMatcher();
    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        //获取当前用户具备的权限
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            if (pathMatcher.match(authority.getAuthority(), (String) permission)) {
                //说明当前登录的用户具备访问该接口所需要的权限
                return true;
            }
        }
        return false;
    }




    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return false;
    }
}

注意:

hasPermission为重点方法,传入三个参数
1.authentication,当前用户角色
2.不重要
3.permission当前用户所需要的权限

3.3 改权限表permission

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

方便我们接下来细分权限。

3.4 写接口UserController

UserController

package com.huang.demo.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    /**
     * 当前用户具备 user 角色的时候,就可以访问 user 接口
     * @return
     */
    @GetMapping("/user/hello")
    //判断用户具备 user 权限才能访问
//    @PreAuthorize("hasAuthority('user')")
//    @PreAuthorize("hasPermission(#principal,'user:select')")
    @PreAuthorize("hasPermission(#principal,'user:select')")
    public String hello() {
        return "hello user";
    }





    /**
     * 当前用户具备 user 角色的时候,就可以访问 user 接口
     * @return
     */

    @DeleteMapping("/user/delete")
    //判断用户具备 user 权限才能访问
//    @PreAuthorize("hasAuthority('user')")
//    @PreAuthorize("hasPermission(#principal,'user:select')")
    @PreAuthorize("hasPermission(#principal,'user:delete')")
    public String delete() {
        return "删除成功";
    }





    /**
     * 当前用户具备 admin 角色的时候,就可以访问 admin 接口
     * @return
     */
    @GetMapping("/admin/hello")
    @PreAuthorize("hasPermission(#principal,'admin')")
    public String admin() {
        return "hello admin";
    }
}

3.5 关于 @PreAuthorize注解的第二种写法

 @PreAuthorize("hasPermission(#principal,'user:select')")

一样可以实现,过几天加测

3.6测试

3.6.1 使用测试用户-lisi-user-user:select

在这里插入图片描述
在这里插入图片描述

用户查询成功

在这里插入图片描述

用户查询失败,报错403说明没有该权限,即我们的权限注解起到了作用。让user用户lisi只能做他有的权限
在这里插入图片描述

同理,user用户lisi没办法登录管理员的接口

在这里插入图片描述

3.6.2 使用测试用户-admin-zhangsan-admin去测试

在这里插入图片描述

zhangsan用户具有管理员的所有权限
在这里插入图片描述

可以进行管理员查询
在这里插入图片描述

但是依旧没有办法做用户查询
在这里插入图片描述

四.

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值