Review the permission framework Shiro used 2 years ago

Posted by bentobenji on Thu, 30 Dec 2021 12:25:51 +0100

1, Rights Management Overview

Permission management generally refers to that users can access and only access their authorized resources according to the security rules or security policies set by the system. Permission management appears in almost any system, as long as there are users and passwords. Many people often confuse the concepts of "user identity authentication", "password encryption" and "system management" with the concept of authority management.

Role Based Access Control (RBAC) is the most used function in permission management.
[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-tyto89pm-1640700920586) (/ upload / 2021 / 12 / 1640696519 (1)] -3f53b312c7bd42449d0acb9315987af0 png)
When permission management needs to be used in the project, we can choose to implement it ourselves (RBAC system) or use the framework implemented by a third party.

Realize the necessary functions of permission management system:

  • 1. Permission management (custom permission annotation, loading permission)
  • 2. Role management (add, edit, delete, associate permissions)
  • 3. User management (add, edit, delete, associate users)
  • 4. Login function (define login interceptor and realize login logic and logout function)
  • 5. Permission interception (define permission interceptor and implement interception logic)

What problems can the framework help us solve in the permission management system?

functionWhat the permission framework can do
Authority management×
Role management×
user management ×
Login function√ (password encryption, verification code, remember me)
Permission interception√ (many built-in interceptors, labels / comments / programming methods for authority authentication)
Other functions√ (cache, session management, etc.)

Here we introduce two common permission management frameworks:

1,Apache Shiro

Apache Shiro is a powerful and easy-to-use Java security framework. More and more people use Apache Shiro. It can realize the functions of authentication, authorization, password and session management.

2,Spring Security

Spring Security is also a popular security rights management framework, which is closely combined with spring.

3. Comparison between Shiro and Spring Security

Shiro is easier to use and understand than Spring Security,
Shiro can run independently without binding to any framework or container, and Spring Security must have a Spring environment,
Shiro may not be as powerful as Spring Security, but it may not need so complex things in actual work, so a small and simple Shiro is enough. There is no need to tangle about which of them is better. It would be better to solve the project problems more simply.

2, Shiro overview

1. What can Shiro do

Shiro can help us complete: authentication, authorization, encryption, session management, integration with the Web, caching, etc.

2. Shiro architecture

Shiro's main components include:
Subject,SecurityManager,Authenticator,Authorizer,SessionManager,CacheManager,Cryptography,Realms.
[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (IMG lkztwjsm-1640700920594) (/ upload / 2021 / 12 / 1640696637 (1)] - ecf4f09271544c0bb2ac00ac49427a6a png)

  • Subject (user): the user accessing the system. The subject can be a user, a program, etc., and the person authenticating is called the subject; the term subject is a professional term, which basically means "current operating user". You can use: Subject currentUser = SecurityUtils.getSubject() to obtain the subject subject object anywhere in the program, Similar to employee user = usercontext getUser().

  • SecurityManager (Security Manager): it is the core of Shiro function implementation. It is responsible for interacting with other components (authenticator, authorizer and cache controller) introduced later to realize various functions entrusted by Subject. It is somewhat similar to the DispatcherServlet front-end controller in Spring MVC, which is responsible for distribution scheduling.

  • Realms (data source): the Realm acts as a "bridge" or "connector" between Shiro and application security data. You can regard the Realm as a DataSource, that is, a secure data source. When performing authentication (login) and authorization (access control), Shiro will look up relevant comparison data from the Realm configured by the application to confirm whether the user is legal and the operation is reasonable.

3, Shiro certification and authorization

Shiro certification

The authentication process is the user's identity confirmation process. The realized function is the familiar login authentication. The user enters the account and password and submits it to the background. The background checks the correctness of the account and password by accessing the database.
There are generally two situations of login failure:

  1. Account error org apache. shiro. authc. UnknownAccountException
  2. Wrong password org apache. shiro. authc. IncorrectCredentialsException
    Analysis of authentication process in Web Environment

Shiro can be used not only in the Web environment, but also in Java se.

Shiro authorization

  • Authorization function in the system
    This is the process of assigning related permissions to users.
  • Authentication function in the system
    The process of judging whether the current user has access to a resource.

If the user's permissions cannot be managed in the system, there will be problems such as customer information disclosure and malicious data tampering. Therefore, in most applications, we will have permission management function.
Analysis of authorization process in Web Environment

4, How to configure Shiro in spring

1,stay web.xml Medium configuration Shiro of Filter
2,stay Spring Configuration in configuration file Shiro
3,Configure customization Realm: Implement custom authentication and authorization
4,to configure Shiro Cache policy used by entity classes
5,to configure SecurityManager
6,Configuration assurance Shiro inside Bean The declaration cycle is implemented Lifecycle Bean Post Processors 
7,to configure AOP Method level permission check
8,to configure Shiro Filter

5, How to configure Shiro in springboot

1.0 import jar package to modify POM xml

<properties>
<shiro.version>1.7.1</shiro.version>
<thymeleaf.extras.shiro.version>2.0.0</thymeleaf.extras.shiro.version>
</properties>
<!--introduce shiro Permission framework integration-->
<!--Shiro Core dependency -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>${shiro.version}</version>
</dependency>
<!--  Spring integrate Shiro rely on -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>${shiro.version}</version>
</dependency>
<!-- Shiro use EhCache Cache dependency -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>${shiro.version}</version>
</dependency>
<!-- Thymeleaf Template engine and Shiro Integrated dependency -->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>${thymeleaf.extras.shiro.version}</version>
</dependency>

2.0 Web environment configuration

package com.itzhouwei.web.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.itzhouwei.realm.EmployeeRealm;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.realm.Realm;

import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

@Configuration
public class ShiroConfig {


    /*****
     * Inherit shiro and use its caching technology
     */
    @Bean

    public EhCacheManager getEhCacheManager(){
        EhCacheManager ehCacheManager=new EhCacheManager();
        ehCacheManager.setCacheManagerConfigFile("classpath:ehcache/ehcache-shiro.xml");
        return ehCacheManager;
    }

    /***
     * Using the shiro tag of the template file has an effect
     */
    @Bean
    public ShiroDialect shiroDialect(){
        return  new ShiroDialect();
    }

    /***
     * md5 encryption and salt processing using shiro framework
     *
     */
    @Bean
     public HashedCredentialsMatcher credentialsMatcher(){
         HashedCredentialsMatcher hash=new HashedCredentialsMatcher("md5");
         hash.setHashIterations(3);
         return hash;
     }
    /***
     * Integrated shiro returns a custom data source for authentication login and authorized access control
     * @return
     */
    @Bean
    public Realm employeeRealm(){
        EmployeeRealm employeeRealm = new EmployeeRealm();
        employeeRealm.setCredentialsMatcher(credentialsMatcher());
        //Set cache manager for Realm
        employeeRealm.setCacheManager(getEhCacheManager());
        return  employeeRealm;
    }

    /****
     * JSESSIONID will appear when solving integration shiro redirection,
     * An error of 400 will appear on the page
     * @return
     */
    @Bean
    public DefaultWebSessionManager sessionManager(){
        DefaultWebSessionManager  sessionManager=new DefaultWebSessionManager();
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }

    /***
     * Integrated shiro configures a security manager Bean
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        securityManager.setRealm(employeeRealm());
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

    /****
     * The core security interface of Shiro is returned to configure which resources can be accessed without authorization
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(){
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        shiroFilterFactoryBean.setLoginUrl("/static/login.html");
        Map<String,String>map=new LinkedHashMap<>();
        //Exclude static resources that do not need to be intercepted and login and logout
        //anon requests for this path can be accessed without logging in
        map.put("/favicon.ico","anon");
        map.put("/static/**","anon");
        map.put("/login","anon");
        //After logging out, the user information in shiro will be automatically cleaned up
        map.put("/logout","logout");
        //All requests must be intercepted
        //Except for the resources excluded above, other resources can only be accessed after logging in
        map.put("/**","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }
    /**
     * Open Shiro annotation Notifier
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
        AuthorizationAttributeSourceAdvisor auth=new AuthorizationAttributeSourceAdvisor();
        auth.setSecurityManager(securityManager());
        return auth;
    }

    /**
     * Set to use CGlib proxy instead
     * See DefaultAopProxyFactory#createAopProxy for details
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator creator=new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

}

3.0 custom realm

package com.itzhouwei.realm;

import com.itzhouwei.domain.Employee;
import com.itzhouwei.domain.Role;
import com.itzhouwei.service.IEmployeeService;
import com.itzhouwei.service.IPermissionService;
import com.itzhouwei.service.IRoleService;

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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.stream.Collectors;

public class EmployeeRealm extends AuthorizingRealm {

    @Autowired
    private IEmployeeService service;

    @Autowired
    private IRoleService  roleService;
    @Autowired
    private IPermissionService permissionService;

    /****
     * Authorization (authority judgment)
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        Employee employee = (Employee) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo  info=new SimpleAuthorizationInfo();
        //Super administrator
        if (employee.isAdmin()){
            List<String> roles=roleService.selectAll().stream().map(Role::getSn).collect(Collectors.toList());
            //The role does not support generic configuration, so you need to query it in the database yourself
            info.addRoles(roles);
            info.addStringPermission("*:*");
            return info;
        }
        // Query the role code owned by the user according to the user's id
        List<String> roles=  roleService.queryByEmployee(employee.getId());
        info.addRoles(roles);
        // Query the permission expression owned by the user according to the user's id
        List<String> list = permissionService.queryByEmployeeId(employee.getId());
        info.addStringPermissions(list);
        return info;
    }

    /*****
     * Perform authentication (login) operation
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String  username = (String) authenticationToken.getPrincipal();
        Employee employee=service.queryByUserName(username);
        if (employee==null) {
            return null;
        }
        return new SimpleAuthenticationInfo(employee,employee.getPassword(),
                 ByteSource.Util.bytes(employee.getSalt()),
                this.getName());
    }
}

4.0 modify the login method in the login control layer

package com.itzhouwei.web.controller;


import com.itzhouwei.exception.BusinessException;
import com.itzhouwei.exception.BusinessExceptionCode;
import com.itzhouwei.qo.JsonResult;

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.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;



@Controller
public class LoginController {
    @ResponseBody
    @RequestMapping("/login")
    public JsonResult login(String username, String password){
      try{
          UsernamePasswordToken token=new UsernamePasswordToken(username,password);
          Subject subject= SecurityUtils.getSubject();
          System.out.println(subject);
          subject.login(token);
          subject.getSession().setAttribute("EMPLOYEE_IN_SESSION",subject.getPrincipal());
          return new JsonResult(true,"Login succeeded");
      }catch (UnknownAccountException ignored){
          throw  new BusinessException(BusinessExceptionCode.UNKNOWN_ACCOUNT_ERROR);
      }catch (IncorrectCredentialsException e){
          throw  new BusinessException(BusinessExceptionCode.LOGIN_USER_ERROR);
      }catch (Exception e){
          e.printStackTrace();
          return new JsonResult(false, "Login exception, please contact the administrator");
      }
    }

    @RequestMapping("/logout")
    public String logout(){
     return "redirect:/static/login";
    }
}

5.0 annotation is completed by placing the annotation provided by Shiro on the processing method of the controller.

    // @RequiresRoles("hr") / / determine roles
    @RequiresPermissions("employee:saveOrUpdate") //Judgment authority
    @RequestMapping("/saveOrUpdate")
    @ResponseBody
    public JsonResult saveOrUpdate(Model model, Employee emp,Long [] roleIds){
        try{
            if (emp.getId()!=null){
                service.update(emp,roleIds);
            }else{
                service.insert(emp,roleIds);
            }
        }catch (Exception ex){
            return new JsonResult(false,ex.getMessage());
        }
        return new JsonResult(true,"success");
    }

6.0 configure template page button control

6.1 Add the following constraint header to the page
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
6.2 Use labels on pages
<a href="#" class="btn btn-success btn-input" style="margin: 10px" shiro:hasPermission="department:saveOrUpdate">
    <span class="glyphicon glyphicon-plus"></span> add to
</a>

7.0 integrate EhCache cache to avoid multiple queries to the database

7.1 create ehcache / ehcache Shiro. In the resource directory XML file

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <defaultCache
        maxElementsInMemory="1000"
        eternal="false"
        timeToIdleSeconds="600"
        timeToLiveSeconds="600"
        memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>
parameterexplain
maxElementsInMemoryMaximum number of cache objects
eternalWhether the object is permanently valid. Once set, timeout will not work
timeToIdleSecondsThe idle time of an object refers to how long the object will become invalid without being accessed (unit: seconds). It is only used when eternal = false. The object is not permanently valid. It is optional. The default value is 0, that is, the idle time is infinite.
timeToLiveSecondsObject survival time refers to the time (in seconds) required for an object from creation to expiration. It is only used when eternal = false, and the object is not permanently valid. The default is 0, that is, the object survival time is infinite.
memoryStoreEvictionPolicyWhen the maxElementsInMemory limit is reached, Ehcache will clean up the memory according to the specified policy.
Cache obsolescence policy:
strategyexplain
LRUBy default, the least recently used and the longest unused elements will be cleared out of the cache
FIFOFirst in, first out. If a data enters the cache first, it should be eliminated first
LFULess use means that the elements that have been used least all the time have a hit attribute (hit rate). The elements with the lowest hit value will be cleared out of the cache

Topics: Java Spring Spring Boot Thymeleaf security