SpringBoot-Shiro

preface

Just look at this article some sense of fog, you can go to Github for source download, combined with their own understanding to learn SpringBoot and Shiro integration.

This article explains the integration operation of SpringBoot and Shiro in detail. At the same time, MyBatis is used to obtain the data in the back-end database. Finally, Shiro and Thymeleaf are combined to complete the display of different information when different users log in. For detailed and complete learning, please refer to Zhang Kaitao-Shiro. Here, there is no in-depth study of specific principles. Before learning this article, by default, Shiro has already had basic cognition to learn the integration operation with Spring. It also serves as a Demo to review when you forget.

The premise to prepare

Dependency import xian

Important dependencies also have annotation information associated with them.

  <! -- Shiro and Thymeleaf -->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
        <! --Shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
 <! --MyBatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
 <! For more dependencies, see pom.xml.
Copy the code

Modify the application.yml configuration file

Specific can see/SRC/main/resources/application. The content in the yml

spring:
  datasource:
    name: springboot
    type: com.alibaba.druid.pool.DruidDataSource
    #druid configuration
    druid:
      # mysql driver
      driver-class-name: com.mysql.cj.jdbc.Driver
      # Basic attributes
      url: JDBC: mysql: / / 127.0.0.1:3306 / springboot_shiro? useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
      username: root
      password: 123456

Copy the code

Create tables for SQL statements

Database name: springBoot_shiro Create a table named Users and insert some necessary data.

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users`  (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `perms` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL.PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES (1.'root'.'123456'.'user:add');
INSERT INTO `users` VALUES (2.'maycope'.'123456'.'user:update');
INSERT INTO `users` VALUES (3.'test'.'123456'.'test');

Copy the code

Main information:

The relationship between creating a SpringBoot project and its three-tier architecture is not explained in detail, which is our three-tier architecture model, Controller-service-mapper.

The main configuration for Shiro is in shiroConfig and UserRealm.

Realm

Shiro obtains security data from realms, such as users, roles, and permissions. To authenticate users, The SecurityManager needs to obtain the corresponding users from realms for comparison. You also need to get the user’s role/permissions from Realm to verify that the user can operate. You can think of a Realm as a DataSource.

We need to inherit AuthorizingRealm and enforce two concrete classes, one for authorization and one for identity:

   @Override
// Authorization authentication
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
// Identity authentication
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
Copy the code
The identity authentication
   protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
       /** * 1. Obtain the entered user name from the token. * 2. Compare the obtained user name with the obtained user name in the database to check whether the user can be obtained. * 3. Put the successful user into the corresponding session (note shiro's session) */
        UsernamePasswordToken userToken = (UsernamePasswordToken)(authenticationToken);
        String username = userToken.getUsername();
        User user = userService.getUser(username);
        if(user == null) {
            return  null;
        }

        // Indicates that the user information is added to the session after the successful login
        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession();
        session.setAttribute("loginUser",user);

        System.out.println("Authentication performed => doGetAuthenticationInfo");
        return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
    }
Copy the code
Authority certification
 @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("Authorization performed => doGetAuthorizationInfo");

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        // Get the current login object;
        Subject subject =SecurityUtils.getSubject();
        User currentUser = (User) subject.getPrincipal();// Get the user object
        // Retrieve permission information from the database. Add the corresponding permission information to the permission authentication using addStringPermission.
        info.addStringPermission(currentUser.getPerms());
        System.out.println(currentUser.getPerms());
        return info;
    }

Copy the code

ShiroConfig

For Shiro, much of this is also fixed configuration information.

 @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean (@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();


        // Set the security manager
        bean.setSecurityManager(defaultWebSecurityManager);

        // Set the login request information. Indicates the login address if no login is performed.
        bean.setLoginUrl("/tologin");
        
        // Set the unauthorized page. When you access some pages that require authorization but the current user is not authorized, the following request will be made.
        bean.setUnauthorizedUrl("/noauth");

        // Add a built-in filter
    /** * anon: no authentication is required * authc: access must be authenticated. * perms: a user must have the permission to access a resource. * role: a user must have the permission to access a resource. * /
    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    /** * Authorization operation */
    // Add specific authorization request information for different pages.
    filterChainDefinitionMap.put("/user/add"."perms[user:add]");
    filterChainDefinitionMap.put("/user/update"."perms[user:update]");
    Shiro :hasPermission is not applicable. Shiro :hasPermission is not applicable. Shiro :hasPermission is not applicable.
    filterChainDefinitionMap.put("/user/other"."perms[user:update]");
    // Static resources, corresponding to the resources under the '/resources/static' folder
    filterChainDefinitionMap.put("/css/**"."anon");
    filterChainDefinitionMap.put("/js/**"."anon");

    bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return  bean;
    }

    @Bean(name="securityManager")
    public DefaultWebSecurityManager getdefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        / / associated userRealm
        securityManager.setRealm(userRealm());
        return  securityManager;
    }
    @Bean
    public SessionManager sessionManager(a) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setGlobalSessionTimeout(60 * 60 * 10); / / for 10 minutes
        sessionManager.setSessionDAO(new EnterpriseCacheSessionDAO());
        return sessionManager;
    }

    @Bean
    public UserRealm userRealm(a){
        return  new UserRealm();
    }


    // Integrate shiroDialect; To integrate Shiro with Thymeleaf
    @Bean
    public ShiroDialect getshiroDialect(a){
        return new ShiroDialect();
    }
Copy the code

The specific practices

After the basic configuration above is complete, you can start concrete practices for concrete verification. First of all, we will explain the functions of the part in detail.

The identity authentication

For the authentication series, it is a set of methods that help us to encapsulate, we authenticate the login of the user, and intelligence can help us to return a specific error message.

  1. The address is intercepted first, and/or /index is blocked to redirect to index page.

  2. For urls we already know, such as 8080/user/add, we usually need to set specific interceptor actions, and jump to the login page when there is no login. For Shiro we just need to do bean.setLoginURL (“/tologin”) to jump to the Tologin request when not logged in. The request is received in the control layer to verify the login form.

  3. After entering the username and password, we can view the login source code in detail:

       @RequestMapping("/login")
        public  String login(String username,String password,Model model){
            // Get the current user
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken(username,password);
           try
           {
               System.out.println("Before");
               subject.login(token);// Perform the login method
               System.out.println("After");
               return "index";
           }catch (UnknownAccountException e){
               model.addAttribute("msg"."User name error");
               return "/login";
           }catch (IncorrectCredentialsException e)
           {
               model.addAttribute("msg"."Password error");
               return  "/login"; }}Copy the code

    You can look at the specific console output to see that identity is authenticated during login.

    Before Indicates the authentication => doGetAuthenticationInfo AfterCopy the code

    Specific logical authentication will be authenticated in Realm, and different error messages will be printed for different errors (none of which need to be managed).

    In the following code, we only need to return NULL and Shiro will handle the error message for us.

      User user = userService.getUser(username);
            if(user == null) {
                return  null;
            }
    Copy the code

Authority certification

For the permission authentication series, we mainly have the following configuration, indicating that when accessing the specific page, we need to add different permissions.

 filterChainDefinitionMap.put("/user/add"."perms[user:add]");
    filterChainDefinitionMap.put("/user/update"."perms[user:update]");
Copy the code

Add permission information and perform dynamic configuration based on perMS information obtained from the database.

  // Get the current login object;
        Subject subject =SecurityUtils.getSubject();
        User currentUser = (User) subject.getPrincipal();// Get the user object
        // Retrieve permission information from the database.
        info.addStringPermission(currentUser.getPerms());
Copy the code

See the perms field in our database. There are different permissions for different users. We configure shiro:hasPermission to show different permissions for different users.

If you log in to the root user first, only add is displayed. You can go to the Add page.

Then log in as a Maycope user and you’ll find only updates.

   <div shiro:hasPermission="user:add" class="color">
        <a th:href="@{/user/add}">add</a>
    </div>
    <div shiro:hasPermission="user:update" class="color">
        <a th:href="@{/user/update}">update</a>
    </div>
Copy the code

unauthorized

The perms field is test for our text user, but for the /user/other page

The following permissions are required for access.

filterChainDefinitionMap.put("/user/other"."perms[user:update]");
Copy the code

Therefore, for unauthorized information, we add the following statement to indicate the interception of unauthorized information:

bean.setUnauthorizedUrl("/noauth");
Copy the code

At this point, when we access, we will be redirected to the corresponding unauthorized request, and when we receive, we will be redirected to the corresponding unauthorized page.

Afterword.

For Shiro, the characteristics of personalized customization are very strong, mainly as follows:

  1. Different users with different permissions add different permissions, and verify the page permissions, to simulate different users in the login will have different display effects. Although relatively simple, but the specific implementation logic is perfect.
  2. withMyBatisIn combination with identity authentication, the interception operation of interceptor when accessing the page that needs to be logged in under the unlogged state (if you manually implement it, the amount of code will not be less than 10 times, and there will be bugs) will directly access the known under the unlogged stateURLThe system intercepts the login and switches to the login page. At the same time for the user name error, or password error, we do not need to manually judge. Here’s a suggestion to goShirogithub sourceDownload it and find it/samples/quickstartRun locally and viewQuickstart.javaSource part, you can find that the error information may be processed, also reduce our workload.
  3. This blog mainly on the integration part of a series of learning, using an example to improve the logical thinking, it is suggested that on the basis of this blog, togithubOr is itgiteeDownload perfectThe SpringBootProject, view specificShiroThe use of. If you find this blog and source code helpful you learnShiro, you can go toGithubOrder a small onestar.