SpringBoot整合Shiro實(shí)現(xiàn)基于角色的權(quán)限訪問控制(RBAC)系統(tǒng)簡單設(shè)計(jì)從零搭建

SpringBoot整合Shiro實(shí)現(xiàn)基于角色的權(quán)限訪問控制(RBAC)系統(tǒng)簡單設(shè)計(jì)從零搭建

技術(shù)棧 : SpringBoot + shiro + jpa + freemark ,因?yàn)槠?,這里只貼了部分代碼說明,完整項(xiàng)目地址 : https://github.com/EalenXie/shiro-rbac-system

1 . 新建一個(gè)項(xiàng)目名為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實(shí)現(xiàn)基于角色的權(quán)限訪問控制(RBAC)系統(tǒng)簡單設(shè)計(jì)從零搭建</description>

    <properties>
        <java.version>1.8</java.version>
        <author>EalenXie</author>
        <description>SpringBoot整合Shiro實(shí)現(xiàn)基于角色的權(quán)限訪問控制(RBAC)系統(tǒng)簡單設(shè)計(jì)</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>

基本的數(shù)據(jù)對(duì)象關(guān)系 :

一個(gè)用戶對(duì)應(yīng)一個(gè)或者多個(gè)角色。
一個(gè)角色對(duì)應(yīng)一個(gè)或者多個(gè)權(quán)限。
一個(gè)權(quán)限對(duì)應(yīng)能夠訪問對(duì)應(yīng)的API或url資源。

1 . RBAC基本實(shí)體關(guān)系,Permission類(權(quán)限資源) :


/**
 * Created by EalenXie on 2019/3/25 11:15.
 * <p>
 * 權(quán)限許可(Permission) 操作 及其能訪問url 權(quán)限對(duì)應(yīng)一個(gè)url地址
 */
@Entity
@Table(name = "system_shiro_permission")
public class Permission extends BaseEntity {
    @Column(unique = true)
    private String name;                //權(quán)限名 唯一
    @Column(unique = true)
    private String url;                 //訪問地址信息 唯一
    private String description;         //描述信息
    //省略getter/setter
}

2 . Role類(用戶角色),一個(gè)角色擁有一個(gè)或者多個(gè)權(quán)限 :


/**
 * Created by EalenXie on 2019/3/25 11:18.
 * <p>
 * 角色(Role) 角色下面對(duì)應(yīng)多個(gè)權(quán)限
 */
@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;   //一個(gè)用戶角色對(duì)應(yīng)多個(gè)權(quán)限
    //省略getter/setter
}

3 . User類(用戶),一個(gè)用戶擁有一個(gè)或者多個(gè)角色 :


/**
 * Created by EalenXie on 2019/3/25 11:01.
 * <p>
 * 用戶表(User) 用戶下面對(duì)應(yīng)多個(gè)角色
 */
@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;//用戶角色  一個(gè)用戶可能有一個(gè)角色,也可能有 多個(gè)角色
    //省略getter/setter
}

2 . 基本配置信息 :

1 . 配置應(yīng)用的基本信息,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中默認(rèn)啟用了OSIV(Open Session in View)>
  http:
    encoding:
      charset: utf-8
      enabled: true

2 . 配置JPA,基本的數(shù)據(jù)庫訪問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,基于角色訪問控制權(quán)限的核心Realm,UserAuthRealm :


@Component
public class UserAuthRealm extends AuthorizingRealm {

    @Resource
    private UserRepository userRepository;

    /**
     * 權(quán)限核心配置 根據(jù)數(shù)據(jù)庫中的該用戶 角色 和 權(quán)限
     */
    @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()) {           //獲取 權(quán)限
                authorizationInfo.addStringPermission(permission.getName());//添加 權(quán)限
            }
        }
        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
        );
    }

    /**
     * 設(shè)置 realm的 HashedCredentialsMatcher
     */
    @PostConstruct
    public void setHashedCredentialsMatcher() {
        this.setCredentialsMatcher(hashedCredentialsMatcher());
    }

    /**
     * 憑證匹配 : 指定 加密算法,散列次數(shù)
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:這里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(1024);//散列的次數(shù),比如散列兩次,相當(dāng)于 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應(yīng)用程序 shiro核心過濾器配置
     */
    @Bean
    public ShiroFilterFactoryBean factoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
        factoryBean.setLoginUrl("/login");//登錄頁
        factoryBean.setSuccessUrl("/index");//首頁
        factoryBean.setUnauthorizedUrl("/unauthorized");//未授權(quán)界面;
        factoryBean.setFilterChainDefinitionMap(setFilterChainDefinitionMap()); //配置 攔截過濾器鏈
        return factoryBean;
    }

    /**
     * 配置 SecurityManager,可配置一個(gè)或多個(gè)realm
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userAuthRealm);
//        securityManager.setRealm(xxxxRealm);
        return securityManager;
    }

    /**
     * 開啟shiro 注解支持. 使以下注解能夠生效 :
     * 需要認(rèn)證 {@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}
     * 需要權(quán)限 {@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的值 : 所有默認(rèn)Shiro過濾器實(shí)例名
     * 默認(rèn)Shiro過濾器實(shí)例 參考 : {@link org.apache.shiro.web.filter.mgt.DefaultFilter}
     */
    private Map<String, String> setFilterChainDefinitionMap() {
        Map<String, String> filterMap = new LinkedHashMap<>();
        //注冊(cè) 數(shù)據(jù)庫中所有的權(quán)限 及其對(duì)應(yīng)url
        List<Permission> allPermission = permissionRepository.findAll();//數(shù)據(jù)庫中查詢所有權(quán)限
        for (Permission p : allPermission) {
            filterMap.put(p.getUrl(), "perms[" + p.getName() + "]");    //攔截器中注冊(cè)所有的權(quán)限
        }
        filterMap.put("/static/**", "anon");    //公開訪問的資源
        filterMap.put("/open/api/**", "anon");  //公開接口地址
        filterMap.put("/logout", "logout");     //配置登出頁,shiro已經(jīng)幫我們實(shí)現(xiàn)了跳轉(zhuǎn)
        filterMap.put("/**", "authc");          //所有資源都需要經(jīng)過驗(yàn)證
        return filterMap;
    }
}

5 . 用于授權(quán)相關(guān)注解進(jìn)行測(cè)試的API RestController。AuthorizationApiFacade :


/**
 * Created by EalenXie on 2019/3/26 16:46.
 * 授權(quán)相關(guān)API 測(cè)試
 */
@RestController
public class AuthorizationApiFacade {

    /**
     * 需要 驗(yàn)證 才能訪問的api
     */
    @RequestMapping("/requiresAuthentication")
    @RequiresAuthentication
    public Map<String, String> requiresAuthentication() {
        Map<String, String> result = new HashMap<>();
        result.put("msg", "Require Authentication : 需要認(rèn)證 測(cè)試, 能夠訪問此接口");
        return result;
    }

    /**
     * 需要 用戶 身份才能訪問的api
     */
    @RequiresUser
    @RequestMapping("/requiresUser")
    public Map<String, String> requiresUser() {
        Map<String, String> result = new HashMap<>();
        result.put("msg", "Require User : 需要用戶 測(cè)試, 能夠訪問此接口");
        return result;
    }

    /**
     * 需要 Guest 身份才能訪問的api
     */
    @RequiresGuest
    @RequestMapping("/requiresGuest")
    public Map<String, String> requiresGuest() {
        Map<String, String> result = new HashMap<>();
        result.put("msg", "Require Guest : 需要認(rèn)證 測(cè)試, 能夠訪問此接口");
        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 權(quán)限才能訪問的api
     */
    @RequiresPermissions("add")
    @RequestMapping("/requiresPermissionsAdd")
    public Map<String, String> requiresPermissionsAdd() {
        Map<String, String> result = new HashMap<>();
        result.put("msg", "require Permissions : 該用戶具有 add 權(quán)限 , 能夠訪問此接口");
        return result;
    }

    /**
     * 需要 delete 權(quán)限才能訪問的api
     */
    @RequiresPermissions("delete")
    @RequestMapping("/requiresPermissionsDelete")
    public Map<String, String> requiresPermissionsDelete() {
        Map<String, String> result = new HashMap<>();
        result.put("msg", "require Permissions : 該用戶具有 delete 權(quán)限 , 能夠訪問此接口");
        return result;
    }

    /**
     * 公開接口
     */
    @RequestMapping(value = "/open/api/sayHello", method = RequestMethod.POST)
    public Map<String, String> sayHello() {
        Map<String, String> result = new HashMap<>();
        result.put("msg", "這個(gè)是公開接口,誰都可以訪問");
        return result;
    }
}

進(jìn)行測(cè)試,用戶登陸(zhangsan)測(cè)試,zhangsan只具有user角色,只有部分權(quán)限。

image

登陸成功后,進(jìn)入到首頁。

image

訪問,/add 則跳轉(zhuǎn)到 新增頁面

image

訪問/delete,因?yàn)闆]有權(quán)限會(huì)跳轉(zhuǎn)到未授權(quán)頁面。

image

zhangsan只能調(diào)用自己擁有角色和權(quán)限的api :

image
image

沒有相關(guān)角色和權(quán)限的api不能調(diào)用 :

image

分類: Java

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容