Integrated Shiro framework for SpringBoot basic learning (Part 2)

Keywords: Mybatis Shiro Spring Boot Thymeleaf IDEA

preface:

Hello, guys, I'm running snail rz. Of course, you can call me snail Jun. I'm a rookie who has studied Java for more than half a year. At the same time, I also have a great dream, that is, to become an excellent Java Architect one day.

This SpringBoot basic learning series is used to record the whole process of learning the basic knowledge of SpringBoot framework (this series is written with reference to the latest SpringBoot tutorial of crazy God in station B. because it was sorted out before, but it was not released at that time, there may be errors in some places. I hope you can correct them in time!)

After that, I will try to update this series at a faster rate every day. If you haven't learned SpringBoot yet, you can learn from my blog; Of course, the little friends who have studied can also review the basics with me. Finally, I hope I can make progress with you! Come on! Boys!

Due to its long length, I divide it into two blogs. The first one mainly understands Shiro's functions and the construction of the basic environment; The second part mainly studies Shiro's integration of Mybatis and Thymeleaf framework.

Special reminder: previous blog link: Integrated Shiro framework for SpringBoot basic learning (Part 1)

Today we come to the ninth stop of SpringBoot basic learning: integrating Shiro framework (Part 2). No more nonsense, let's start today's learning content!

8.4 Shiro integrates Mybatis

8.4.1 resource dependency of import module

1. Import MySQL database driver resource dependency

<!-- MySQL Database driven resource dependency -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
    <version>5.1.46</version>
</dependency>

2. Import mybatis to integrate springboot resource dependency

<!-- Mybatis-SpringBoot Starter for -->
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>

3. Import Druid data source resource dependency

<!-- Druid Data source resource dependency -->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version>
</dependency>

4. Import lombok resource dependency

<!-- lombok resources dependence -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.10</version>
</dependency>

5. All resource dependencies

<dependencies>
    <!-- Shiro Three cores of:
         Subject: user SecurityManager: Manage all users Realm: Connection data-->
    <!-- springboot integration shiro Resource dependency -->
    <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.1</version>
    </dependency>
    <!-- thymeleaf Resource dependency -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <!-- spring-boot-web resources dependence -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- spring-boot-test resources dependence -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- log4j resources dependence -->
    <!-- https://mvnrepository.com/artifact/log4j/log4j -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <!-- MySQL Database driven resource dependency -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
        <version>5.1.46</version>
    </dependency>
    
    <!-- Starters provided by third parties -->
    <!-- Mybatis-SpringBoot Starter for -->
    <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.3</version>
    </dependency>
    <!-- Druid Data source resource dependency -->
    <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.12</version>
    </dependency>
    <!-- lombok resources dependence -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.10</version>
    </dependency>
    
</dependencies>

8.4.2 building the basic environment of the project

1. Basic structure of project module and preparation of configuration file

1-1 basic structure of project module

1-2 write application.yml configuration file
# Set server port number
server:
  port: 8888

# Set database driver
spring:
  datasource:
    username: root
    password: 123456
    # url connection format under version 8.0
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true
    # Database driver under version 8.0
    driver-class-name: com.mysql.jdbc.Driver

    # Data source using Druid
    type: com.alibaba.druid.pool.DruidDataSource
    # Spring Boot does not inject these attribute values by default, so it needs to be configured by itself
    # Sets the basic configuration of the Druid data source
    # Number of physical connections during initialization: initialization occurs when the init method is explicitly called or when getConnection is called for the first time
    initialSize: 5
    # Minimum number of connection pools
    minIdle: 5
    # Maximum number of connection pools
    maxActive: 20
    # Gets the maximum wait time for a connection, in milliseconds
    maxWait: 60000
    # Interval between running and recycling: that is, the interval between destroying thread detection connections
    timeBetweenEvictionRunsMillis: 300000
    # Minimum recycle time: if it is detected that the last active time and current time of the current connection are greater than the minimum recycle time, the current connection will be closed
    minEvictableIdleTimeMillis: 300000
    # Judge whether the query statement connected to is valid
    validationQuery: Select 1 from dual
    # Detect idle connection time: it is recommended to set it to true to apply for connection detection. If the idle time is greater than the recovery run interval, execute to detect whether the connected query statement is effective
    testWhiteIdle: true
    # Check whether the connection of the imported query language is effective: this configuration will reduce performance, and the default value is false
    testOnBorrow: false
    # Check whether the effective query statement executed when returning the connection is valid: this configuration will reduce performance
    testOnReturn: false
    # Whether to cache the precompiled statement pool: that is, PSCache is used. The default value is false. There is PSCache in MySQL version 5.5 or above. It is recommended to enable it
    poolPreparedStatements: true

    # Configure filters for monitoring interception: monitoring statistics stat; Logging -log4j; Defensive SQL injection: wall
    # If an error occurs when running - java.lang.ClassNotFoundException: org.apache,log4j.Priority
    # Import log4j dependency. Maven warehouse address: https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,log4j,wall
    # Sets the maximum pool precompiled statement size per connection
    maxPoolPreparedStatementPerConnectionSize: 20
    # Set the monitoring statistics interceptor stat that uses the global data source
    useGlobalDataSourceStat: true
    # Set connection attribute: the mergesql attribute of the monitoring statistics interceptor stat is true, that is, this configuration takes effect; The slow SQL time is 0.5 seconds
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

# Configure mybatis related properties
mybatis:
  # Set alias by package
  type-aliases-package: com.kuang.pojo
  # Sets the location of the mapper mapping file
  mapper-locations: classpath:mapper/*.xml

2. Write User entity class, UserMapper interface and mapping file

2-1 writing User entity classes
package com.kuang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

@Data // Import parameterless constructs, set and get methods, and toString methods
@AllArgsConstructor // Import parametric construction
@NoArgsConstructor // The nonparametric structure is constructed again to prevent the nonparametric structure from being covered
// Implement the Serializable interface to serialize the User object
public class User implements Serializable {
    
    private Integer id; // number
    private String name; // user name
    private String pwd; // password
    
}
2-2 writing UserMapper interface
package com.kuang.mapper;

import com.kuang.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Repository // The Dao layer is managed by Spring's IOC container
@Mapper // Register as Mapper interface
public interface UserMapper {
    
    // Obtain the specified user information through the user name
    public User getUserByName(String name);
    
}
2-3 write UserMapper.xml mapping file
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.mapper.UserMapper">

    <!-- Obtain the specified user information through the user name -->
    <select id="getUserByName" resultType="com.kuang.pojo.User"  parameterType="string">
        Select id, name, pwd from mybatis.user where name = #{name}
    </select>

</mapper>

3. Write UserService interface and UserServiceImpl implementation class

3-1 writing UserService interface
package com.kuang.service;

import com.kuang.pojo.User;

public interface UserService {
    
    // Obtain the specified user information through the user name
    public User getUserByName(String name);
    
}
3-2 write UserServiceImpl implementation class
package com.kuang.service.impl;

import com.kuang.mapper.UserMapper;
import com.kuang.pojo.User;
import com.kuang.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

// The Service layer is managed by Spring's IOC container
@Service
public class UserServiceImpl implements UserService {
    
    // Auto inject UserMapper interface
    @Autowired
    private UserMapper userMapper;

    // Obtain the specified user information according to the user name
    @Override
    public User getUserByName(String name) {
        return this.userMapper.getUserByName(name);
    }
    
}

4. Modify the UserRealm configuration class and write the UserController control class

4-1 modifying UserRealm configuration class
package com.kuang.config;

import com.kuang.pojo.User;
import com.kuang.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import sun.security.provider.MD5;

// Customize UserRealm and inherit the AuthorizingRealm class
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * 1.to grant authorization
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("Yes=>to grant authorization doGetAuthorizationInfo");
        // 1.1 get simple authorization info object
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        /**
         * Comment out the previous add user request
         */
        // 1.2 set the value of permission request in authorization info
//        authorizationInfo.addStringPermission("user:addUser");
        // 1.3 get the currently logged in user (i.e. subject object)
        Subject subject = SecurityUtils.getSubject();
        // 1.4 obtain the permission of the current user
        User currentUser = (User) subject.getPrincipal();
        // 1.5 set the permissions of the current user and store them in the authorization info
        authorizationInfo.addStringPermission(currentUser.getPerms());
        // 1.6 obtain the permission of the current user
        String perms = currentUser.getPerms();
        // 1.7 judge whether the permission of the current user is empty
        if(perms!=null) {
            // 1.7.1 set the permissions of the current user and store them in the authorization info
            authorizationInfo.addStringPermission(currentUser.getPerms());
            // 1.7.2 return the authorized authentication information
            return authorizationInfo;
        } else {
            // 1.7.3 if the user's permission is empty, return null value and throw an exception
            return null;
       }
    }

    /**
     * 2.authentication
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        
        System.out.println("Yes=>to grant authorization doGetAuthenticationInfo");
        
        /**
         * 2.2 Connect to database
         */
        // 2.2.1 obtain user authentication information: you can interrupt the point for testing
        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        // 2.2.2 obtain user information through user name acquisition
        User user = userService.getUserByName(userToken.getUsername());
        // 2.2.3 judge whether the user information is empty
        if(user==null) {
          // If the user name does not exist, an exception UnknownAccountException is thrown
          return null;
        }
        
        /** 
         * 2.3 Password authentication
         * Let Shiro do it, and you can encrypt the password (in order to save trouble, we don't encrypt it here)
         */
       return new SimpleAuthenticationInfo("",user.getPwd(),"");
    }
    
}
4-2 writing UserController interface
package com.kuang.controller;

import com.kuang.pojo.User;
import com.kuang.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

@Controller
public class UserController {
    
    /** 
     * Jump to home page
     * Use the @ RequestMapping annotation to set the request mapping path. The request method is get
     * Multiple requests are surrounded by {} and separated by "," 
     */
    @RequestMapping({"/","/index"})
    public String toIndex(Model model) {
        // Set view model information
        model.addAttribute("msg", "Hello,Shiro!");
        // Returns the logical name of the view
        return "index";
    }

    /** 
     * Jump to add page
     * Use the @ RequestMapping annotation to set the request mapping path. The request method is get
     */
    @RequestMapping("/user/addUser")
    public String toAddPage() {
        // Returns the logical name of the view
        return "user/addUser";
    }

    /** 
     * Jump to the modification page
     * Use the @ RequestMapping annotation to set the request mapping path. The request method is get
     */
    @RequestMapping("/user/updateUser")
    public String toUpdatePage() {
        // Returns the logical name of the view
        return "user/updateUser";
    }

    
    /** 
     * Jump to login page
     * Use the @ RequestMapping annotation to set the request mapping path. The request method is get
     */
    @RequestMapping("/toLogin")
    public String toLogin() {
        // Returns the logical name of the view
        return "login";
    }
    
    /**
     * validate logon
     * Use the @ PostMapping annotation to set the request mapping path. The request method is post
     */
    @PostMapping("/login")
    public String login(String username, String password,Model model) {
        // Get current user
        Subject subject = SecurityUtils.getSubject();
        // Encapsulate user login data
        UsernamePasswordToken token = new UsernamePasswordToken(username,MD5Util.MD5EncodeUtf8(password));
        try{
            // Execute the login method. If there is no exception, it indicates that it is OK
            subject.login(token);
            // Set the logical name of the view to index home page
            return "index";
        // Catch unknown user exception
        } catch(UnknownAccountException uae) {
            // Set view model information
            model.addAttribute("msg","User name error");
            // Set the logical name of the view to login page
            return "login";
        // Catch error authentication exception
        } catch (IncorrectCredentialsException ica) {
            // Set view model information
            model.addAttribute("msg","Password error");
            // Set the logical name of the view to login page
            return "login";
        }
    }

}

5. Write test classes and test results

5-1 write shirospprinbootapplicationtests test class
package com.kuang;

import com.kuang.service.impl.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ShiroSpringbootApplicationTests {

    @Autowired
    private UserServiceImpl userService;

    @Test
    void contextLoads() {
        // Print user information obtained by user name
        System.out.println(userService.getUserByName("root"));
    }

}
5-2 test results

Result: the account and password information of user 6 is successfully output in the console!

8.4.3 improve the project environment

1. Modify UserRealm and ShiroConfig configuration classes

1-1 modify UserRealm configuration class
package com.kuang.config;

import com.kuang.pojo.User;
import com.kuang.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import sun.security.provider.MD5;

// Customize UserRealm and inherit the AuthorizingRealm class
public class UserRealm extends AuthorizingRealm {

    // Use the @ Autowired annotation to automatically assemble the UserService interface by type
    @Autowired
    private UserService userService;

    /**
     * 1.to grant authorization
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        
        System.out.println("Yes=>to grant authorization doGetAuthorizationInfo");
        // 1.1 get simple authorization info object
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 1.2 set the value of permission request in authorization info
//        authorizationInfo.addStringPermission("user:addUser");
        // 1.3 get the currently logged in user (i.e. subject object)
        Subject subject = SecurityUtils.getSubject();
        // 1.4 obtain the permission of the current user
        User currentUser = (User) subject.getPrincipal();
        // 1.5 set the permissions of the current user and store them in the authorization info
        authorizationInfo.addStringPermission(currentUser.getPerms());
        // 1.6 obtain the permission of the current user
        String perms = currentUser.getPerms();
        // 1.7 judge whether the permission of the current user is empty
        if(perms!=null) {
            // 1.7.1 set the permissions of the current user and store them in the authorization info
            authorizationInfo.addStringPermission(currentUser.getPerms());
            // 1.7.2 return the authorized authentication information
            return authorizationInfo;
        } else {
            // 1.7.3 if the user's permission is empty, return null value and throw an exception
            return null;
       }
        
    }

    /**
     * 2.authentication
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        
        System.out.println("Yes=>to grant authorization doGetAuthenticationInfo");
        /** 
         * 2.2 Connect to database
         */
        // 2.2.1 obtain user authentication information: you can interrupt the point for testing
        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        // 2.2.2 obtain user information through user name acquisition
        User user = userService.getUserByName(userToken.getUsername());
        // 2.2.3 judge whether the user information is empty
        if(user==null) {
          // If the user name does not exist, an exception UnknownAccountException is thrown
          return null;
        }

        /** 
         * 2.3 Password encryption and authentication
         */
        /**
         * 2.3.1 You can encrypt the password:
         * Use MD5 encryption: e10adc3949ba59abbe56e057f20f883e
         * Use MD5 salt value encryption: e10adc3949ba59abbe56e057f20f883e+username
         */
        // 2.3.2 in the principal parameter, pass in user; Password encryption uses salt value encryption
        return new SimpleAuthenticationInfo(user, MD5Util.MD5EncodeUtf8(user.getPwd()),"");
    }
    
}
1-2 modify ShiroConfig configuration class
package com.kuang.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

// Use the @ Configuration annotation to make ShiroConfig a Configuration class
@Configuration
public class ShiroConfig {
    
    /** 
     * 1.To create a realm object, you need to customize the class
     */
    // 1.1 register userRealm as a component in the IOC container of Spring
    @Bean
    public UserRealm userRealm() {
        // 1.2 the return value is to create a UserRealm object
        return new UserRealm();
    }

    /** 
     * 2.Set defaultwebsecuritymanager (default Web security manager)
     */
    // 2.1 use the @ Bean annotation to register the getDefaultWebSecurityManager method as a component in the IOC container of Spring
    @Bean(name="securityManager")
    // 2.2 use the @ Qualifier annotation to obtain the userrealm object in the SpringdeIOC container through the name userrealm
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        // 2.3 get DefaultWebSecurityManager object
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        // 2.4 associating userRealm objects
        securityManager.setRealm(userRealm);
        // 2.5 return securityManager object
        return securityManager;
    }
    
    /** 
     * 3.Set ShiroFilterFactoryBean(Shiro filter factory Bean)
     */
    // Use the @ Bean annotation to register the getShiroFilterFactoryBean method into the Spring container
    @Bean
    // Use the @ Qualifier annotation to get the DefaultWebSecurityManager object in the Spring container by the name securityManage
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        // 3.1 obtain Shiro's filter factory and set up the security manager
        // 3.1.1 get ShiroFilterFactoryBean object
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 3.1.2 setting the securitymanager object
        factoryBean.setSecurityManager(securityManager);

        /** 
         * 3.2 Add shiro's built-in filter
         * anon: Access without authentication
         * authc: Must be authenticated to access
         * user: You must have the remember me function to use
         * perms: You must have permission on a resource to access it
         * role: You must have a role permission to access
         */
        /** 
         * 3.2.1 intercept
         */
        // Get LinkedHashMap object
        Map<String, String> filterMap = new LinkedHashMap<>();

         /**
          * 3.2.2 to grant authorization
          * Use the Map set to set the key value value corresponding to the permission: key is the corresponding request and value is the permission value
          */
       // #1 "/user/addUser" is a request to add a user, and "perms[user:addUser]" means that you can only access it if you have the permission to add a user
         filterMap.put("/user/addUser", "perms[user:addUser]");
        // #2 "/user/updateUser" is a request to modify a user, "perms[user:updateUser]" means that only users with modify user permission can access it
        filterMap.put("/user/updateUser", "perms[user:updateUser]");
        // #3 "/user / *" for all requests under user, "authc" means that they can only be accessed after authentication
        filterMap.put("/user/*", "authc");

        // 3.2.3 Set filterchaindefinitionmap
        factoryBean.setFilterChainDefinitionMap(filterMap);

        /**
         * 3.3 Set login and unauthorized related requests
         */
        // 3.3.1 setting login request
        factoryBean.setLoginUrl("/toLogin");
        // 3.3.2 request for unauthorized page
        factoryBean.setUnauthorizedUrl("/unAuthorized");

        // 3.4 return factoryBean object
        return factoryBean;
    }
    
}

2. Modify UserController control class

package com.kuang.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserController {

    /** 
     * Jump to home page
     * Use the @ RequestMapping annotation to set the request mapping path. The request method is get
     * Multiple requests are surrounded by {} and separated by "," 
     */
    @RequestMapping({"/","/index"})
    public String toIndex(Model model) {
        // Set view model information
        model.addAttribute("msg", "Hello,Shiro!");
        // Returns the logical name of the view
        return "index";
    }

    /** 
     * Jump to add page
     * Use the @ RequestMapping annotation to set the request mapping path. The request method is get
     */
    @RequestMapping("/user/addUser")
    public String toAddPage() {
        // Returns the logical name of the view
        return "user/addUser";
    }

    /** 
     * Jump to the modification page
     * Use the @ RequestMapping annotation to set the request mapping path. The request method is get
     */
    @RequestMapping("/user/updateUser")
    public String toUpdatePage() {
        // Returns the logical name of the view
        return "user/updateUser";
    }

    
    /** 
     * Jump to login page
     * Use the @ RequestMapping annotation to set the request mapping path. The request method is get
     */
    @RequestMapping("/toLogin")
    public String toLogin() {
        // Returns the logical name of the view
        return "login";
    }
    
    /**
     * validate logon
     * Use the @ PostMapping annotation to set the request mapping path. The request method is post
     */
    @PostMapping("/login")
    public String login(String username, String password,Model model) {
        // Get current user
        Subject subject = SecurityUtils.getSubject();
        // Encapsulate user login data
        UsernamePasswordToken token = new UsernamePasswordToken(username,MD5Util.MD5EncodeUtf8(password));
        try{
            // Execute the login method. If there is no exception, it indicates that it is OK
            subject.login(token);
            // Set the logical name of the view to index home page
            return "index";
        // Catch unknown user exception
        } catch(UnknownAccountException uae) {
            // Set view model information
            model.addAttribute("msg","User name error");
            // Set the logical name of the view to login page
            return "login";
        // Catch error authentication exception
        } catch (IncorrectCredentialsException ica) {
            // Set view model information
            model.addAttribute("msg","Password error");
            // Set the logical name of the view to login page
            return "login";
        }
    }

    /** 
     * Unauthorized
     * Use the @ RequestMapping annotation to set the request mapping path. The request method is get
     */
    @RequestMapping("/unAuthorized")
    // Use the @ ResponseBody annotation to convert the string returned by the method into a JSON string
    @ResponseBody 
    public String unAuthorized() {
        return "Unauthorized, unable to access this page!";
    }

}

3. Modify User entity class and write MD5 tool class

3-1 modifying User entity class
package com.kuang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data // Import parameterless constructs, set and get methods, and toString methods
@AllArgsConstructor // Import parametric construction
@NoArgsConstructor // The nonparametric structure is constructed again to prevent the nonparametric structure from being covered
// Implement the Serializable interface to serialize the User object
public class User implements Serializable {
    
    private Integer id; //number
    private String name; //user name
    private String pwd; //password
    private String perms; //jurisdiction
    
}

3-2 writing MD5Util tool class
package com.kuang.utils;

import java.security.MessageDigest;

public class MD5Util {

    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++)
            resultSb.append(byteToHexString(b[i]));

        return resultSb.toString();
    }

    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }

    /**
     * Return uppercase MD5
     *
     * @param origin Initial value
     * @param charsetname Character set name
     * @return String Salt value encrypted string
     */
    private static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname))
                resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
            else
                resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
        } catch (Exception exception) {
        }
        return resultString.toUpperCase();
    }

    public static String MD5EncodeUtf8(String origin) {

        //Salt value salt encryption
        //origin = origin + PropertiesUtil.getProperty("password.salt", "");
        return MD5Encode(origin, "utf-8");
    }


    private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};

}

4. Modify database information

4-1 modify User table
  • Add the perms field of varchar type to set the user's permissions
4-2 adding permissions to users

4. Authority verification test

4-1 log in with root account
  • Log in as root to test

  • Access the add user page

  • Access the modify user page

4-2 login with admin account
  • Log in to the admin user to test

  • Visit the new user page

  • Access the modify user page

8.5 Shiro integrates thymeleaf

8.5.1 import resource dependency and modify some codes

1. Import shiro to integrate thymeleaf resource dependency

<!-- shiro-thymeleaf Integration package -->
<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

2. Modify ShiroConfig configuration class

package com.kuang.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

// Use the @ Configuration annotation to make ShiroConfig a Configuration class
@Configuration
public class ShiroConfig {

    /** 
     * 1.To create a realm object, you need to customize the class
     */
    // 1.1 register userRealm as a component in the IOC container of Spring
    @Bean
    public UserRealm userRealm() {
        // 1.2 the return value is to create a UserRealm object
        return new UserRealm();
    }

    /** 
     * 2.Set defaultwebsecuritymanager (default Web security manager)
     */
    // 2.1 use the @ Bean annotation to register the getDefaultWebSecurityManager method as a component in the IOC container of Spring
    @Bean(name="securityManager")
    // 2.2 use the @ Qualifier annotation to obtain the userrealm object in the SpringdeIOC container through the name userrealm
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        // 2.3 get DefaultWebSecurityManager object
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        // 2.4 associating userRealm objects
        securityManager.setRealm(userRealm);
        // 2.5 return securityManager object
        return securityManager;
    }
    
    /** 
     * 3.Set ShiroFilterFactoryBean(Shiro filter factory Bean)
     */
    // Use the @ Bean annotation to register the getShiroFilterFactoryBean method into the Spring container
    @Bean
    // Use the @ Qualifier annotation to get the DefaultWebSecurityManager object in the Spring container by the name securityManage
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        // 3.1 obtain Shiro's filter factory and set up the security manager
        // 3.1.1 get ShiroFilterFactoryBean object
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 3.1.2 setting the securitymanager object
        factoryBean.setSecurityManager(securityManager);

        /** 
         * 3.2 Add shiro's built-in filter
         * anon: Access without authentication
         * authc: Must be authenticated to access
         * user: You must have the remember me function to use
         * perms: You must have permission on a resource to access it
         * role: You must have a role permission to access
         */
        /** 
         * 3.2.1 intercept
         */
        // Get LinkedHashMap object
        Map<String, String> filterMap = new LinkedHashMap<>();

         /**
          * 3.2.2 to grant authorization
          * Use the Map set to set the key value value corresponding to the permission: key is the corresponding request and value is the permission value
          */
       // #1 "/user/addUser" is a request to add a user, and "perms[user:addUser]" means that you can only access it if you have the permission to add a user
         filterMap.put("/user/addUser", "perms[user:addUser]");
        // #2 "/user/updateUser" is a request to modify a user, "perms[user:updateUser]" means that only users with modify user permission can access it
        filterMap.put("/user/updateUser", "perms[user:updateUser]");
        // #3 "/user / *" for all requests under user, "authc" means that they can only be accessed after authentication
        filterMap.put("/user/*", "authc");

        // 3.2.3 Set filterchaindefinitionmap
        factoryBean.setFilterChainDefinitionMap(filterMap);

        /**
         * 3.3 Set login and unauthorized related requests
         */
        // 3.3.1 setting login request
        factoryBean.setLoginUrl("/toLogin");
        // 3.3.2 request for unauthorized page
        factoryBean.setUnauthorizedUrl("/unAuthorized");

        // 3.4 return factoryBean object
        return factoryBean;
    }

    /**
     * 4.Integrate ShiroDialect(Shiro dialect): used to integrate shiro thymeleaf
     */
    // 4.1 register ShiroDialect into the Spring container
    @Bean
    public ShiroDialect getShiroDialect() {
        // 4.2 return ShiroDialect object
        return new ShiroDialect();
    }

}

3. Modify UserRealm configuration class

package com.kuang.config;

import com.kuang.pojo.User;
import com.kuang.service.UserService;
import com.kuang.utils.MD5Util;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

// Customize UserRealm and inherit the AuthorizingRealm class
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * 1.to grant authorization
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        
        System.out.println("Yes=>to grant authorization doGetAuthorizationInfo");
        
        // 1.1 get simple authorization info object
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 1.3 get the currently logged in user (i.e. subject object)
        Subject subject = SecurityUtils.getSubject();
        // 1.4 obtain the permission of the current user
        User currentUser = (User) subject.getPrincipal();
        // 1.5 set the permissions of the current user and store them in the authorization info
        authorizationInfo.addStringPermission(currentUser.getPerms());
        // 1.6 obtain the permission of the current user
        String perms = currentUser.getPerms();
        // 1.7 judge whether the permission of the current user is empty
        if(perms!=null) {
            // 1.7.1 set the permissions of the current user and store them in the authorization info
            authorizationInfo.addStringPermission(currentUser.getPerms());
            // 1.7.2 return the authorized authentication information
            return authorizationInfo;
        } else {
            // 1.7.3 if the user's permission is empty, return null value and throw an exception
            return null;
       }
    }

    /**
     * 2.authentication
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        
        System.out.println("Yes=>to grant authorization doGetAuthenticationInfo");
        
        /** 
         * 2.2 Connect to database
         */
        // 2.2.1 obtain user authentication information: you can interrupt the point for testing
        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        // 2.2.2 obtain user information through user name acquisition
        User user = userService.getUserByName(userToken.getUsername());
        // 2.2.3 judge whether the user information is empty
        if(user==null) {
          // If the user name does not exist, an exception UnknownAccountException is thrown
          return null;
        }
        
        /**
         * 3.Get user session information
         */
        // 3.1 get current user
        Subject currentUser = SecurityUtils.getSubject();
        // 3.2 get the session information of the current user
        Session userSession = currentUser.getSession();
        // 3.3 set the session information of the current user and encapsulate the user name into loginUser
        userSession.setAttribute("loginUser",user.getName());

        /**
         * 2.4 Password encryption and authentication
         */
        /**
         * 2.4.1 You can encrypt the password:
         * Use MD5 encryption: e10adc3949ba59abbe56e057f20f883e
         * Use MD5 salt value encryption: e10adc3949ba59abbe56e057f20f883e+username
         */
        // 2.4.2 in the principal parameter, pass in the user; Password encryption uses salt value encryption
        return new SimpleAuthenticationInfo(user, user.getPwd(),"");
    }
    
}

4. Modify UserController control class

package com.kuang.controller;

import com.kuang.utils.MD5Util;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserController {

    /** 
     * Jump to home page
     * Use the @ RequestMapping annotation to set the request mapping path. The request method is get
     * Multiple requests are surrounded by {} and separated by "," 
     */
    @RequestMapping({"/","/index"})
    public String toIndex(Model model) {
        // Set view model information
        model.addAttribute("msg", "Hello,Shiro!");
        // Returns the logical name of the view
        return "index";
    }

    /** 
     * Jump to add page
     * Use the @ RequestMapping annotation to set the request mapping path. The request method is get
     */
    @RequestMapping("/user/addUser")
    public String toAddPage() {
        // Returns the logical name of the view
        return "user/addUser";
    }

    /** 
     * Jump to the modification page
     * Use the @ RequestMapping annotation to set the request mapping path. The request method is get
     */
    @RequestMapping("/user/updateUser")
    public String toUpdatePage() {
        // Returns the logical name of the view
        return "user/updateUser";
    }

    
    /** 
     * Jump to login page
     * Use the @ RequestMapping annotation to set the request mapping path. The request method is get
     */
    @RequestMapping("/toLogin")
    public String toLogin() {
        // Returns the logical name of the view
        return "login";
    }
    
    /**
     * validate logon
     * Use the @ PostMapping annotation to set the request mapping path. The request method is post
     */
    @PostMapping("/login")
    public String login(String username, String password,Model model) {
        // Get current user
        Subject subject = SecurityUtils.getSubject();
        // Encapsulate user login data
        UsernamePasswordToken token = new UsernamePasswordToken(username,MD5Util.MD5EncodeUtf8(password));
        try{
            // Execute the login method. If there is no exception, it indicates that it is OK
            subject.login(token);
            // Set the logical name of the view to index home page
            return "index";
        // Catch unknown user exception
        } catch(UnknownAccountException uae) {
            // Set view model information
            model.addAttribute("msg","User name error");
            // Set the logical name of the view to login page
            return "login";
        // Catch error authentication exception
        } catch (IncorrectCredentialsException ica) {
            // Set view model information
            model.addAttribute("msg","Password error");
            // Set the logical name of the view to login page
            return "login";
        }
    }

    /** 
     * Unauthorized
     * Use the @ RequestMapping annotation to set the request mapping path. The request method is get
     */
    @RequestMapping("/unAuthorized")
    // Use the @ ResponseBody annotation to convert the string returned by the method into a JSON string
    @ResponseBody 
    public String unAuthorized() {
        return "Unauthorized, unable to access this page!";
    }

    /** 
     * Log out
     * Use the @ RequestMapping annotation to set the request mapping path. The request method is get
     */
    @RequestMapping("/logout")
    public String logout(){
        // Get current user information
        Subject currentUser = SecurityUtils.getSubject();
        // Log off the currently logged in user
        currentUser.logout();
        // Return to the index home page
        return "index";
    }
    
}

8.5.2 page modification and login verification test

1. Modify login.html landing page

<!DOCTYPE html>
<!-- introduce thymeleaf Namespace for -->
<html lang="en" xmlns:th=http://www.thymeleaf.org>
<head>
    <meta charset="UTF-8">
    <title>Sign in</title>
</head>
<body>
<h1>Sign in</h1>
<hr/>
<p th:text="${msg}" style="color: red"></p>
<!-- Login verification form -->
<form th:action="@{/login}" method="post">
    <p>
        user name:<input type="text" name="username"/>
    </p>
    <p>
        password:<input type="password" name="password"/>
    </p>
    <p>
        <input type="checkbox"/>Remember me
    </p>
    <input type="submit" value="Sign in" />
</form>

</body>
</html>

2. Modify the index.html home page

<!DOCTYPE html>
<!-- introduce thymeleaf and thymeleaf integration shiro Namespace for -->
<html lang="en" xmlns:th=http://www.thymeleaf.org
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>home page</title>
</head>
<body>
<h1>home page</h1>
    
<!-- 1.Login account- ->
<!-- 1.1 from session Medium judgment value -->
<!-- <div th:if="${session.loginUser==null}">
    <a th:href="@{/toLogin}">Sign in</a>
</div> -->

<!-- 1.2 Determine whether it is authenticated -->
<!--
<div shiro:notAuthenticated="">
    <a th:href="@{/toLogin}">Sign in</a>
</div>-->

<!-- 1.3 Judge whether it is a tourist -->
<!-- <div shiro:guest="true">
    <a th:href="@{/toLogin}">Sign in</a>
</div> -->

<!-- 1.4 judge session Empty: use ternary operator -->
<div>
    <!--If the current user is empty, login will be displayed; If it is not empty, the user's will be displayed session information-->
    <a th:href="@{/toLogin}" th:text="${session.loginUser==null?'Sign in':session.loginUser}"></a>
</div>

<!-- 2.Cancellation of account -->
<!-- 2.1 Judge whether the user is not empty -->
<div th:if="${session.loginUser!=null}">
    <!-- 2.2 If it is not empty, it is allowed to log off -->
    <a th:href="@{/logout}">cancellation</a>
</div>

<!-- use th:text To display information -->
<p th:text="${msg}"></p>

<hr/>

<!-- 3.Set the entry for adding and modifying users -->
<!-- 3.1 have"user:addUser"Permission to see -->
<div shiro:haspermission="'user:addUser'">
    <a th:href="@{/user/addUser}">Add user</a>
</div>
<!-- 3.2 have"user:updateUser"Permission to see -->
<div shiro:haspermission="'user:updateUser'">
    <a th:href="@{/user/updateUser}">Modify user</a>
</div>

</body>
</html>

3. Login verification test

3-1 log in with root account
  • Before logging in

  • Log in to the root account

  • Visit home page successfully

  • Access the modify user page

  • Log out

3-2 login with admin account
  • Log in to the root account

  • Visit home page successfully

  • Visit the new user page

Well, today's study on the Shiro framework for the integration of SpringBoot basic learning (Part 2) is over. Welcome to actively study and discuss with your friends. You can pay attention to snail Jun if you like. By the way, let's have a one click triple connection. See you next time. Bye!

Reference video link: https://www.bilibili.com/video/BV1PE411i7CV([crazy God says Java] the latest tutorial of SpringBoot, the IDEA version is easy to understand)

Posted by wrapper on Mon, 25 Oct 2021 07:56:13 -0700