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: