SpringBoot integrates Shiro to implement role-based access control (RBAC) system simple design from scratch
Technology stack: SpringBoot + shiro + + freemark jpa, because space reasons, here put only a part of the code, complete address of the project: github.com/EalenXie/shiro-rbac-system
1. Create a project named shro-rbac-system and add the basic dependencies to 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 integrates Shiro to implement role-based access control (RBAC) system simple design from scratch</description>
<properties>
<java.version>1.8</java.version>
<author>EalenXie</author>
<description>SpringBoot integrates Shiro to implement a simple design of role-based access control (RBAC) systems</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>
Copy the code
Basic data object relationships:
A user corresponds to one or more roles. A role corresponds to one or more rights. A permission corresponds to the access to the corresponding API or URL resources.Copy the code
1. RBAC basic entity relationship, Permission class (Permission resource) :
/** * Created by EalenXie on 2019/3/25 11:15. * * Permission Created by EalenXie on 2019/3/25 11:15
@Entity
@Table(name = "system_shiro_permission")
public class Permission extends BaseEntity {
@Column(unique = true)
private String name; // The permission name is unique
@Column(unique = true)
private String url; // Access address information is unique
private String description; // Description
/ / omit getter/setter
}
Copy the code
2. The Role class (user Role), where a Role has one or more permissions:
/** * Created by EalenXie on 2019/3/25 11:18. * * Roles There are multiple permissions */
@Entity
@Table(name = "system_shiro_role")
public class Role extends BaseEntity {
@Column(unique = true)
private String name; // The role name is unique
private String description; // Description
@ManyToMany(fetch= FetchType.EAGER)
private List<Permission> permissions; // A user role has multiple permissions
/ / omit getter/setter
}
Copy the code
3. The User class, where a User has one or more roles:
/** * Created by EalenXie on 2019/3/25 11:01. * * User table (User
@Entity
@Table(name = "system_shiro_user")
public class User extends BaseEntity {
@Column(unique = true)
private String username;// Unique user name
private String password;// User password
private String passwordSalt;// User password encryption salt value
@ManyToMany(fetch = FetchType.EAGER)
private List<Role> roles;// User roles A user may have one or more roles
/ / omit getter/setter
}
Copy the code
2. Configure basic information
1. Run the application.yml command to configure basic application information.
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 < Open Session in View >
http:
encoding:
charset: utf-8
enabled: true
Copy the code
2. Configure JPA and basic database access Dao.
public interface PermissionRepository extends JpaRepository<Permission.Integer> {
Permission findByName(String name);
}
Copy the code
public interface RoleRepository extends JpaRepository<Role.Integer> {
Role findByName(String name);
}
Copy the code
public interface UserRepository extends JpaRepository<User.Integer> {
User findByUsername(String username);
}
Copy the code
3. Configure Shiro core Realm based on role access control privileges, UserAuthRealm:
@Component
public class UserAuthRealm extends AuthorizingRealm {
@Resource
private UserRepository userRepository;
/** * The permission core is configured according to the user role and permission in the database */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
User user = (User) principals.getPrimaryPrincipal();
for (Role role : user.getRoles()) { // Get the role
authorizationInfo.addRole(role.getName()); // Add a role
for (Permission permission : role.getPermissions()) { // Obtain permission
authorizationInfo.addStringPermission(permission.getName());// Add permissions}}return authorizationInfo;
}
/** * User login credentials */
@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();// add salt to username
return new SimpleAuthenticationInfo(
user, / / user name
user.getPassword(), / / password
ByteSource.Util.bytes(credentials), / / encryption
getName() //realm name
);
}
/** * Set realm's HashedCredentialsMatcher */
@PostConstruct
public void setHashedCredentialsMatcher(a) {
this.setCredentialsMatcher(hashedCredentialsMatcher());
}
/** * Credential matching: specifies the encryption algorithm, hash times */
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(a) {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");// Hash algorithm: MD5 algorithm is used here;
hashedCredentialsMatcher.setHashIterations(1024);// The number of hashes, such as hashes twice, equals md5(MD5 (""));
returnhashedCredentialsMatcher; }}Copy the code
Shiro core configuration, including basic filter policy, annotation support, etc.
/** * Created by EalenXie on 2019/3/25 15:12. */
@Configuration
public class ShiroConfig {
@Resource
private PermissionRepository permissionRepository;
@Resource
private UserAuthRealm userAuthRealm;
/** * Configure the resource access policy. Web application Shiro core filter configuration */
@Bean
public ShiroFilterFactoryBean factoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
factoryBean.setLoginUrl("/login");/ / login page
factoryBean.setSuccessUrl("/index");/ / home page
factoryBean.setUnauthorizedUrl("/unauthorized");// Unauthorised interface;
factoryBean.setFilterChainDefinitionMap(setFilterChainDefinitionMap()); // Configure the interceptor filter chain
return factoryBean;
}
/** * To configure the SecurityManager, you can configure one or more realms */
@Bean
public SecurityManager securityManager(a) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userAuthRealm);
// securityManager.setRealm(xxxxRealm);
return securityManager;
}
/** * Enable Shiro annotation support. Enable the following annotations to take effect: * Requires authentication {@linkOrg, apache shiro. Authz. The annotation. RequiresAuthentication RequiresAuthentication} * {user needs@linkOrg, apache shiro. Authz. The annotation. RequiresUser RequiresUser} * need visitors {@linkOrg, apache shiro. Authz. The annotation. RequiresGuest RequiresGuest} {* need role@linkOrg, apache shiro. Authz. The annotation. RequiresRoles RequiresRoles} * need permissions {@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions}
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/** * Configure the interceptor filter chain. Map values: all default Shiro filter instance names * Default Shiro filter instance references: {@link org.apache.shiro.web.filter.mgt.DefaultFilter}
*/
private Map<String, String> setFilterChainDefinitionMap(a) {
Map<String, String> filterMap = new LinkedHashMap<>();
// Register all permissions in the database and their corresponding urls
List<Permission> allPermission = permissionRepository.findAll();// Query all permissions in the database
for (Permission p : allPermission) {
filterMap.put(p.getUrl(), "perms[" + p.getName() + "]"); // Register all permissions in the interceptor
}
filterMap.put("/static/**"."anon"); // Expose access to resources
filterMap.put("/open/api/**"."anon"); // Public interface address
filterMap.put("/logout"."logout"); // Configure the logout page. Shiro has already implemented the jump for us
filterMap.put("/ * *"."authc"); // All resources need to be validated
returnfilterMap; }}Copy the code
5. RestController API used to authorize related annotations for testing. AuthorizationApiFacade :
/** * Created by EalenXie on 2019/3/26 16:46
@RestController
public class AuthorizationApiFacade {
/** * The API that requires validation to access */
@RequestMapping("/requiresAuthentication")
@RequiresAuthentication
public Map<String, String> requiresAuthentication(a) {
Map<String, String> result = new HashMap<>();
result.put("msg"."Require Authentication: requires Authentication tests to access this interface.");
return result;
}
/** * API that requires user identity */
@RequiresUser
@RequestMapping("/requiresUser")
public Map<String, String> requiresUser(a) {
Map<String, String> result = new HashMap<>();
result.put("msg"."Require User: requires User tests to be able to access this interface");
return result;
}
/** * The API that requires Guest access */
@RequiresGuest
@RequestMapping("/requiresGuest")
public Map<String, String> requiresGuest(a) {
Map<String, String> result = new HashMap<>();
result.put("msg"."Require Guest: Requires authentication tests to access this interface");
return result;
}
/** * The API that requires the administrator role */
@RequiresRoles("administrator")
@RequestMapping("/requiresRoles")
public Map<String, String> requiresRoles(a) {
Map<String, String> result = new HashMap<>();
result.put("msg"."Require Roles: This user has the administrator role and has access to this interface.");
return result;
}
/** * need add permission to access the API */
@RequiresPermissions("add")
@RequestMapping("/requiresPermissionsAdd")
public Map<String, String> requiresPermissionsAdd(a) {
Map<String, String> result = new HashMap<>();
result.put("msg"."Require Permissions: The user has add Permissions and can access the interface");
return result;
}
/** * need delete permission to access API */
@RequiresPermissions("delete")
@RequestMapping("/requiresPermissionsDelete")
public Map<String, String> requiresPermissionsDelete(a) {
Map<String, String> result = new HashMap<>();
result.put("msg"."Require Permissions: The user has delete permission and can access the interface");
return result;
}
/** * public interface */
@RequestMapping(value = "/open/api/sayHello", method = RequestMethod.POST)
public Map<String, String> sayHello(a) {
Map<String, String> result = new HashMap<>();
result.put("msg"."This is an open interface. Anyone can access it.");
returnresult; }}Copy the code
Perform a test and login (Zhangsan) test. Zhangsan has only the user role and only some permissions.
After login successfully, enter the home page.
Access, /add to jump to the new page
Access /delete because no permission will jump to an unauthorized page.
Zhangsan can only call apis that have their own roles and permissions:
Apis without associated roles and permissions cannot be called: