Back-end —— "demostración y explicación completa de la autenticación de permisos del sistema shiro

shiro es un marco muy poderoso para la administración de seguridad y la autenticación de autorización. Puede hacer administración de autoridad de inicio de sesión, administración de autoridad de interfaz. De la siguiente manera, puede usar el botón de permiso de la página para controlar si el usuario tiene los permisos de consulta, agregar y editar. Después de completar, las funciones son las siguientes: establecer permisos para usuarios normales después de iniciar sesión de administración, que pueden ser permisos de menú, menú lista de permisos de consulta y botones de lista Permisos. La autoridad del menú solo se puede controlar mediante la lógica del código, y la autoridad de la consulta de la lista y la interfaz del botón puede ser administrada por shiro.

 Vaya directamente a los pasos de implementación.

Gran paso uno: las tablas y funciones que deben prepararse en la etapa inicial (si el sistema ya tiene tablas o funciones similares, o si no se requiere la administración del menú sino solo la administración de permisos, omita este paso)

1, sys_menu (tabla de menú)

2, sys_role_menu (tabla de asociación del menú de funciones <muchos a muchos>)

3. sys_authorities (tabla de autoridad)

4. sys_role_authorities (tabla de asociación de autoridad de roles <muchos a muchos>)

5. sys_role (tabla de roles), sys_user (tabla de usuarios), sys_user_role (tabla de asociación de roles de usuarios <muchos a muchos>). Estas tres tablas estarán disponibles en sistemas generales.

------------- Las tablas anteriores son las que deben prepararse. Las relaciones de las tablas, las descripciones de los campos y el sql de creación de tablas se han subido a mis recursos, bienvenido a descargar, el enlace es como sigue --------- ----

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

Uno de los puntos que puede resultar un poco difícil de entender es la tabla sys_authorities. Esta tabla tiene dos preguntas.

Pregunta 1: ¿Cuál es el campo de autoridad en la tabla, de dónde proviene el valor de este campo y cuál es su función?

Respuesta 1: El valor de este campo es la ruta de la interfaz escaneada automáticamente por swagger, como post: / v1 / user / query. Después de obtener la ruta de la interfaz, almacene esta cadena en la tabla. La función es agregar la ruta al administrador de permisos de Shiro para comparar cuando el usuario llama a la interfaz para verificar si el usuario tiene permiso.

Ejemplo 1: Para el siguiente método de interfaz de lista de UserController, dado que el tipo de publicación y la ruta "/ consulta" se especifican en el método, la ruta de la interfaz de esta lista es publicación: / v1 / usuario / consulta (no publicación: / v1 / usuario / list), la ruta escaneada por swagger también es post: / v1 / user / query. Entonces, cómo escanear, solo necesita ajax para acceder a la interfaz / v2 / api-docs a través de get. En cuanto a qué es swagger, o cómo configurarlo, portal: https://blog.csdn.net/nienianzhi1744/article/details/102622722   (solo mire el contenido de la parte swagger en el documento)

Pregunta 2: ¿Qué son los campos parent_menu_id y auth_type en la tabla, cómo provienen y cuáles son sus funciones?

Respuesta 2: parent_menu_id es el ID del menú principal al que pertenece la interfaz, y auth_type es el tipo de interfaz (adición, eliminación, modificación, exportación, etc.), que es conveniente para ver durante la autorización. Por supuesto, hay una lista de administración de permisos y un botón de edición para seleccionar manualmente a qué menú pertenece el permiso actual (porque swagger no identificará a qué menú o lista pertenece la interfaz después de escanear la interfaz del sistema .Necesitamos seleccionar manualmente la función) y seleccionar el tipo de interfaz. La función es facilitarnos la asociación de permisos de interfaz con menús durante la gestión de autorizaciones, lograr la autorización de lotes y facilitar la comprensión. Si solo realiza la administración de permisos sin la administración del menú, puede agregarla según sea el caso.

 

Gran paso dos:

1. Archivo Pom, agregue 3 paquetes de 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. Estructura de directorios. Cree un directorio shiro detrás de com.xxx, un directorio de configuración y un directorio de reino en el directorio shiro.

Dos directorios suman hasta 5 archivos. Las funciones de estos cinco archivos son:

ehcache.xml: configuración de caché. Debido a la adición de la función de autoridad de interfaz, shiro comparará la autoridad cada vez que se llame a la interfaz, pero los datos de autoridad se almacenan en la tabla sys_authorities y la tabla sys_role_authorities de nuestra base de datos. ¿Tengo que verificar los permisos en la base de datos cada vez que accedo a una interfaz? Por supuesto que no, debido a que agregamos el paquete jar de ehcache arriba, verificamos los permisos de la base de datos por primera vez después de iniciar sesión en el sistema, y ​​luego almacenamos los permisos verificados en el caché, es decir, ehcache, que se puede recuperar de la caché más tarde. Consulta los permisos. En cuanto al proceso de almacenar los permisos en la base de datos y luego compararlos, déjelo en manos de Shiro, no es necesario escribir manualmente. (No importa)

EhCacheManager: configuración de caché. El contenido es relativamente simple, asigne y pegue (esto no es importante)

RetryLimitHashedCredentialsMatcher: procesamiento concurrente. El contenido es relativamente simple, asigne y pegue (esto no es importante)

ShiroConfig: clase de configuración Shiro. Esta clase se escaneará cuando se inicie el proyecto, similar al archivo de configuración de springmvc. Simplemente configure y procese el filtro de archivos de recursos de interceptación, el administrador de caché ehcache, el administrador de inicio de sesión automático, el administrador de autorización y autenticación de dominio. (Este es el documento más importante)

ShiroRealm: administrador de autenticación de inicio de sesión y autenticación de autoridad de Shiro. (Este es el segundo documento más importante)

 

 Los siguientes cinco archivos se publicarán sucesivamente.

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

El anterior es el documento principal. Lo que hace cada archivo está claramente anotado, por lo que no entraré en detalles. Por supuesto, esto solo completa los aspectos de configuración de shiro. Cómo aplicarlo específicamente, hay varios puntos importantes:

Punto clave 1: Llame al método de autenticación de identidad en ShiroRealm cuando inicie sesión. En este momento, la autenticación de permisos no está involucrada. Los detalles son los siguientes

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

El segundo punto: este es un trabajo arduo. Agregue autenticación de permisos en la consulta, edite y agregue interfaces, de la siguiente manera, en la anotación de la interfaz RequirePermissions, el valor en la anotación es la ruta de la interfaz actual. Como se mencionó en los pasos anteriores, la ruta de esta anotación debe ser consistente con la ruta escaneada por swagger en sys_authorities. Si la ruta de permisos en sys_authorities no es escaneada por swagger sino escrita a mano (la admiro a continuación), también debe ser escrito a mano. Agregar esta anotación significa que esta interfaz se ha agregado a la administración de autoridad de shiro. En la captura de pantalla siguiente, el método getResult () se agrega a la administración de autoridad y solo las personas con autoridad pueden acceder a él. El método de lista no está autorizado y cualquiera puede llamarlo.

 

 El tercer punto: cada vez que se utiliza el método de inicio de sesión, los permisos del usuario se volverán a almacenar en la caché de ehcache. Los permisos de la caché se invalidarán según el tiempo configurado en el archivo de configuración ehcache.xml. Después de la invalidación, se le pedirá que inicie sesión nuevamente.

El cuarto punto: Respecto al manejo de no tener autoridad y volver a iniciar sesión.

1. Si la interfaz llamada no tiene permiso, se informará este error:

2. Si no hay autenticación de inicio de sesión, se informará este error:

Si mostramos el error directamente en la página, sería demasiado violento.Cuando un usuario abre una página sin permiso, lo que esperamos que vea, por supuesto, no es el boom. Es un mensaje de texto suave. Entonces usamos el procesamiento de captura de excepciones globales. como sigue. En este punto, si el usuario no tiene autenticación de inicio de sesión o no tiene permiso, se puede convertir en un indicador cálido.

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

}

 

 Hasta ahora, se ha completado la configuración del backend de shiro.

La página html relacionada con el front-end o el código JavaScript y js, y la lógica del back-end de la autorización de roles se actualizarán en un futuro próximo.

Los comentarios son bienvenidos, a largo plazo en línea, la edición no es fácil, reimprima y deje su nombre.

Supongo que te gusta

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