SpringBoot整合Shiro實現基於角色的權限訪問控制(RBAC)系統簡單設計從零搭建html
技術棧 : SpringBoot + shiro + jpa + freemark ,由於篇幅緣由,這裏只貼了部分代碼說明,完整項目地址 : https://github.com/EalenXie/shiro-rbac-system java
<?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>
基本的數據對象關係 :mysql
一個用戶對應一個或者多個角色。 一個角色對應一個或者多個權限。 一個權限對應可以訪問對應的API或url資源。
1 . RBAC基本實體關係,Permission類(權限資源) :git
/** * 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類(用戶角色),一個角色擁有一個或者多個權限 :github
/** * 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類(用戶),一個用戶擁有一個或者多個角色 :web
/** * 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 }
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。spring
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 :sql
@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不能調用 :
原文出處:https://www.cnblogs.com/ealenxie/p/10610741.html