Back-end——"shiro system permission authentication complete demo and explanation

shiro is a very powerful framework for security management and authorization authentication. Can do login authority management, interface authority management. As follows, you can use the page permission button to control whether the user has the query, add, and edit permissions. After completion, the functions are as follows: set permissions for ordinary users after management login, which can be menu permissions, menu list query permissions and list buttons Permissions. The authority of the menu can be controlled only by code logic, and the authority of list query and button interface can be managed by shiro.

 Go directly to the implementation steps.

Big step one: the tables and functions that need to be prepared in the early stage (if the system already has similar tables or functions, or if you don’t need menu management and only do permission management, skip this step)

1, sys_menu (menu table)

2, sys_role_menu (role menu association table <many to many>)

3. sys_authorities (authority table)

4. sys_role_authorities (role authority association table <many to many>)

5. sys_role (role table), sys_user (user table), sys_user_role (user role association table <many to many>). These three tables will be available in general systems.

-------------The above are the tables that need to be prepared. The table relationships, field descriptions, and table building sql have been uploaded to my resources, welcome to download, the link is as follows --------- ----

https://download.csdn.net/download/nienianzhi1744/12015139

One of the points that may be slightly difficult to understand is the sys_authorities table. This table has two questions.

Question 1: What is the authority field in the table, where does the value of this field come from, and what is its function?

Answer 1: The value of this field is the interface path automatically scanned by swagger, such as post:/v1/user/query. After obtaining the interface path, store this string in the table. The function is to add the path to Shiro's permission manager for comparison when the user front-end calls the interface to verify whether the user has permission.

Example 1: For the following UserController list interface method, since the post type and path "/query" are specified on the method, the interface path of this list is post:/v1/user/query (not post:/v1/ user/list), then the path scanned by swagger is also post:/v1/user/query. So how to scan, just need ajax to access the /v2/api-docs interface through get. As for what swagger is, or how to configure it, portal: https://blog.csdn.net/nienianzhi1744/article/details/102622722   (just look at the contents of the swagger part in the document)

Question 2: What are the parent_menu_id and auth_type fields in the table, how do they come from, and what are their functions.

Answer 2: parent_menu_id is the id of the parent menu to which the interface belongs, and auth_type is the interface type (addition, deletion, modification, export, etc.), which is convenient for viewing during authorization. How did it come? Of course, there is a permission management list, and there is an edit button to manually select which menu the current permission belongs to (because swagger will not identify which menu or list the interface belongs to after scanning the system interface. We need to manually select the function) and select the interface type. The function is to make it easier for us to associate interface permissions with menus during authorization management, to achieve batch authorization, and to facilitate understanding. If you only do permission management without menu management, you can add it as the case may be.

 

Big step two:

1. Pom file, add 3 jar packages

<!--权限认证相相关-->
    <dependency>
      <groupId>net.sf.ehcache</groupId>
      <artifactId>ehcache-core</artifactId>
      <version>2.5.0</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-ehcache</artifactId>
      <version>1.2.6</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-all</artifactId>
      <version>1.3.1</version>
    </dependency>

2. Directory structure. Create a shiro directory behind com.xxx, a config directory and a realm directory under the shiro directory.

Two directories add up to 5 files. The functions of these five files are:

ehcache.xml: cache configuration. Due to the addition of the interface authority function, shiro will compare the authority every time the interface is called, but the authority data is stored in the sys_authorities table and sys_role_authorities table of our database. Do I have to check the permissions in the database every time I access an interface? Of course not, because we added the jar package of ehcache above, we check the permissions from the database for the first time after logging in to the system, and then store the checked permissions in the cache, namely ehcache, which can be retrieved from the cache later. Query permissions. As for the process of storing the permissions in the database and then comparing them, just leave it to shiro, no need to write manually. (It does not matter)

EhCacheManager: Cache configuration. The content is relatively simple, assign and paste (this is not important)

RetryLimitHashedCredentialsMatcher: Concurrent processing. The content is relatively simple, assign and paste (this is not important)

ShiroConfig: Shiro configuration class. This class will be scanned when the project starts, similar to the been configuration file of springmvc. Just configure and process the interception resource file filter, ehcache cache manager, automatic login manager, realm authentication and authorization manager. (This is the most important document one)

ShiroRealm: Shiro login authentication and authority authentication manager. (This is the second most important document)

 

 The following five files will be posted in turn.

 ehcache.xml :

<ehcache>
    <diskStore path="java.io.tmpdir"/>
    <!--
 maxElementsInMemory:内存存储数据的个数
 eternal:缓存数据是否永久有效  建议false
 timeToIdleSeconds:最大空闲时间 (s)  空闲时间超出配置,清理内存数据
 timeToLiveSeconds:存活时间(s)
 overflowToDisk: 溢出到磁盘(磁盘最多存储多少个对象) 如果内存超过maxElementsInMemory配置那么放置到配置的磁盘路径上
 diskPersistent: 服务器重启时是否保留磁盘数据
 diskExpiryThreadIntervalSeconds: 每隔多长时间进行线程扫描
 memoryStoreEvictionPolicy:淘汰策略 LRU(最近最少)  FIFO(先进先出 Frist in Frist out)
 -->
    <defaultCache
            maxElementsInMemory="10000"
            timeToIdleSeconds="86400"
            timeToLiveSeconds="86400"
            maxElementsOnDisk="10000000"
            diskExpiryThreadIntervalSeconds="86400"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
    <!-- 设定缓存的默认数据过期策略 -->
    <cache name="shiro"
           maxElementsInMemory="10000"
           timeToIdleSeconds="86400"
           timeToLiveSeconds="86400"
           maxElementsOnDisk="10000000"
           diskExpiryThreadIntervalSeconds="86400"
           memoryStoreEvictionPolicy="LRU">
    </cache>
    <!-- 登录记录缓存 锁定86400秒,即一天 -->
    <cache name="passwordRetryCache"
           maxEntriesLocalHeap="10000"
           eternal="false"
           timeToIdleSeconds="86400"
           timeToLiveSeconds="86400"
           overflowToDisk="false"
           statistics="false">
    </cache>
</ehcache>

EhCacheManager:

import net.sf.ehcache.Ehcache;
import org.apache.commons.io.IOUtils;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCache;
import org.apache.shiro.config.ConfigurationException;
import org.apache.shiro.io.ResourceUtils;
import org.apache.shiro.util.Destroyable;
import org.apache.shiro.util.Initializable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

public class EhCacheManager implements CacheManager, Initializable, Destroyable {
    private static final Logger log = LoggerFactory.getLogger(EhCacheManager.class);
    protected net.sf.ehcache.CacheManager manager;
    private boolean cacheManagerImplicitlyCreated = false;
    /*这里有必要解释一下,这里是为了获取到ehcache的路径,你可能会说不就是在同一个文件夹吗,获取个文件这么费力?,
     *其实这里这么麻烦是考虑到了项目在windows和linux两种不同的系统的路径差异,同时也适用于打jar和war包两种部署方式
    * System.getProperty("user.dir")是获取到当前项目所在的前置路径
    * File.separator是获取当前操作系统下的文件夹分隔符*/
      private String cacheManagerConfigFile = System.getProperty("user.dir")+ File.separator+"src"+File.separator+"main"+File.separator+"java"+
    File.separator+"com"+File.separator+"xxx"+File.separator+"shiro"+File.separator+"config"+File.separator+"ehcache.xml";

    public EhCacheManager() {
    }

    public net.sf.ehcache.CacheManager getCacheManager() {
        return this.manager;
    }

    public void setCacheManager(net.sf.ehcache.CacheManager manager) {
        this.manager = manager;
    }

    public String getCacheManagerConfigFile() {
        return this.cacheManagerConfigFile;
    }

    public void setCacheManagerConfigFile(String classpathLocation) {
        this.cacheManagerConfigFile = classpathLocation;
    }

    protected InputStream getCacheManagerConfigFileInputStream() {
        String configFile = getCacheManagerConfigFile();
        if(cacheManagerConfigFile.contains("/target")){
            cacheManagerConfigFile=cacheManagerConfigFile.replace("/target","");
        }else if(cacheManagerConfigFile.contains("\\target")){
            cacheManagerConfigFile=cacheManagerConfigFile.replace("\\target","");
        }

        InputStream inputStream = null;
        try {
            inputStream = ResourceUtils.getInputStreamForPath(configFile); //原始的输入流
            byte[] b = IOUtils.toByteArray(inputStream);//使用字节数组保存流,实现将流保存到内存中.
            InputStream in = new ByteArrayInputStream(b);//从数组重建输入流
            return in;
        } catch (IOException e) {
            throw new ConfigurationException("Unable to obtain input stream for cacheManagerConfigFile [" + configFile + "]", e);
        } finally {
            IOUtils.closeQuietly(inputStream);//关闭打开文件的原始输入流.
        }
    }

    public final <K, V> Cache<K, V> getCache(String name) throws CacheException {
        if(log.isTraceEnabled()) {
            log.trace("Acquiring EhCache instance named [" + name + "]");
        }

        try {
            Object e = this.ensureCacheManager().getEhcache(name);
            if(e == null) {
                if(log.isInfoEnabled()) {
                    log.info("Cache with name \'{}\' does not yet exist.  Creating now.", name);
                }

                this.manager.addCache(name);
                e = this.manager.getCache(name);
                if(log.isInfoEnabled()) {
                    log.info("Added EhCache named [" + name + "]");
                }
            } else if(log.isInfoEnabled()) {
                log.info("Using existing EHCache named [" + ((Ehcache)e).getName() + "]");
            }

            return new EhCache((Ehcache)e);
        } catch (net.sf.ehcache.CacheException var3) {
            throw new CacheException(var3);
        }
    }

    public final void init() throws CacheException {
        this.ensureCacheManager();
    }

    private net.sf.ehcache.CacheManager ensureCacheManager() {
        try {
            if(this.manager == null) {
                if(log.isDebugEnabled()) {
                    log.debug("cacheManager property not set.  Constructing CacheManager instance... ");
                }

                this.manager = new net.sf.ehcache.CacheManager(this.getCacheManagerConfigFileInputStream());
                if(log.isTraceEnabled()) {
                    log.trace("instantiated Ehcache CacheManager instance.");
                }

                this.cacheManagerImplicitlyCreated = true;
                if(log.isDebugEnabled()) {
                    log.debug("implicit cacheManager created successfully.");
                }
            }

            return this.manager;
        } catch (Exception var2) {
            throw new CacheException(var2);
        }
    }

    public void destroy() {
        if(this.cacheManagerImplicitlyCreated) {
            try {
                net.sf.ehcache.CacheManager e = this.getCacheManager();
                e.shutdown();
            } catch (Exception var2) {
                if(log.isWarnEnabled()) {
                    log.warn("Unable to cleanly shutdown implicitly created CacheManager instance.  Ignoring (shutting down)...");
                }
            }

            this.cacheManagerImplicitlyCreated = false;
        }

    }
}

RetryLimitHashedCredentialsMatcher:

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by dearx on 2019/10/16.
 */
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
    //集群中可能会导致出现验证多过5次的现象,因为AtomicInteger只能保证单节点并发
    //解决方案,利用ehcache、redis(记录错误次数)和mysql数据库(锁定)的方式处理:密码输错次数限制; 或两者结合使用
    private Cache<String, AtomicInteger> passwordRetryCache;

    public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
        //读取ehcache中配置的登录限制锁定时间
        passwordRetryCache = cacheManager.getCache("passwordRetryCache");
    }

    /**
     * 在回调方法doCredentialsMatch(AuthenticationToken token,AuthenticationInfo info)中进行身份认证的密码匹配,
     * </br>这里我们引入了Ehcahe用于保存用户登录次数,如果登录失败retryCount变量则会一直累加,如果登录成功,那么这个count就会从缓存中移除,
     * </br>从而实现了如果登录次数超出指定的值就锁定。
     * @param token
     * @param info
     * @return
     */
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token,AuthenticationInfo info) {
        //获取登录用户名
        String username = (String) token.getPrincipal();
        //从ehcache中获取密码输错次数
        // retryCount
        AtomicInteger retryCount = passwordRetryCache.get(username);
        if (retryCount == null) {
            //第一次
            retryCount = new AtomicInteger(0);
            passwordRetryCache.put(username, retryCount);
        }
        //retryCount.incrementAndGet()自增:count + 1
        if (retryCount.incrementAndGet() > 5) {
            // if retry count > 5 throw  超过5次 锁定
            throw new ExcessiveAttemptsException("账户:"+username+"的密码连续5次输入错误将被锁定");
        }
        //否则走判断密码逻辑
        boolean matches = super.doCredentialsMatch(token, info);
        if (matches) {
            // clear retry count  清楚ehcache中的count次数缓存
            passwordRetryCache.remove(username);
        }
        return matches;
    }
}

ShiroConfig :

import com.xxx.shiro.realm.ShiroRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Created by dearx on 2019/10/15.
 */

@Configuration
public class ShiroConfig {
    private static final Logger logger = LoggerFactory
            .getLogger(ShiroConfig.class);

    /**
     * ShiroFilterFactoryBean 处理拦截资源文件过滤器
     *  </br>1,配置shiro安全管理器接口securityManage;
     *  </br>2,shiro 连接约束配置filterChainDefinitions;
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        System.out.println("进入拦截器方法ShiroConfig.shiroFilterFactoryBean()");
        //shiroFilterFactoryBean对象
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        // 配置shiro安全管理器 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 指定要求登录时的链接
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        // 未授权时跳转的界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/login.html");

        // filterChainDefinitions拦截器
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 配置不会被拦截的链接 从上向下顺序判断
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/templates/**", "anon");

        // 配置退出过滤器,具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");
        //add操作,该用户必须有【addOperation】权限
        filterChainDefinitionMap.put("/add", "perms[addOperation]");

        // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问【放行】-->
        filterChainDefinitionMap.put("/user/**", "authc");

        shiroFilterFactoryBean
                .setFilterChainDefinitionMap(filterChainDefinitionMap);
        logger.debug("Shiro拦截器工厂类注入成功");
        return shiroFilterFactoryBean;
    }

    /**
     * shiro安全管理器设置realm认证
     * @return
     */
    @Bean public org.apache.shiro.mgt.SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(shiroRealm());
        // //注入ehcache缓存管理器;
        securityManager.setCacheManager(ehCacheManager());
        //注入Cookie记住我管理器
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }

    /**
     * 身份认证realm; (账号密码校验;权限等)
     *
     * @return
     */
    @Bean public ShiroRealm shiroRealm() {
        ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return shiroRealm;
    }

    /**
     * ehcache缓存管理器;shiro整合ehcache:
     * 通过安全管理器:securityManager
     * @return EhCacheManager
     */
    @Bean public EhCacheManager ehCacheManager() {
        logger.debug(
                "=====shiro整合ehcache缓存:ShiroConfiguration.getEhCacheManager()");
        EhCacheManager cacheManager = new EhCacheManager();

        String path= System.getProperty("user.dir")+ File.separator+"src"+File.separator+"main"+File.separator+"java"+
                File.separator+"com"+File.separator+"xxx"+File.separator+"shiro"+File.separator+"config"+File.separator+"ehcache.xml";
        if(path.contains("/target")){
            path=path.replace("/target","");
        }else if(path.contains("\\target")){
            path=path.replace("\\target","");
        }
        System.out.println("ehcache缓存管理器path:"+path);
        cacheManager.setCacheManagerConfigFile(path);
        return cacheManager;
    }


    /**
     * 设置记住我cookie过期时间
     * @return
     */
    @Bean
    public SimpleCookie remeberMeCookie(){
        logger.debug("记住我,设置cookie过期时间!");
        //cookie名称;对应前端的checkbox的name = rememberMe
        SimpleCookie scookie=new SimpleCookie("rememberMe");
        //设置自动登录时间为1天
        scookie.setMaxAge(86400);
        return scookie;
    }
    // 配置cookie记住我管理器
    @Bean
    public CookieRememberMeManager rememberMeManager(){
        logger.debug("配置cookie记住我管理器!");
        CookieRememberMeManager cookieRememberMeManager=new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(remeberMeCookie());
        return cookieRememberMeManager;
    }

    /**
     * 凭证匹配器 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     * 所以我们需要修改下doGetAuthenticationInfo中的代码,更改密码生成规则和校验的逻辑一致即可; )
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new RetryLimitHashedCredentialsMatcher(ehCacheManager());
        //new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");// 散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(1);// 散列的次数,比如散列两次,相当于 // md5(md5(""));
        return hashedCredentialsMatcher;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * *
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * *
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
     * * @return
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

}

ShiroRealm:

import com.xxx.system.entity.Authorities;
import com.xxx.system.entity.RoleAuthorities;
import com.xxx.system.entity.User;
import com.xxx.system.entity.UserRole;
import com.xxx.system.service.AuthoritiesJpaService;
import com.xxx.system.service.RoleAuthoritiesJpaService;
import com.xxx.system.service.UserJpaService;
import com.xxx.system.service.UserRoleJpaService;
import com.wangfan.endecrypt.utils.EndecryptUtils;
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.cache.Cache;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

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

/**
 * Created by dearx on 2019/10/15.
 */

public class ShiroRealm extends AuthorizingRealm {
   
    @Autowired
    private UserJpaService userJpaService;

    @Autowired
    private RoleAuthoritiesJpaService roleAuthoritiesJpaService;

    @Autowired
    private AuthoritiesJpaService authoritiesService;

    @Autowired
    private UserRoleJpaService userRoleJpaService;

    /*权限认证*/
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        Set<String> stringSet = new HashSet<>();
        String username = (String) SecurityUtils.getSubject().getPrincipal();
        User user = userJpaService.getByUsername(username);
        if (user != null) {
            if ("admin".equals(username)) {
                /*管理员永远有全部权限*/
                List<Authorities> authoritiesList = authoritiesService.selectList();
                for (Authorities authorities : authoritiesList) {
                    stringSet.add(authorities.getAuthority());
                }
            } else {
                /*根据用户获取用户所属角色*/
                List<UserRole> userRoleList = userRoleJpaService.findByUserId(user.getUserId());
                /*获取角色id集合*/
                List<Integer> roleIdList = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toList());
                /*根据角色id获取权限集合*/
                List<RoleAuthorities> roleAuthoritiesList = roleAuthoritiesJpaService.findRoleAuthoritiesByRoleIdIn(roleIdList);
                for (RoleAuthorities authorities : roleAuthoritiesList) {
                    stringSet.add(authorities.getAuthority());
                }
            }

        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setStringPermissions(stringSet);
        return info;
    }


    /*身份认证*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String userName = (String) authenticationToken.getPrincipal();
        String userPwd = new String((char[]) authenticationToken.getCredentials());
        User user = userJpaService.getByUsername(userName);
        String password = "";
        if (user == null) {
            throw new UnknownAccountException("账户不存在");
        } else {
            //根据用户名从数据库获取密码
            password = user.getPassword();
            if (!EndecryptUtils.encrytMd5(userPwd).equals(password)) {
                throw new IncorrectCredentialsException("账户或密码不正确");
            }
        }
         return new SimpleAuthenticationInfo(userName, password, getName());
    }

    @Override
    protected void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        Cache cache = this.getAuthorizationCache();
        Set<Object> keys = cache.keys();
        super.clearCachedAuthorizationInfo(principals);
    }
}

The above is the main document. What each file does is clearly annotated, so I won’t go into details. Of course, this only completes the configuration aspects of shiro. How to apply it specifically, there are several important points:

Key point 1: Call the identity authentication method in ShiroRealm when logging in. At this time, permission authentication is not involved. The details are as follows

/*这是需要的jar包
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;*/

/*登录的时候调用*/
public JsonResult login(String username, String password) {
        User user = userJpaService.getByUsername(username);
        Subject subject = SecurityUtils.getSubject();
        /*true代表记住密码,自动登录*/
        UsernamePasswordToken newtoken = new UsernamePasswordToken(username, password,true);
        // 执行认证登陆
        try {
            subject.login(newtoken);
        } catch (UnknownAccountException uae) {
            return  JsonResult.error(uae.getMessage());
        } catch (IncorrectCredentialsException ice) {
            return  JsonResult.error(ice.getMessage());
        } catch (LockedAccountException lae) {
            return JsonResult.error(lae.getMessage());
        } catch (ExcessiveAttemptsException eae) {
            return JsonResult.error(eae.getMessage());
        } catch (AuthenticationException ae) {
            return JsonResult.error(ae.getMessage());
        }
        if (subject.isAuthenticated()) {
            Session session = subject.getSession();
            session.setAttribute("currentUser",user);
            return JsonResult.ok("登录成功");
        } else {
            newtoken.clear();
            return JsonResult.error("登录失败");
        }
     }

/*登出的时候调用*/
public void logout(){
        Subject currentUser = SecurityUtils.getSubject();
        currentUser.logout();
    }

The second point: This is a hard work. Add permission authentication on the query, edit, and add interfaces, as follows, on the interface RequiresPermissions annotation, the value in the annotation is the path of the current interface. As mentioned in the previous steps, the path of this annotation must be consistent with the path scanned by swagger in sys_authorities. If the permission path in sys_authorities is not scanned by swagger but written by hand (I admire it below), it must also be written by hand. Consistent. Adding this annotation means that this interface has been added to shiro authority management. In the screenshot below, the getResult() method is added to the authority management, and only people with authority can access it. The list method is not authorized, and anyone can call it.

 

 The third point: every time the login method is used, the user's permissions will be re-stored in the ehcache cache. The permissions in the cache will be invalidated according to the time configured in the ehcache.xml configuration file. After invalidation, you will be prompted to log in again.

The fourth point: Regarding the handling of not having no authority and logging in again.

1. If the called interface does not have permission, this error will be reported:

2. If there is no login authentication, this error will be reported:

If we display the error directly on the page, it would be too violent. When a user opens a page without permission, what we hope he sees is of course not the boom. It is a gentle text prompt. So we used global exception capture processing. as follows. At this point, if the user does not have login authentication or does not have permission, it can be converted into a warm prompt.

import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.wf.jwtp.exception.TokenException;

import java.util.HashMap;
import java.util.Map;


@ControllerAdvice
public class MyExceptionHandler {
    private Logger logger = LoggerFactory.getLogger("MyExceptionHandler");

    @ResponseBody
    @ExceptionHandler(Exception.class)
    public Map<String, Object> errorHandler(Exception ex) {
        Map<String, Object> map = new HashMap<>();
        // 根据不同错误获取错误信息
        if (ex instanceof IException) {
            map.put("code", ((IException) ex).getCode());
            map.put("msg", ex.getMessage());
        } else if (ex instanceof TokenException) {
            map.put("code", ((TokenException) ex).getCode());
            map.put("msg", ex.getMessage());
        } else if (ex instanceof UnauthenticatedException) {
            ex.printStackTrace();
            map.put("code", "401");
            map.put("msg", "请重新登录");
        }else if(ex instanceof UnauthorizedException){
            ex.printStackTrace();
            map.put("code", "403");
            map.put("msg", "抱歉,您没有权限");
        }
        else {
            String message = ex.getMessage();
            map.put("code", 500);
            map.put("msg", message == null || message.trim().isEmpty() ? "未知错误" : message);
            logger.error(message, ex);
            ex.printStackTrace();
        }
        return map;
    }

}

 

 So far, the configuration of the shiro backend has been completed.

The front-end related html page or JavaScript code and js, and the back-end logic of role authorization will be updated in the near future.

Comments are welcome, long-term online, editing is not easy, reprint and leave your name

Guess you like

Origin blog.csdn.net/nienianzhi1744/article/details/103406556