zoukankan      html  css  js  c++  java
  • SpringBoot整合Shiro实现基于角色的权限访问控制(RBAC)系统简单设计从零搭建

    SpringBoot整合Shiro实现基于角色的权限访问控制(RBAC)系统简单设计从零搭建

    技术栈 : SpringBoot + shiro + jpa + freemark ,因为篇幅原因,这里只贴了部分代码说明,完整项目地址 : https://github.com/EalenXie/shiro-rbac-system

    1 . 新建一个项目名为shiro-rbac-system,pom.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 http://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.1.3.RELEASE</version>
        </parent>
        <groupId>name.ealen</groupId>
        <artifactId>shiro-rbac-system</artifactId>
        <version>0.0.1</version>
        <name>shiro-rbac-system</name>
        <description>SpringBoot整合Shiro实现基于角色的权限访问控制(RBAC)系统简单设计从零搭建</description>
    
        <properties>
            <java.version>1.8</java.version>
            <author>EalenXie</author>
            <description>SpringBoot整合Shiro实现基于角色的权限访问控制(RBAC)系统简单设计</description>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-freemarker</artifactId>
            </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.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.3.2</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.31</version>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    

    基本的数据对象关系 :

    一个用户对应一个或者多个角色。
    一个角色对应一个或者多个权限。
    一个权限对应能够访问对应的API或url资源。
    

    1 . RBAC基本实体关系,Permission类(权限资源) :

    
    /**
     * Created by EalenXie on 2019/3/25 11:15.
     * <p>
     * 权限许可(Permission) 操作 及其能访问url 权限对应一个url地址
     */
    @Entity
    @Table(name = "system_shiro_permission")
    public class Permission extends BaseEntity {
        @Column(unique = true)
        private String name;                //权限名 唯一
        @Column(unique = true)
        private String url;                 //访问地址信息 唯一
        private String description;         //描述信息
        //省略getter/setter
    }
    

    2 . Role类(用户角色),一个角色拥有一个或者多个权限 :

    
    /**
     * Created by EalenXie on 2019/3/25 11:18.
     * <p>
     * 角色(Role) 角色下面对应多个权限
     */
    @Entity
    @Table(name = "system_shiro_role")
    public class Role extends BaseEntity {
    
        @Column(unique = true)
        private String name;                    //角色名 唯一
        private String description;             //描述信息
        @ManyToMany(fetch= FetchType.EAGER)
        private List<Permission> permissions;   //一个用户角色对应多个权限
        //省略getter/setter
    }
    

    3 . User类(用户),一个用户拥有一个或者多个角色 :

    
    /**
     * Created by EalenXie on 2019/3/25 11:01.
     * <p>
     * 用户表(User) 用户下面对应多个角色
     */
    @Entity
    @Table(name = "system_shiro_user")
    public class User extends BaseEntity {
        @Column(unique = true)
        private String username;//用户名 唯一
        private String password;//用户密码
        private String passwordSalt;//用户密码加密盐值
        @ManyToMany(fetch = FetchType.EAGER)
        private List<Role> roles;//用户角色  一个用户可能有一个角色,也可能有 多个角色
        //省略getter/setter
    }
    

    2 . 基本配置信息 :

    1 . 配置应用的基本信息,application.yml :

    
    server:
      port: 8082
    spring:
      application:
        name: shiro-rbac-system
      resources:
        static-locations: classpath:/
      freemarker:
        template-loader-path: classpath:/templates/
        suffix: .html
        content-type: text/html
        charset: UTF-8
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/yourdatabase?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=true
        username: yourname
        password: yourpass
      jpa:
    #    show-sql: true
        hibernate:
          ddl-auto: update
        open-in-view: false # 禁用 OSIV <Spring Boot中默认启用了OSIV(Open Session in View)>
      http:
        encoding:
          charset: utf-8
          enabled: true
    

    2 . 配置JPA,基本的数据库访问Dao。

    
    public interface PermissionRepository extends JpaRepository<Permission, Integer> {
        Permission findByName(String name);
    }
    
    
    public interface RoleRepository extends JpaRepository<Role, Integer> {
        Role findByName(String name);
    }
    
    
    
    public interface UserRepository extends JpaRepository<User, Integer> {
        User findByUsername(String username);
    }
    

    3 . 配置shiro,基于角色访问控制权限的核心Realm,UserAuthRealm :

    
    @Component
    public class UserAuthRealm extends AuthorizingRealm {
    
        @Resource
        private UserRepository userRepository;
    
        /**
         * 权限核心配置 根据数据库中的该用户 角色 和 权限
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            User user = (User) principals.getPrimaryPrincipal();
            for (Role role : user.getRoles()) {                                 //获取 角色
                authorizationInfo.addRole(role.getName());                      //添加 角色
                for (Permission permission : role.getPermissions()) {           //获取 权限
                    authorizationInfo.addStringPermission(permission.getName());//添加 权限
                }
            }
            return authorizationInfo;
        }
    
        /**
         * 用户登陆 凭证信息
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            String username = (String) token.getPrincipal();
            User user = userRepository.findByUsername(username);
            if (user == null) return null;
            String credentials = user.getPasswordSalt() + user.getUsername() + user.getPasswordSalt();//自定义加盐 salt + username + salt
            return new SimpleAuthenticationInfo(
                    user, //用户名
                    user.getPassword(), //密码
                    ByteSource.Util.bytes(credentials), //加密
                    getName()  //realm name
            );
        }
    
        /**
         * 设置 realm的 HashedCredentialsMatcher
         */
        @PostConstruct
        public void setHashedCredentialsMatcher() {
            this.setCredentialsMatcher(hashedCredentialsMatcher());
        }
    
        /**
         * 凭证匹配 : 指定 加密算法,散列次数
         */
        @Bean
        public HashedCredentialsMatcher hashedCredentialsMatcher() {
            HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
            hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
            hashedCredentialsMatcher.setHashIterations(1024);//散列的次数,比如散列两次,相当于 md5(md5(""));
            return hashedCredentialsMatcher;
        }
    }
    

    4 . shiro核心配置,包含基本过滤器策略,注解支持等。

    
    /**
     * Created by EalenXie on 2019/3/25 15:12.
     */
    @Configuration
    public class ShiroConfig {
    
        @Resource
        private PermissionRepository permissionRepository;
    
        @Resource
        private UserAuthRealm userAuthRealm;
    
        /**
         * 配置 资源访问策略 . web应用程序 shiro核心过滤器配置
         */
        @Bean
        public ShiroFilterFactoryBean factoryBean(SecurityManager securityManager) {
            ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
            factoryBean.setSecurityManager(securityManager);
            factoryBean.setLoginUrl("/login");//登录页
            factoryBean.setSuccessUrl("/index");//首页
            factoryBean.setUnauthorizedUrl("/unauthorized");//未授权界面;
            factoryBean.setFilterChainDefinitionMap(setFilterChainDefinitionMap()); //配置 拦截过滤器链
            return factoryBean;
        }
    
        /**
         * 配置 SecurityManager,可配置一个或多个realm
         */
        @Bean
        public SecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(userAuthRealm);
    //        securityManager.setRealm(xxxxRealm);
            return securityManager;
        }
    
        /**
         * 开启shiro 注解支持. 使以下注解能够生效 :
         * 需要认证 {@link org.apache.shiro.authz.annotation.RequiresAuthentication RequiresAuthentication}
         * 需要用户 {@link org.apache.shiro.authz.annotation.RequiresUser RequiresUser}
         * 需要访客 {@link org.apache.shiro.authz.annotation.RequiresGuest RequiresGuest}
         * 需要角色 {@link org.apache.shiro.authz.annotation.RequiresRoles RequiresRoles}
         * 需要权限 {@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions}
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    
        /**
         * 配置 拦截过滤器链.  map的键 : 资源地址 ;  map的值 : 所有默认Shiro过滤器实例名
         * 默认Shiro过滤器实例 参考 : {@link org.apache.shiro.web.filter.mgt.DefaultFilter}
         */
        private Map<String, String> setFilterChainDefinitionMap() {
            Map<String, String> filterMap = new LinkedHashMap<>();
            //注册 数据库中所有的权限 及其对应url
            List<Permission> allPermission = permissionRepository.findAll();//数据库中查询所有权限
            for (Permission p : allPermission) {
                filterMap.put(p.getUrl(), "perms[" + p.getName() + "]");    //拦截器中注册所有的权限
            }
            filterMap.put("/static/**", "anon");    //公开访问的资源
            filterMap.put("/open/api/**", "anon");  //公开接口地址
            filterMap.put("/logout", "logout");     //配置登出页,shiro已经帮我们实现了跳转
            filterMap.put("/**", "authc");          //所有资源都需要经过验证
            return filterMap;
        }
    }
    

    5 . 用于授权相关注解进行测试的API RestController。AuthorizationApiFacade :

    
    /**
     * Created by EalenXie on 2019/3/26 16:46.
     * 授权相关API 测试
     */
    @RestController
    public class AuthorizationApiFacade {
    
    
        /**
         * 需要 验证 才能访问的api
         */
        @RequestMapping("/requiresAuthentication")
        @RequiresAuthentication
        public Map<String, String> requiresAuthentication() {
            Map<String, String> result = new HashMap<>();
            result.put("msg", "Require Authentication : 需要认证 测试, 能够访问此接口");
            return result;
        }
    
        /**
         * 需要 用户 身份才能访问的api
         */
        @RequiresUser
        @RequestMapping("/requiresUser")
        public Map<String, String> requiresUser() {
            Map<String, String> result = new HashMap<>();
            result.put("msg", "Require User : 需要用户 测试, 能够访问此接口");
            return result;
        }
    
        /**
         * 需要 Guest 身份才能访问的api
         */
        @RequiresGuest
        @RequestMapping("/requiresGuest")
        public Map<String, String> requiresGuest() {
            Map<String, String> result = new HashMap<>();
            result.put("msg", "Require Guest : 需要认证 测试, 能够访问此接口");
            return result;
        }
    
        /**
         * 需要 administrator 角色才能访问的api
         */
        @RequiresRoles("administrator")
        @RequestMapping("/requiresRoles")
        public Map<String, String> requiresRoles() {
            Map<String, String> result = new HashMap<>();
            result.put("msg", "require Roles : 该用户具有 administrator 角色, 能够访问此接口");
            return result;
        }
    
        /**
         * 需要 add 权限才能访问的api
         */
        @RequiresPermissions("add")
        @RequestMapping("/requiresPermissionsAdd")
        public Map<String, String> requiresPermissionsAdd() {
            Map<String, String> result = new HashMap<>();
            result.put("msg", "require Permissions : 该用户具有 add 权限 , 能够访问此接口");
            return result;
        }
    
        /**
         * 需要 delete 权限才能访问的api
         */
        @RequiresPermissions("delete")
        @RequestMapping("/requiresPermissionsDelete")
        public Map<String, String> requiresPermissionsDelete() {
            Map<String, String> result = new HashMap<>();
            result.put("msg", "require Permissions : 该用户具有 delete 权限 , 能够访问此接口");
            return result;
        }
    
    
        /**
         * 公开接口
         */
        @RequestMapping(value = "/open/api/sayHello", method = RequestMethod.POST)
        public Map<String, String> sayHello() {
            Map<String, String> result = new HashMap<>();
            result.put("msg", "这个是公开接口,谁都可以访问");
            return result;
        }
    }
    

    进行测试,用户登陆(zhangsan)测试,zhangsan只具有user角色,只有部分权限。

    登陆成功后,进入到首页。

    访问,/add 则跳转到 新增页面

    访问/delete,因为没有权限会跳转到未授权页面。

    zhangsan只能调用自己拥有角色和权限的api :

    没有相关角色和权限的api不能调用 :

  • 相关阅读:
    day-14 模块的使用,循环导入,模块导入路径优先级,项目的目录结构
    day13-三元表达式,生成式,递归函数,匿名函数
    day12-无参装饰器,迭代器和生成器
    day11-函数对象,函数嵌套,名称空间与作用域,闭包函数,以及装饰器的前言
    一周总结(4)
    大道至简读后感
    一周总结(3)
    一周总结(2)
    一周总结(1)
    关于教室派app的使用体验与建议
  • 原文地址:https://www.cnblogs.com/ealenxie/p/10610741.html
Copyright © 2011-2022 走看看