about shiro integration in springboot

Shiro is a powerful and easy-to-use Java security framework, which is mainly used for more convenient authentication, authorization, encryption, session management, etc., and can provide security for any application. This course mainly introduces Shiro's authentication and authorization functions.

1. Shiro's three core components

Shiro has three core components: Subject, SecurityManager and Realm. Let's look at the relationship between them first.

1. Subject: authentication subject. It contains two pieces of information: Principals and Credentials. Let's see what these two messages are.

Principals: identity. It can be user name, email, mobile phone number, etc. to identify the identity of a login subject; Credentials: credentials. Common are password, digital certificate and so on.

To put it bluntly, it is something that needs authentication. The most common is the user name and password. For example, when a user logs in, Shiro needs to authenticate his identity, so he needs the Subject authentication Subject.  

2. SecurityManager: security administrator. This is the core of Shiro architecture. It is like the umbrella of all the original components in Shiro. We usually configure the SecurityManager in the project, and most of the developers focus on the Subject authentication Subject. When we interact with the Subject, the SecurityManager actually does some security operations behind it.

3. Realms: realms is a domain. It is a bridge connecting Shiro and specific applications. When it is necessary to interact with security data, such as user account and access control, Shiro will find it from one or more realms. Realm will customize itself in detail below.

1. Shiro identity and authority authentication

1.2 Shiro identity authentication
 

Let's analyze the process of Shiro identity authentication and take a look at an official authentication diagram:

 

Step 1: the application code is calling subject After the login (token) method, an AuthenticationToken instance token representing the identity and credentials of the end user is passed in.

Step 2: delegate the Subject instance to the application's SecurityManager (Shiro's security management) to start the actual authentication. The real certification work begins here.

Step 3, 4, 5: then the SecurityManager will conduct security authentication according to the specific realm. As can be seen from the figure, the realm can be customized.

1.3 Shiro authority authentication

 

Authority authentication, or access control, controls who can access which resources in the application. In authority authentication, the three core elements are: authority, role and user.

permission: the right to operate resources, such as the right to access a page and the right to add, modify, delete and view the data of a module;

Role: refers to the role played by the user. A role can have multiple permissions;

User: in Shiro, it represents the user accessing the system, that is, the Subject authentication Subject mentioned above.

The relationship between them can be represented by the following figure:

 

A user can have multiple roles, and different roles can have different permissions or the same permissions. For example, there are three roles: 1 is an ordinary role, 2 is also an ordinary role, 3 is an administrator, role 1 can only view information, role 2 can only add information, administrators can, and can delete information, similar to this.

2. Spring Boot integration Shiro process

 

2.1 dependency import

Spring Boot 2.0.3 integration Shiro needs to import the following starter dependencies:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>

2.2 initialization of database table data

 

There are three tables involved here: user table, role table and permission table. In fact, in the demo, we can simulate it ourselves without creating a table. However, in order to be closer to the actual situation, we still add mybatis to operate the database. The following is the script of the database table.

CREATE TABLE `t_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key',
`rolename` varchar(20) DEFAULT NULL COMMENT 'Role name',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8

CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'User primary key',
`username` varchar(20) NOT NULL COMMENT 'user name',
`password` varchar(20) NOT NULL COMMENT 'password',
`role_id` int(11) DEFAULT NULL COMMENT 'Foreign key Association role surface',
PRIMARY KEY (`id`),
KEY `role_id` (`role_id`),
CONSTRAINT `t_user_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8

CREATE TABLE `t_permission` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key',
`permissionname` varchar(50) NOT NULL COMMENT 'Permission name',
`role_id` int(11) DEFAULT NULL COMMENT 'Foreign key Association role',
PRIMARY KEY (`id`),
KEY `role_id` (`role_id`),
CONSTRAINT `t_permission_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role`
(`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

Where, t_user,t_ Roles and t_permission stores user information, role information and permission information respectively. After the table is established, we insert some test data into the table.

t_user table:

 

t_role table:

t_permission table:

Explain the permissions here: user: * indicates that the permission can be user:create or others, and * indicates a placeholder, which can be defined by ourselves. The details will be described in Shiro configuration below.

2.2 custom Realm

After having the database tables and data, we start to customize the realm. The customized realm needs to inherit the authorizing realm class, because this class encapsulates many methods, and it also inherits from the realm class step by step. After inheriting the authorizing realm class, we need to rewrite two methods:

doGetAuthenticationInfo() method: used to authenticate the currently logged in user and obtain authentication information

doGetAuthorizationInfo() method: used to grant permissions and roles to users who have successfully logged in

The specific implementation is as follows. The relevant explanations are put in the comments of the code, which is more convenient and intuitive:

/**
* Custom realm
* @author yzgu
*/
public class MyRealm extends AuthorizingRealm {
    @Resource
    private UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection
    principalCollection) {
        // Get user name
        String username = (String) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = new
        SimpleAuthorizationInfo();
        // Set the role for the user. The role information exists t_ From the role table
        authorizationInfo.setRoles(userService.getRoles(username));
        // Set permissions for this user. Permission information exists t_ From permission table
        authorizationInfo.setStringPermissions(userService.getPermissions(username));
        return authorizationInfo;
    }

    @Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    // Get the user name according to the token. If you don't know how the token came from, you can ignore it first. It will be explained below
    String username = (String) authenticationToken.getPrincipal();
    // Query the user from the database according to the user name
    User user = userService.getByUsername(username);
    if(user != null) {
        // Save the current user into the session
        SecurityUtils.getSubject().getSession().setAttribute("user", user);
        // Pass in the user name and password for identity authentication, and return the authentication information
        AuthenticationInfo authcInfo = new
        SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "myRealm");
        return authcInfo;
    } else {
        return null;
    }
    }
}

It can be seen from the above two methods: during authentication, the user corresponding to the user name is found out from the database according to the user name entered by the user. At this time, the password is not involved. That is, when it comes to this step, the user can be found even if the password entered by the user is wrong, and then the correct information of the user can be encapsulated in authcInfo and returned to Shiro. Next, Shiro, It will verify the user name and password entered by the user at the front desk according to the real information. At this time, it will also verify the password. If the verification passes, let the user log in, otherwise jump to the specified page. Similarly, when verifying permissions, first obtain the roles and permissions related to the user name from the database according to the user name, and then package them into the authorization info and return them to Shiro.

 

2.3 Shiro configuration

After the custom realm is written, Shiro needs to be configured. We mainly configure three things: Custom realm, security manager, SecurityManager and Shiro filter. As follows:

Configure custom realm:

@Configuration
public class ShiroConfig {
private static final Logger logger =LoggerFactory.getLogger(ShiroConfig.class);
    /**
    * Inject custom realm
    * @return MyRealm
    */
    @Bean
    public MyRealm myAuthRealm() {
        MyRealm myRealm = new MyRealm();
        logger.info("====myRealm Registration complete=====");
        return myRealm;
    }
}

Configure security manager SecurityManager:

@Configuration
public class ShiroConfig {
private static final Logger logger =LoggerFactory.getLogger(ShiroConfig.class);
    /**
    * Injection Security Manager
    * @return SecurityManager
    */
    @Bean
    public SecurityManager securityManager() {
        // Add custom realm
           DefaultWebSecurityManager securityManager = new
        DefaultWebSecurityManager(myAuthRealm());
        logger.info("====securityManager Registration complete====");
        return securityManager;
    }
}

When configuring SecurityManager, you need to add the above custom realm, so Shiro can go to the custom realm.

To configure Shiro filters:

@Configuration
public class ShiroConfig {
private static final Logger logger =LoggerFactory.getLogger(ShiroConfig.class);
    /**
    * Inject Shiro filter
    * @param securityManager Security Manager
    * @return ShiroFilterFactoryBean
    */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        // Define shiroFactoryBean
        ShiroFilterFactoryBean shiroFilterFactoryBean=new
        ShiroFilterFactoryBean();
        // Set custom securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // Set the default login url, which will be accessed if authentication fails
        shiroFilterFactoryBean.setLoginUrl("/login");
        // Set the link to jump after success
        shiroFilterFactoryBean.setSuccessUrl("/success");
        // Set the unauthorized interface. If the authority authentication fails, the url will be accessed
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
        // LinkedHashMap is ordered and configured as a sequential interceptor
           Map<String,String> filterChainMap = new LinkedHashMap<>();
        // Configure addresses that can be accessed anonymously. You can add them according to the actual situation and release some static resources. anon means release
        filterChainMap.put("/css/**", "anon");
       filterChainMap.put("/imgs/**", "anon");        
        filterChainMap.put("/js/**", "anon");
        filterChainMap.put("/swagger-*/**", "anon");
        filterChainMap.put("/swagger-ui.html/**", "anon");
        // Login url release
        filterChainMap.put("/login", "anon");
        // "/user/admin" The first one needs identity authentication, and authc means identity authentication
        filterChainMap.put("/user/admin*", "authc");
        // "/user/student" The first one needs role authentication and is allowed only when it is "admin"
        filterChainMap.put("/user/student*/**", "roles[admin]");
        // The beginning of "/ user/teacher" requires permission authentication and is allowed only when it is "user:create"
        filterChainMap.put("/user/teacher*/**", "perms[\"user:create\"]");
        // Configure logout filter
        filterChainMap.put("/logout", "logout");
        // Set FilterChainDefinitionMap of shiroFilterFactoryBean
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
        logger.info("====shiroFilterFactoryBean Registration complete====");
        return shiroFilterFactoryBean;
    }
}

When configuring Shiro filter, a security manager will be passed in. It can be seen that this is one ring after another, reall - > SecurityManager - > filter. In the filter, we need to define a shiroFactoryBean, and then add the SecurityManager. Combined with the above code, we can see that the main things to be configured are:

Default login url:

If the authentication fails, the url will be accessed

url to jump to after successful authentication

If the authority authentication fails, the url will be accessed

URLs that need to be intercepted or released: These are all placed in a map

As can be seen from the above code, there are different permission requirements for different URLs in the map. Here is a summary of several commonly used permissions.

 

 

2.4 certification with Shiro

Here, we have finished the preparation of Shiro, and then we start to use Shiro for certification. Let's first design several interfaces:

Interface I: use http://localhost:8080/user/admin To verify authentication

Interface 2: use http://localhost:8080/user/student To verify role authentication

Interface 3: use http://localhost:8080/user/teacher To verify authority authentication

Interface 4: use http://localhost:8080/user/login To achieve user login

Then come to the certification process:

Process 1: directly access interface 1 (not logged in at this time), authentication fails, and jump to login The HTML page allows the user to log in, and the login will request interface 4 to realize the user login function. At this time, Shiro has saved the user information.

Process 2: access interface 1 again (at this time, the user has logged in), the authentication is successful, and jump to success HTML page to display user information.

Process 3: access interface 2, test whether the role authentication is successful.

Process 4: access interface 3, test whether the authority authentication is successful.  

2.4.1 identity, role and authority authentication interface

@Controller
@RequestMapping("/user")
public class UserController {
    /**
    * Identity authentication test interface
    * @param request
    * @return
    */
    @RequestMapping("/admin")
    public String admin(HttpServletRequest request) {
        Object user = request.getSession().getAttribute("user");
        return "success";
    }

    /**
    * Role authentication test interface
    * @param request
    * @return
    */
    @RequestMapping("/student")
    public String student(HttpServletRequest request) {
        return "success";
    }
    /**
    * Authority authentication test interface
    * @param request
    * @return
    */
    @RequestMapping("/teacher")
    public String teacher(HttpServletRequest request) {
        return "success";
    }
}

These three interfaces are very simple. You can directly return to the specified page for display. As long as the authentication is successful, you will jump normally. If the authentication fails, you will jump to the page configured in shreoconfig above for display.

2.4.2 user login interface

@Controller
@RequestMapping("/user")
public class UserController {
    /**
    * User login interface
    * @param user user
    * @param request request
    * @return string
    */
    @PostMapping("/login")
    public String login(User user, HttpServletRequest request) {
        // Create a token based on the user name and password
        UsernamePasswordToken token = new
        UsernamePasswordToken(user.getUsername(), user.getPassword());
        // Get subject authentication principal
        Subject subject = SecurityUtils.getSubject();
        try{
            // Start authentication. This step will jump to our custom realm
            subject.login(token);
            request.getSession().setAttribute("user", user);
            return "success";
        }catch(Exception e){
            e.printStackTrace();
            request.getSession().setAttribute("user", user);
            request.setAttribute("error", "Wrong user name or password!");
            return "login";
        }
    }
}

Let's focus on the analysis of this login interface. First, we will create a token according to the user name and password passed from the front end, then use SecurityUtils to create an authentication subject, and then start calling subject Login (token) starts identity authentication. Note that the token just created is passed here. As described in the note, this step will jump to our custom realm and enter the doGetAuthenticationInfo method. So here, you will understand the parameter token in this method. Then, as analyzed above, start identity authentication.

 

2.4.3 test

 

Finally, start the project and test it: browser request http://localhost:8080/user/admin Identity authentication will be performed. Because you are not logged in at this time, you will jump to the / login interface in IndexController, and then jump to login The HTML page lets us log in. After logging in with the user name and password csdn/123456, we request in the browser http://localhost:8080/user/student Interface, role authentication will be carried out. Because the user role of csdn1 in the database is admin, it is consistent with that in the configuration, and the authentication is passed; We ask again http://localhost:8080/user/teacher Interface, authority authentication will be carried out. Because the user authority of csdn1 in the database is user: *, which meets the user:create in the configuration, the authentication is passed. Next, we click exit and the system will log out and let us log in again. We log in with csdn2 and repeat the above operations. When we perform role authentication and permission authentication, the authentication fails. Because the role and permission of csdn2 in the database are different from those in the configuration, the authentication fails.

 

3. Summary

 

This section mainly introduces the integration of Shiro security framework and Spring Boot. First, it introduces the three core components of Shiro and their functions; Then it introduces Shiro's identity authentication, role authentication and authority authentication; Finally, combined with the code, this paper introduces in detail how Shiro is integrated in Spring Boot, designs a set of test process, and gradually analyzes Shiro's workflow and principle, so that readers can more intuitively experience Shiro's whole workflow. Shiro is widely used. I hope readers can master it and apply it to practical projects.

Tags: Java Shiro Spring Spring Boot

Posted by timolein on Sat, 26 Mar 2022 04:25:34 +0300