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不能調用 :