SpringBoot integrates Shiro

1. What is Shiro

Shiro is a security framework provided by Apache. It is a permission management framework that performs authentication, authorization, password and session management. It is very powerful and easy to use;
There is Spring Security in Spring, which is a permissions framework. It is too closely dependent on Spring and is not as easy to use as Shiro;

2. Core components

Subject

The subject is the “current operating user”. The system needs to authenticate and authorize the “current operating user”; it only means what is currently interacting with the software;

SecurityManager

Security Manager, the subject is authenticated and authorized through SecurityManager. SecurityManager is a collection. What really does things is not the SecurityManager but the things in it; equivalent to the front-end controller in Spring MVC;

Realm

Data source, access authentication and authorization related data through realm (originally accessed through database);
The authenticator and authorizer call the data and logic stored in the realm for authorization and authentication;

3. SpringBoot integration

Source: https://blog.csdn.net/chy_18883701161/article/details/107454059

Preliminary preparation:

rely:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!-- 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.3.1</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.41</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.20</version>
</dependency>
\t\t
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>

<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.3</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.4.1</version>
</dependency>

mysql table creation

--User information table--
create table user_info(
id bigint primary key,
user_name varchar(50),
pwdvarchar(300)
) engine = InnoDB default charset = utf8;

--Character information table--
create table role_info(
id bigint primary key,
role_desc varchar(50)
) engine = InnoDB default charset = utf8;

--Permission information table--
create table module_info(
id bigint primary key,
module_code varchar(50),
module_name varchar(50),
parent_module varchar(50)
) engine = InnoDB default charset = utf8;

-- Intermediate table of users and roles
create table user_role(
id bigint primary key,
user_id bigint,
role_id bigint
) engine = InnoDB default charset = utf8;

-- Intermediate table of roles and permissions
create table role_module(
id bigint primary key,
role_id bigint,
module_id bigint
) engine = InnoDB default charset = utf8;

Custom Realm

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lq.mycreate.bean.ModuleInfo;
import com.lq.mycreate.bean.RoleInfo;
import com.lq.mycreate.bean.RoleModule;
import com.lq.mycreate.bean.UserInfo;
import com.lq.mycreate.bean.UserRole;
import com.lq.mycreate.mapper.ModuleInfoMapper;
import com.lq.mycreate.mapper.RoleInfoMapper;
import com.lq.mycreate.mapper.RoleModuleMapper;
import com.lq.mycreate.mapper.UserInfoMapper;
import com.lq.mycreate.mapper.UserRoleMapper;
import org.apache.commons.collections.CollectionUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Custom realm
 */
public class UserRealm extends AuthorizingRealm {<!-- -->
    @Autowired
    private UserInfoMapper userInfoMapper;

    @Autowired
    private RoleInfoMapper roleInfoMapper;

    @Autowired
    private ModuleInfoMapper moduleInfoMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Autowired
    private RoleModuleMapper roleModuleMapper;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {<!-- -->
        System.out.println("Perform authorization operation");
        //Get the subject ID
        String username = (String) principalCollection.getPrimaryPrincipal();

        //Query the user's corresponding roles and permissions based on the subject ID
        //Role
        QueryWrapper<UserInfo> userInfoWrapper = new QueryWrapper<>();
        userInfoWrapper.eq("user_name", username);
        UserInfo userInfo = userInfoMapper.selectOne(userInfoWrapper);
        if (userInfo == null) {<!-- -->
            return null;
        }
        QueryWrapper<UserRole> userRoleWrapper = new QueryWrapper<>();
        userRoleWrapper.eq("user_id", userInfo.getId());
        List<UserRole> userRoles = userRoleMapper.selectList(userRoleWrapper);
        if (CollectionUtils.isEmpty(userRoles)) {<!-- -->
            return null;
        }
        List<RoleInfo> roleInfos = new ArrayList<>();
        Set<String> roles = userRoles.stream().map(userRole -> {<!-- -->
            RoleInfo roleInfo = roleInfoMapper.selectById(userRole.getRoleId());
            if (roleInfo != null) {<!-- -->
                roleInfos.add(roleInfo);
                return roleInfo.getRoleDesc();
            }
            return null;
        }).filter(node -> node != null).collect(Collectors.toSet());


        Set<String> permissions = new HashSet<>();
        roleInfos.stream().forEach(role -> {<!-- -->
            QueryWrapper<RoleModule> roleModuleWrapper = new QueryWrapper<>();
            roleModuleWrapper.eq("role_id", role.getId());
            List<RoleModule> roleModules = roleModuleMapper.selectList(roleModuleWrapper);
            List<String> collect = roleModules.stream().map(roleModule -> {<!-- -->
                ModuleInfo moduleInfo = moduleInfoMapper.selectById(roleModule.getModuleId());
                return moduleInfo.getModuleCode();
            }).collect(Collectors.toList());
            permissions.addAll(collect);
        });

        //Set and return the subject's authorization information
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(roles);
        simpleAuthorizationInfo.setStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {<!-- -->
        System.out.println("Perform authentication operation");
        //Get the subject ID
        String username = (String) token.getPrincipal();

        QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
        wrapper.eq("user_name", username);
        UserInfo userInfo = userInfoMapper.selectOne(wrapper);
        if (userInfo == null) {<!-- -->
            return null;
        }

        String pwd = userInfo.getPwd();

        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, pwd,
                ByteSource.Util.bytes(userInfo.getUserName()), this.getName());
        return authenticationInfo;
    }

    public static void main(String[] args) {<!-- -->
        SimpleHash simpleHash = new SimpleHash("md5", "123456", "wangwu", 2);
        String s = simpleHash.toHex();
        System.out.println(s);
    }
}

Configuring ShiroConfig

import com.lq.mycreate.filter.CustomRolesOrAuthorizationFilter;
import com.lq.mycreate.realm.UserRealm;
import com.lq.mycreate.session.CustomSessionManager;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;


@Configuration
public class ShiroConfig {<!-- -->
    //@Autowired
    //private UserRealm userRealm;

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {<!-- -->
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

        //Set securityManager
        factoryBean.setSecurityManager(securityManager);

        //Some pages need to be logged in before they can be accessed. If not logged in, they will be handed over to the specified URL for processing;
        //If the front and back ends are separated, set the URL that is not mapped by the controller: If the front and back ends are not separated, set it to the URL of the view page
        factoryBean.setLoginUrl("/login");

        //The url to jump to after successful login
        //If the front and back ends are separated, there is no need to set this item
        //factoryBean.setSuccessUrl("/");

        //If there is no permission, the unauthorized will be handed over to the specified URL for processing. The processing method is generally: first verify the login, and then verify whether there is permission;
        //If the front and back ends are not separated, directly set it to the URL of the view page
        factoryBean.setUnauthorizedUrl("/pub/notPermit.html");

        //Set a custom Filter
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
        factoryBean.setFilters(filterMap);

        //Filter execution has a certain order. LinkedHashMap should be used. Using HashMap will cause problems.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        //Exit filter
        filterChainDefinitionMap.put("/logout", "logout");

        //You can access without logging in (verification)
        filterChainDefinitionMap.put("/pub/**", "anon");

        //Users can only access after logging in
        filterChainDefinitionMap.put("/authc/**", "authc");

        //Only users whose role is administrator can access
        //filterChainDefinitionMap.put("/admin/**","roleOrFilter[admin]");

        //Only users with update permission can access
        filterChainDefinitionMap.put("/update", "perms[update]");

        //[]The identification parameter is an array. When there are multiple elements, they are separated by commas. [video_watch, video_download]. The requirements in [] must be met at the same time to pass.

        //LinkedHashMap, the execution order of Filter is the same as the adding order, generally put /** at the end

        //If the previous url does not match the request path, use /** to filter it.
        filterChainDefinitionMap.put("/**", "authc");

        factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return factoryBean;
    }

    /**
     * Get redisManager
     * @return RedisManager
     */
    public RedisManager redisManager(){<!-- -->
        RedisManager redisManager = new RedisManager();
        //Set the address of the redis server. The default is 127.0.0.1:6379
        redisManager.setHost("127.0.0.1:6379");
        return redisManager;
    }

    /**
     * Get RedisSessionDAO
     * @return RedisSessionDAO
     */
    public RedisSessionDAO redisSessionDAO(){<!-- -->
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    /**
     * Get custom sessionManager
     * @return
     */
    @Bean
    public SessionManager sessionManager(){<!-- -->
        CustomSessionManager customSessionManager = new CustomSessionManager();

        //Set session timeout in ms, default 30min
        customSessionManager.setGlobalSessionTimeout(1800_000);

        //Configure session persistence
        customSessionManager.setSessionDAO(redisSessionDAO());
        return customSessionManager;
    }

    public RedisCacheManager redisCacheManager(){<!-- -->
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());

        //Set the expiration time of the key corresponding to the authorization information, unit s. When user permissions change, in addition to updating the database, the corresponding key in redis also needs to be updated.
        //one week
        redisCacheManager.setExpire(24*60*60*7);
        return redisCacheManager;
    }

    /**
     * Configure SecurityManager
     *
     * @return
     */
    @Bean
    public SecurityManager securityManager() {<!-- -->
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        //If the front and back ends are not separated, there is no need to use sessionManager or set up SessionManager related configurations.
        //securityManager.setSessionManager(sessionManager());

        //Use custom CacheManager
        //securityManager.setCacheManager(redisCacheManager());

        //Set Realm, it is recommended to put it at the end, otherwise problems may occur
        securityManager.setRealm(userRealm());
        return securityManager;
    }

    /**
     * Return password matcher
     *
     * @return
     */
    public HashedCredentialsMatcher hashedCredentialsMatcher() {<!-- -->
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        //Set encryption algorithm
        matcher.setHashAlgorithmName("md5");
        //Set the number of encryption times
        matcher.setHashIterations(2);

        //Set the ciphertext to hexadecimal encoding, false to base64 encoding
        matcher.setStoredCredentialsHexEncoded(true);

        return matcher;
    }

    /**
     * Return the custom realm
     *
     * @return
     */
    @Bean
    public UserRealm userRealm() {<!-- -->
        UserRealm userRealm = new UserRealm();
        //Set password matcher
        userRealm.setCredentialsMatcher(hashedCredentialsMatcher());

        //cancel cache
        userRealm.setCachingEnabled(false);
        return userRealm;
    }
}

Login control

import com.alibaba.fastjson2.JSON;
import com.lq.mycreate.bean.UserInfo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoginController {<!-- -->

    @RequestMapping("/login")
    public String login(UserInfo userInfo) {<!-- -->
        System.out.println("The parameters of the front-end page are: " + JSON.toJSONString(userInfo));
        UsernamePasswordToken token = new UsernamePasswordToken(userInfo.getUserName(), userInfo.getPwd());
        Subject subject = SecurityUtils.getSubject();
        try {<!-- -->
            subject.login(token);
            return "index.html";
        } catch (Exception ex) {<!-- -->
            ex.printStackTrace();
            return "login.html";
        }
    }

    @RequestMapping("/update")
    public String update() {<!-- -->
        return "update";
    }
}

Custom Filter (not verified)

import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.Set;

/**
 * Custom filters
 */
public class CustomRolesOrAuthorizationFilter extends AuthorizationFilter {<!-- -->
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mapperValue) throws Exception {<!-- -->
        Subject subject = getSubject(request, response);

        //Get the role collection required for the current access path
        String[] roles = (String[]) mapperValue;

        //No role restrictions, can be accessed directly
        if (roles == null || roles.length == 0) {<!-- -->
            return true;
        }

        Set<String> roleSet = CollectionUtils.asSet(roles);

        for (String role : roleSet) {<!-- -->
            if (subject.hasRole(role)) {<!-- -->
                return true;
            }
        }
        return false;
    }
}

Custom SessionManager (not verified)

public class CustomSessionManager extends DefaultWebSessionManager {<!-- -->
    private static final String AUTHORIZATION = "token";

    public CustomSessionManager() {<!-- -->
        super();
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {<!-- -->
        String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        if (sessionId != null) {<!-- -->
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, true);
            return sessionId;
        } else {<!-- -->
            return super.getSessionId(request, response);
        }
    }
}

UserInfoMapper.xml

<?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.lq.mycreate.mapper.UserInfoMapper">
    <resultMap id="UserRole" type="com.lq.mycreate.bean.UserInfo">
        <id property="id" column="uid"/>
        <result property="userName" column="userName"/>
        <result property="pwd" column="pwd"/>
        <!--One-to-many-->
        <collection property="userRoles" ofType="com.lq.mycreate.bean.UserRole">
            <!--One-on-one among many-->
            <association property="roleInfo" javaType="com.lq.mycreate.bean.RoleInfo">
                <result property="id" column="rid"/>
                <result property="roleDesc" column="roleDesc"/>
            </association>
        </collection>
    </resultMap>

    <select id="selectRoleByUserName" resultMap="UserRole">
        select u.id uid,u.user_name userName,u.pwd pwd,r.id rid,r.role_desc roleDesc from user_info u,user_role
        ur,role_info r
        where u.id =ur.user_id and ur.role_id=r.id and user_name = #{userName};
    </select>

</mapper>

Note

An error will be reported when starting: xxx is not eligible for getting processed by all BeanPostProcessors
Processing method: https://blog.csdn.net/u014163312/article/details/124954945

Project code: https://gitee.com/qi_tian_sheng_sheng/SpringBoot.git