A. Instructions

Shiro is a security framework, which is mainly used for authentication, authorization, encryption, and user session management in projects. Although Shiro is not as rich as SpringSecurity, it is lightweight and simple, and Shiro is also competent for business requirements in projects.

Ii. Project environment

MyBatis-Plus version: 3.1.0

SpringBoot version: 2.1.5

JDK version: 1.8

Shiro version: 1.4

Shiro-redis plugin version :3.1.0

Data table (SQL file in the project): The password of the test number in the database is encrypted, and the password is 123456

The data table name Chinese name of the table descr
sys_user System user table Based on table
sys_menu Permissions on the table Based on table
sys_role Character sheet Based on table
sys_role_menu Table of roles and permission relationships In the middle of table
sys_user_role User – role relationship table In the middle of table

Maven has the following dependencies:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <! AOP dependencies must be added, otherwise permission interception validation will not take effect -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <! -- Lombok plugin -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <! -- Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
        <! -- mybatisPlus Core library -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.0</version>
        </dependency>
        <! Ali database connection pool -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.6</version>
        </dependency>
        <! -- Shiro core dependencies -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <! -- Shiro-redis plugin -->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.1.0</version>
        </dependency>
        <! -- StringUtilS tool -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.5</version>
        </dependency>
</dependencies>
Copy the code

The configuration is as follows:

# config port
server:
  port: 8764
spring:
  # configure data source
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/my_shiro? serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource
  # Redis data source
  redis:
    host: localhost
    port: 6379
    timeout: 6000
    password: 123456
    jedis:
      pool:
        max-active: 1000  # maximum number of connections in the pool (use negative values to indicate no limit)
        max-wait: - 1      Maximum connection pool blocking wait time (negative value indicates no limit)
        max-idle: 10      The maximum number of free connections in the connection pool
        min-idle: 5       Minimum free connection in connection pool
# Mybatis -plus related configuration
mybatis-plus:
  # XML scan, multiple directories separated by commas or semicolons (to tell Mapper where the corresponding XML file is)
  mapper-locations: classpath:mapper/*.xml
  The following configurations have default values
  global-config:
    db-config:
      # primary key type AUTO:" database ID increment "INPUT:" user INPUT ID",ID_WORKER:" globally unique ID (number type unique ID)", UUID:" globally unique ID UUID";
      id-type: auto
      # field policy IGNORED:" ignore judgment "NOT_NULL:" non-null judgment ") NOT_EMPTY:" Non-null judgment"
      field-strategy: NOT_EMPTY
      # database type
      db-type: MYSQL
  configuration:
    Enable automatic camel name mapping: a similar mapping from database column names to Java attribute camel names
    map-underscore-to-camel-case: true
    True: null is returned if the map is null. False: hidden if the map is null
    call-setters-on-nulls: true
    This configuration prints out the executed SQL for use during development or testing
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
Copy the code

Two. Write the project base class

User entities,Dao,Service, etc. are omitted here, please refer to the source code

Write an Exception class to handle Shiro permission interception exceptions

/ * * *@DescriptionCustom exception *@Author Sans
 * @CreateTime2019/6/15 22:56 * /
@ControllerAdvice
public class MyShiroException {
    /** * Handle Shiro permission blocking exception * If return JSON data format please add@ResponseBodyNote *@Author Sans
     * @CreateTime2019/6/15 "*@ReturnMap<Object> returns the result set */
    @ResponseBody
    @ExceptionHandler(value = AuthorizationException.class)
    public Map<String,Object> defaultErrorHandler(a){
        Map<String,Object> map = new HashMap<>();
        map.put("403"."Insufficient authority");
        returnmap; }}Copy the code

Create the SHA256Util encryption tool

/ * * *@DescriptionSha-256 Encryption tool *@Author Sans
 * @CreateTime2019/6/12 9:27 * /
public class SHA256Util {
    /** Private constructor **/
    private SHA256Util(a){};
    /** Encryption algorithm **/
    public final static String HASH_ALGORITHM_NAME = "SHA-256";
    /** loop number **/
    public final static int HASH_ITERATIONS = 15;
    /** Perform encryption - use SHA256 and salt encryption **/
    public static String sha256(String password, String salt) {
        return newSimpleHash(HASH_ALGORITHM_NAME, password, salt, HASH_ITERATIONS).toString(); }}Copy the code

Creating the Spring tool

/ * * *@DescriptionSpring context utility classes *@Author Sans
 * @CreateTime2019/6/17 "* /
@Component
public class SpringUtil implements ApplicationContextAware {
    private static ApplicationContext context;
    /** * Spring determines if the bean is a subclass of ApplicationContextAware after it is initialized. If it is, the setApplicationContext() method passes in the ApplicationContext from the container as an argument *@Author Sans
     * @CreateTime2019/6/17 16:58 * /
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
    /** * returns the specified Bean * by Name@Author Sans
     * @CreateTime2019/6/17 16:03 * /
    public static <T> T getBean(Class<T> beanClass) {
        returncontext.getBean(beanClass); }}Copy the code

Create the Shiro tool

/ * * *@DescriptionShiro utility classes *@Author Sans
 * @CreateTime2019/6/15 tightly with * /
public class ShiroUtils {

    /** Private constructor **/
    private ShiroUtils(a){}

    private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);

    /** * Get the current user Session *@Author Sans
     * @CreateTime 2019/6/17 17:03
     * @ReturnSysUserEntity User information */
    public static Session getSession(a) {
        return SecurityUtils.getSubject().getSession();
    }

    /** * User logout *@Author Sans
     * @CreateTime2019/6/17 17:23 * /
    public static void logout(a) {
        SecurityUtils.getSubject().logout();
    }

    /** * Get the current user information *@Author Sans
    * @CreateTime 2019/6/17 17:03
    * @ReturnSysUserEntity User information */
    public static SysUserEntity getUserInfo(a) {
      return (SysUserEntity) SecurityUtils.getSubject().getPrincipal();
    }

    /** * Delete user cache information *@Author Sans
     * @CreateTime2019/6/17 and * appear@ParamUsername username *@ParamIsRemoveSession Whether to delete the Session *@Return void
     */
    public static void deleteCache(String username, boolean isRemoveSession){
        // Get Session from cache
        Session session = null;
        Collection<Session> sessions = redisSessionDAO.getActiveSessions();
        SysUserEntity sysUserEntity;
        Object attribute = null;
        for(Session sessionInfo : sessions){
            // Traverses the Session and finds the Session corresponding to the user name
            attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            if (attribute == null) {
                continue;
            }
            sysUserEntity = (SysUserEntity) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();
            if (sysUserEntity == null) {
                continue;
            }
            if (Objects.equals(sysUserEntity.getUsername(), username)) {
                session=sessionInfo;
                break; }}if (session == null||attribute == null) {
            return;
        }
        / / delete the session
        if (isRemoveSession) {
            redisSessionDAO.delete(session);
        }
        // Delete the Cache and reauthorize access to restricted interfacesDefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager(); Authenticator authc = securityManager.getAuthenticator(); ((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute); }}Copy the code

Create Shiro’s SessionId generator

/ * * *@DescriptionCustom SessionId generator *@Author Sans
 * @CreateTimeTruly 2019/6/11 * /
public class ShiroSessionIdGenerator implements SessionIdGenerator {
    /** * implement SessionId generation *@Author Sans
     * @CreateTime2019/6/11 11:54 * /
    @Override
    public Serializable generateId(Session session) {
        Serializable sessionId = new JavaUuidSessionIdGenerator().generateId(session);
        return String.format("login_token_%s", sessionId); }}Copy the code

Write Shiro core classes

Create a Realm for authorization and authentication

/ * * *@DescriptionShiro permission match and account password match *@Author Sans
 * @CreateTime2019/6/15 at * /
public class ShiroRealm extends AuthorizingRealm {
    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private SysRoleService sysRoleService;
    @Autowired
    private SysMenuService sysMenuService;
    /** * Authorization * Shiro will check the user's permission in the cache. If the user does not find the data, Shiro will execute this method to check the permission and put it in the cache *@Author Sans
     * @CreateTime2019/6/12 11:44 * /
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        SysUserEntity sysUserEntity = (SysUserEntity) principalCollection.getPrimaryPrincipal();
        // Get the user ID
        Long userId =sysUserEntity.getUserId();
        // This is where authorization and processing can take place
        Set<String> rolesSet = new HashSet<>();
        Set<String> permsSet = new HashSet<>();
        // Query roles and permissions.
        List<SysRoleEntity> sysRoleEntityList = sysRoleService.selectSysRoleByUserId(userId);
        for (SysRoleEntity sysRoleEntity:sysRoleEntityList) {
            rolesSet.add(sysRoleEntity.getRoleName());
            List<SysMenuEntity> sysMenuEntityList = sysMenuService.selectSysMenuByRoleId(sysRoleEntity.getRoleId());
            for(SysMenuEntity sysMenuEntity :sysMenuEntityList) { permsSet.add(sysMenuEntity.getPerms()); }}// Pass the found permissions and roles into authorizationInfo
        authorizationInfo.setStringPermissions(permsSet);
        authorizationInfo.setRoles(rolesSet);
        return authorizationInfo;
    }
    
    /** * Authentication *@Author Sans
     * @CreateTime 2019/6/12 12:36
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // Get the user's input account.
        String username = (String) authenticationToken.getPrincipal();
        // Use username to find the User object in the database
        // In a real project, this can be done according to the actual situation. If not,Shiro has its own time interval mechanism and will not execute this method again for 2 minutes
        SysUserEntity user = sysUserService.selectUserByName(username);
        // Check whether the account exists
        if (user == null) {
            throw new AuthenticationException();
        }
        // Check whether the account is frozen
        if (user.getState()==null||user.getState().equals("PROHIBIT")) {throw new LockedAccountException();
        }
        // Verify
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user,                                  / / user name
                user.getPassword(),                    / / password
                ByteSource.Util.bytes(user.getSalt()), // Set the salt value
                getName()
        );
        // Start kicking successfully (clear cache and Session)
        ShiroUtils.deleteCache(username,true);
        returnauthenticationInfo; }}Copy the code

Create our SessionManager class

/ * * *@DescriptionObtain a Token * by custom@Author Sans
 * @CreateTime2019/6/13 8:34 * /
public class ShiroSessionManager extends DefaultWebSessionManager {
    // Define constants
    private static final String AUTHORIZATION = "Authorization";
    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
    // Override the constructor
    public ShiroSessionManager(a) {
        super(a);this.setDeleteInvalidSessions(true);
    }
    /** * Shiro will retrieve the Value(Token) of the Authorization key from the request header@Author Sans
     * @CreateTime 2019/6/13 8:47
     */
    @Override
    public Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String token = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        // If a token exists in the request header, the token is obtained from the request header
        if(! StringUtils.isEmpty(token)) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);return token;
        } else {
            // Disable Cookie fetching
            // Fetch the Token from the Cookie according to the default rules
            // return super.getSessionId(request, response);
            return null; }}}Copy the code

Create the ShiroConfig configuration class

/ * * *@DescriptionShiro configuration class *@Author Sans
 * @CreateTime 2019/6/10 17:42
 */
@Configuration
public class ShiroConfig {

    private final String CACHE_KEY = "shiro:cache:";
    private final String SESSION_KEY = "shiro:session:";

    / / Redis configuration
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.password}")
    private String password;

    /** * Enable shiro-AOP annotation support *@AttentionTo use proxy mode, you need to enable code support *@Author Sans
     * @CreateTime2019/6/12 8:38 * /
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /** * Shiro base configuration *@Author Sans
     * @CreateTime2019/6/12 8:42 * /
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // Note that the filter configuration order cannot be reversed
        // Configure filtering: links that cannot be blocked
        filterChainDefinitionMap.put("/static/**"."anon");
        filterChainDefinitionMap.put("/userLogin/**"."anon");
        filterChainDefinitionMap.put("/ * *"."authc");
        // Set the default shiro login interface address. In the separation of front and back ends, the login interface hop should be controlled by the front-end route, and only JSON data is returned in the background
        shiroFilterFactoryBean.setLoginUrl("/userLogin/unauth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /** * Security manager *@Author Sans
     * @CreateTime2019/6/12 joyfully * /
    @Bean
    public SecurityManager securityManager(a) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // Customize Ssession management
        securityManager.setSessionManager(sessionManager());
        // Custom Cache implementation
        securityManager.setCacheManager(cacheManager());
        // Custom Realm validation
        securityManager.setRealm(shiroRealm());
        return securityManager;
    }

    /** * Authenticator *@Author Sans
     * @CreateTime2019/6/12 he * /
    @Bean
    public ShiroRealm shiroRealm(a) {
        ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return shiroRealm;
    }

    /** * The credential matcher passes the password verification to Shiro's SimpleAuthenticationInfo for processing, where the matching configuration is done *@Author Sans
     * @CreateTime2019/6/12 he * /
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(a) {
        HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
        // Hash algorithm: this uses SHA256 algorithm;
        shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);
        // The number of hashes, such as hashes twice, equals md5(MD5 (""));
        shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS);
        return shaCredentialsMatcher;
    }

    /** * Configure the Redis manager *@AttentionThe shro-Redis open source plug-in is used@Author Sans
     * @CreateTime2019/6/12 11:06 * /
    @Bean
    public RedisManager redisManager(a) {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setTimeout(timeout);
        redisManager.setPassword(password);
        return redisManager;
    }

    /** * Configure the Cache manager * to store permissions and role ids to Redis *@AttentionThe shro-Redis open source plug-in is used@Author Sans
     * @CreateTime2019/6/12 children of * /
    @Bean
    public RedisCacheManager cacheManager(a) {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        redisCacheManager.setKeyPrefix(CACHE_KEY);
        // To configure caching, the entity class in session must have an ID
        redisCacheManager.setPrincipalIdFieldName("userId");
        return redisCacheManager;
    }

    /** * SessionID generator *@Author Sans
     * @CreateTime2019/6/12 * /
    @Bean
    public ShiroSessionIdGenerator sessionIdGenerator(a){
        return new ShiroSessionIdGenerator();
    }

    /** * RedisSessionDAO *@AttentionThe shro-Redis open source plug-in is used@Author Sans
     * @CreateTime2019/6/12 "* /
    @Bean
    public RedisSessionDAO redisSessionDAO(a) {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
        redisSessionDAO.setKeyPrefix(SESSION_KEY);
        redisSessionDAO.setExpire(timeout);
        return redisSessionDAO;
    }

    /** * Configure Session manager *@Author Sans
     * @CreateTime2019/6/12 now * /
    @Bean
    public SessionManager sessionManager(a) {
        ShiroSessionManager shiroSessionManager = new ShiroSessionManager();
        shiroSessionManager.setSessionDAO(redisSessionDAO());
        returnshiroSessionManager; }}Copy the code

4. Realization of permission control

Shiro can use code or annotations to control permissions. Usually we use annotations to control permissions, which is not only simple and convenient, but also more flexible. There are five Shiro notes:

Note the name instructions
RequiresAuthentication The current Subject must be authenticated in the current session when classes, methods, etc., annotated with this annotation are accessed.
RequiresGuest Classes, methods, etc., annotated with this annotation can be accessed as “gust” and do not need to be authenticated or recorded in the previous session.
RequiresUser To verify that a user is remembered, there are two meanings: first, if the user is successfully logged in (subject.isauthenticated () is true); The other isRemembered(subject.isremembered () results true).
RequiresPermissions The current Subject needs to have certain permissions in order to execute the method annotated by the annotation. Without permission, the method will not execute and an AuthorizationException will be thrown.
RequiresRoles The current Subject must have all the specified roles in order to access the methods annotated by the annotation. If there is no role, the method will not execute and an AuthorizationException will be thrown.
RequiresPermissions AND RequiresRoles are the most commonly used permissions for projects, allowing multiple roles AND permissions. The default logic is AND, which allows access to methods only if you have both. You can specify OR as an argument in annotations
“` java
The sample
// Have a role to access
@RequiresRoles(value={“ADMIN”,”USER”},logical = Logical.OR)
// Only with all permissions
@RequiresPermissions(value={“sys:user:info”,”sys:role:info”},logical = Logical.AND)
` ` `
Order of use :Shiro annotations exist in order. When multiple annotations are on a method, they are checked one by one until they all pass. The default order of intercession is RequiresRoles->RequiresPermissions->RequiresAuthentication->
RequiresUser->RequiresGuest
“` java
The sample
// Have the ADMIN role and sys:role:info permission
@RequiresRoles(value={“ADMIN”)
@RequiresPermissions(“sys:role:info”)
` ` `
Create the UserRoleController role to intercept the test class
“` java
/ * *
  • @description Role test
  • @Author Sans
  • @CreateTime 2019/6/19 11:38

*/ @RestController @RequestMapping(“/role”) public class UserRoleController {

@Autowired private SysUserService sysUserService; @Autowired private SysRoleService sysRoleService; @Autowired private SysMenuService sysMenuService; @Autowired private SysRoleMenuService sysRoleMenuService; /** * Administrator role test interface * @author Sans * @createTime 2019/6/19 10:38 * @return Map<String,Object> Return result */ @RequestMapping("/getAdminInfo") @RequiresRoles("ADMIN") public Map<String,Object> getAdminInfo(){ Map<String,Object> map = new HashMap<>(); map.put("code",200); Map. put(" MSG "," this is the interface that only the administrator can access "); return map; } /** * User role test interface * @author Sans * @createTime 2019/6/19 10:38 * @return Map<String,Object> Return result */ @RequestMapping("/getUserInfo") @RequiresRoles("USER") public Map<String,Object> getUserInfo(){ Map<String,Object> map =  new HashMap<>(); map.put("code",200); Map. put(" MSG "," this is the interface that only user roles can access "); return map; } /** * Role test interface * @author Sans * @createTime 2019/6/19 10:38 * @return Map<String,Object> Return result */ @RequestMapping("/getRoleInfo") @RequiresRoles(value={"ADMIN","USER"},logical = Logical.OR) @RequiresUser public Map<String,Object> getRoleInfo(){ Map<String,Object> map = new HashMap<>(); map.put("code",200); Map. put(" MSG "," here is the interface that can be accessed by any ADMIN or USER role "); return map; } /** * logout (test logout) * @author Sans * @createTime 2019/6/19 10:38 * @return Map<String,Object @RequestMapping("/getLogout") @RequiresUser public Map<String,Object> getLogout(){ ShiroUtils.logout(); Map<String,Object> map = new HashMap<>(); map.put("code",200); The map. The put (" MSG ", "logout"); return map; }Copy the code

}

Create UserMenuController permission blocking test class Java /** * @description permission test * @author Sans * @createTime 2019/6/19 11:38 */ @RestController @RequestMapping("/menu") public class UserMenuController { @Autowired private SysUserService sysUserService; @Autowired private SysRoleService sysRoleService; @Autowired private SysMenuService sysMenuService; @Autowired private SysRoleMenuService sysRoleMenuService; @author Sans * @createTime 2019/6/19 10:36 * @return Map<String,Object> Result */ @RequestMapping("/getUserInfoList") @RequiresPermissions("sys:user:info") public Map<String,Object> getUserInfoList(){ Map<String,Object> map = new HashMap<>(); List<SysUserEntity> sysUserEntityList = sysUserService.list(); map.put("sysUserEntityList",sysUserEntityList); return map; } @author Sans * @createTime 2019/6/19 10:37 * @return Map<String,Object> Result */ @RequestMapping("/getRoleInfoList") @RequiresPermissions("sys:role:info") public Map<String,Object> getRoleInfoList(){ Map<String,Object> map = new HashMap<>(); List<SysRoleEntity> sysRoleEntityList = sysRoleService.list(); map.put("sysRoleEntityList",sysRoleEntityList); return map; } /** * Get permission information set * @author Sans * @createTime 2019/6/19 10:38 * @return Map<String,Object> Result */ @RequestMapping("/getMenuInfoList") @RequiresPermissions("sys:menu:info") public Map<String,Object> getMenuInfoList(){ Map<String,Object> map = new HashMap<>(); List<SysMenuEntity> sysMenuEntityList = sysMenuService.list(); map.put("sysMenuEntityList",sysMenuEntityList); return map; } @author Sans * @createTime 2019/6/19 10:38 * @return Map<String,Object> Return result */ @RequestMapping("/getInfoAll") @RequiresPermissions("sys:info:all") public Map<String,Object> getInfoAll(){ Map<String,Object> map = new HashMap<>(); List<SysUserEntity> sysUserEntityList = sysUserService.list(); map.put("sysUserEntityList",sysUserEntityList); List<SysRoleEntity> sysRoleEntityList = sysRoleService.list(); map.put("sysRoleEntityList",sysRoleEntityList); List<SysMenuEntity> sysMenuEntityList = sysMenuService.list(); map.put("sysMenuEntityList",sysMenuEntityList); return map; } /** * Add administrator role permission (test dynamic permission update) * @author Sans * @createTime 2019/6/19 10:39 * @param username user ID * @return */ @requestMapping ("/addMenu") public Map<String,Object> addMenu(){// Add permission for the administrator role SysRoleMenuEntity sysRoleMenuEntity = new SysRoleMenuEntity(); sysRoleMenuEntity.setMenuId(4L); sysRoleMenuEntity.setRoleId(1L); sysRoleMenuService.save(sysRoleMenuEntity); // Clear cache String username = "admin"; ShiroUtils.deleteCache(username,false); Map<String,Object> map = new HashMap<>(); map.put("code",200); Map. put(" MSG "," permission added successfully "); return map; }}Copy the code

Create the UserLoginController login class

/ * * *@DescriptionUser login@Author Sans
 * @CreateTime2019/6/17 15:21 * /
@RestController
@RequestMapping("/userLogin")
public class UserLoginController {

    /** * login *@Author Sans
     * @CreateTime2019/6/20 9:21 and * /
    @RequestMapping("/login")
    public Map<String,Object> login(@RequestBody SysUserEntity sysUserEntity){
        Map<String,Object> map = new HashMap<>();
        // Perform authentication
        try{
            // Verify identity and login
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken(sysUserEntity.getUsername(), sysUserEntity.getPassword());
            // Verify that the login is successful
            subject.login(token);
        }catch (IncorrectCredentialsException e) {
            map.put("code".500);
            map.put("msg"."User does not exist or password is incorrect");
            return map;
        } catch (LockedAccountException e) {
            map.put("code".500);
            map.put("msg"."Login failed. The user has been frozen.");
            return map;
        } catch (AuthenticationException e) {
            map.put("code".500);
            map.put("msg"."This user does not exist");
            return map;
        } catch (Exception e) {
            map.put("code".500);
            map.put("msg"."Unknown exception");
            return map;
        }
        map.put("code".0);
        map.put("msg"."Login successful");
        map.put("token",ShiroUtils.getSession().getId().toString());
        return map;
    }
    /** * Not logged in *@Author Sans
     * @CreateTime2019/6/20 9:22 * /
    @RequestMapping("/unauth")
    public Map<String,Object> unauth(a){
        Map<String,Object> map = new HashMap<>();
        map.put("code".500);
        map.put("msg"."Not logged in");
        return map;
    }
    /** * add a user demo interface * this is only used as demo without any permissions and repeated query verification *@Author Sans
     * @CreateTime2020/1/6 9:22 * /
    @RequestMapping("/testAddUser")
    public Map<String,Object> testAddUser(a){
        // Set basic parameters
        SysUserEntity sysUser = new SysUserEntity();
        sysUser.setUsername("user1");
        sysUser.setState("NORMAL");
        // Randomly generate salt values
        String salt = RandomStringUtils.randomAlphanumeric(20);
        sysUser.setSalt(salt);
        // Encrypt
        String password ="123456";
        sysUser.setPassword(SHA256Util.sha256(password, sysUser.getSalt()));
        // Save the user
        sysUserService.save(sysUser);
        // Save the role
        SysUserRoleEntity sysUserRoleEntity = new SysUserRoleEntity();
        sysUserRoleEntity.setUserId(sysUser.getUserId()); // After the user is saved, the ID is returned to the user entity
        sysUserRoleService.save(sysUserRoleEntity);
        // Return the result
        Map<String,Object> map = new HashMap<>();
        map.put("code".0);
        map.put("msg"."Added successfully");
        returnmap; }}Copy the code

5. POSTMAN test

After a successful login, the Redis TOKEN will be returned, because it is a single sign-on. If you log in again, the Redis TOKEN will be returned, and the previous TOKEN will be invalidWhen accessing the interface for the first time,Shiro will directly fetch the permission from the cache. Note that the request header must be set when accessing the interface. ADMIN does not have the permission sys:info:all, so it cannot access the getInfoAll interface. After dynamically assigning the permission,Shiro will clear the cache. When accessing the interface,Shiro will perform the authorization method again, and then put the permission and role data into the cache againAccess add permission test interface, because it is a test, I add permission user ADMIN dead in the inside, after adding permission, call the tool class to clear the cache, we can find that Redis has no cache Access the getInfoAll interface again, because there is no data in the cache,Shiro will reauthorize the query and the interception passes

Vi. Follow-up supplement

As SpringBoot releases become more advanced, the Sirio-reids plugin has been incompatibilities since 2.2.1. For more information, go to github.com/alexxiyang/…

The fix: Thanks to Github’s Manondidi for the fix. Go to github.com/manondidi/s… Replace the Sirio-redis part of the POM with

<groupId>com.github.manondidi</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.10</version>
Copy the code

Seven. Project source code

Yards cloud: gitee.com/liselotte/s…

GitHub:github.com/xuyulong201…

Thank you for reading this article. If you like it, please click “like” and give more stars. Please give your valuable opinions on the shortcomings of this article.