Back-end —— "demonstração e explicação completa de autenticação de permissão do sistema shiro

shiro é uma estrutura muito poderosa para gerenciamento de segurança e autenticação de autorização. Pode fazer gerenciamento de autoridade de login, gerenciamento de autoridade de interface. A seguir, você pode usar o botão de permissão de página para controlar se o usuário tem as permissões de consulta, adição e edição. Após a conclusão, as funções são as seguintes: definir permissões para usuários comuns após o login de gerenciamento, que podem ser permissões de menu, menu listar permissões de consulta e listar botões Permissões. A autoridade do menu pode ser controlada apenas pela lógica do código, e a autoridade da consulta da lista e da interface do botão pode ser gerenciada pelo shiro.

 Vá diretamente para as etapas de implementação.

Grande passo um: as tabelas e funções que precisam ser preparadas no estágio inicial (se o sistema já tiver tabelas ou funções semelhantes, ou se o gerenciamento do menu não for necessário, mas apenas o gerenciamento de permissões, pule esta etapa)

1, sys_menu (tabela de menu)

2, sys_role_menu (tabela de associação do menu de funções <muitos para muitos>)

3. sys_authorities (tabela de autoridade)

4. sys_role_authorities (tabela de associação de autoridade de função <muitos para muitos>)

5. sys_role (tabela de função), sys_user (tabela de usuário), sys_user_role (tabela de associação de função de usuário <muitos para muitos>). Essas três tabelas estarão disponíveis em sistemas gerais.

------------- Acima estão as tabelas que precisam ser preparadas. Os relacionamentos da tabela, as descrições dos campos e o sql de construção da tabela foram carregados para meus recursos, bem-vindo ao download, o link é como segue --------- ----

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

Um dos pontos que podem ser um pouco difíceis de entender é a tabela sys_authorities, que contém duas questões.

Questão 1: Qual é o campo de autoridade na tabela, de onde vem o valor desse campo e qual é sua função?

Resposta 1: O valor deste campo é o caminho da interface verificado automaticamente pelo swagger, como post: / v1 / user / query. Após obter o caminho da interface, armazene essa string na tabela. A função é adicionar o caminho ao gerenciador de permissão de Shiro para comparação quando o front-end do usuário chama a interface para verificar se o usuário tem permissão.

Exemplo 1: para o seguinte método de interface de lista de UserController, uma vez que o tipo de postagem e o caminho "/ query" são especificados no método, o caminho de interface desta lista é post: / v1 / user / query (não post: / v1 / user / list), então o caminho verificado pelo swagger também será post: / v1 / user / query. Então, como escanear, só precisa do ajax para acessar a interface / v2 / api-docs por meio de get. Quanto ao que é o swagger, ou como configurá-lo, portal: https://blog.csdn.net/nienianzhi1744/article/details/102622722   (basta olhar para o conteúdo da parte do swagger no documento)

Questão 2: Quais são os campos parent_menu_id e auth_type na tabela, de onde eles vêm e quais são suas funções.

Resposta 2: parent_menu_id é o id do menu pai ao qual a interface pertence, e auth_type é o tipo de interface (adição, exclusão, modificação, exportação, etc.), que é conveniente para visualização durante a autorização. Claro, há uma lista de gerenciamento de permissões e um botão de edição para selecionar manualmente a qual menu a permissão atual pertence (porque o swagger não identificará a qual menu ou lista a interface pertence após verificar a interface do sistema . Precisamos selecionar manualmente a função) e selecionar o tipo de interface. A função é tornar mais fácil para nós associar permissões de interface a menus durante o gerenciamento de autorização, para obter autorização de lote e para facilitar o entendimento. Se você só faz gerenciamento de permissões sem gerenciamento de menu, pode adicioná-lo conforme o caso.

 

Grande passo dois:

1. Arquivo Pom, adicione 3 pacotes jar

<!--权限认证相相关-->
    <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. Estrutura do diretório. Crie um diretório shiro atrás de com.xxx, um diretório config e um diretório realm sob o diretório shiro.

Dois diretórios adicionam até 5 arquivos. As funções desses cinco arquivos são:

ehcache.xml: configuração do cache. Devido à adição da função de autoridade da interface, shiro comparará a autoridade toda vez que a interface for chamada, mas os dados de autoridade são armazenados na tabela sys_authorities e na tabela sys_role_authorities de nosso banco de dados. Devo verificar as permissões no banco de dados toda vez que acessar uma interface? Claro que não, porque adicionamos o pacote jar do ehcache acima, verificamos as permissões do banco de dados pela primeira vez após fazer login no sistema e, em seguida, armazenamos as permissões verificadas no cache, ou seja, ehcache, que pode ser recuperado o cache mais tarde. Consultar permissões. Quanto ao processo de armazenar as permissões no banco de dados e depois compará-las, deixe para o shiro, não há necessidade de escrever manualmente. (Isso não importa)

EhCacheManager: Configuração do cache. O conteúdo é relativamente simples, atribua e cole (isso não é importante)

RetryLimitHashedCredentialsMatcher: processamento simultâneo. O conteúdo é relativamente simples, atribua e cole (isso não é importante)

ShiroConfig: classe de configuração Shiro. Esta classe será digitalizada quando o projeto iniciar, semelhante ao arquivo de configuração do springmvc. Basta configurar e processar o filtro de arquivo de recurso de interceptação, gerenciador de cache ehcache, gerenciador de login automático, autenticação de domínio e gerenciador de autorização. (Este é o documento mais importante)

ShiroRealm: gerenciador de autenticação de login e autoridade de autenticação Shiro. (Este é o segundo documento mais importante)

 

 Os cinco arquivos a seguir serão publicados sucessivamente.

 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);
    }
}

O texto acima é o documento principal. O que cada arquivo faz é claramente anotado, então não vou entrar em detalhes. Claro, isso apenas completa os aspectos de configuração do shiro. Como aplicá-lo especificamente, existem vários pontos importantes:

Ponto-chave 1: chame o método de autenticação de identidade em ShiroRealm ao fazer login. No momento, a autenticação de permissão não está envolvida. Os detalhes são os seguintes

/*这是需要的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();
    }

O segundo ponto: Este é um trabalho árduo.Adicione autenticação de permissão na consulta, edite e adicione interfaces, como segue, na anotação de interface RequerPermissões, o valor na anotação é o caminho da interface atual. Conforme mencionado nas etapas anteriores, o caminho desta anotação deve ser consistente com o caminho verificado por swagger em sys_authorities. Se o caminho de permissão em sys_authorities não for verificado por swagger, mas escrito à mão (eu admiro abaixo), também deve ser escrito à mão. Consistente. Adicionar esta anotação significa que esta interface foi adicionada ao gerenciamento de autoridade shiro. Na captura de tela abaixo, o método getResult () é adicionado ao gerenciamento de autoridade, e apenas pessoas com autoridade podem acessá-lo.O método de lista não é autorizado e qualquer pessoa pode chamá-lo.

 

 O terceiro ponto: toda vez que o método de login for usado, as permissões do usuário serão re-armazenadas no cache do ehcache. As permissões no cache serão invalidadas de acordo com o tempo configurado no arquivo de configuração ehcache.xml. Após a invalidação, você será solicitado a fazer o login novamente.

O quarto ponto: Em relação ao tratamento de não ter autoridade e logar novamente.

1. Se a interface chamada não tiver permissão, este erro será relatado:

2. Se não houver autenticação de login, este erro será relatado:

Se exibirmos o erro diretamente na página, seria muito violento. Quando um usuário abre uma página sem permissão, o que esperamos que ele veja não é o boom. É um prompt de texto suave. Portanto, usamos o processamento de captura de exceção global. do seguinte modo. Nesse ponto, se o usuário não tiver autenticação de login ou não tiver permissão, ele pode ser convertido em um prompt quente.

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;
    }

}

 

 Até agora, a configuração do backend shiro foi concluída.

A página html relacionada ao front-end ou código JavaScript e js, e a lógica de back-end de autorização de função serão atualizados em um futuro próximo.

Comentários são bem-vindos, on-line de longo prazo, editar não é fácil, reimprima e deixe seu nome.

Acho que você gosta

Origin blog.csdn.net/nienianzhi1744/article/details/103406556
Recomendado
Clasificación