后端——》shiro系统权限认证完整demo及讲解

shiro是一款很强大的安全管理、权限认证框架。可以做登录权限管理,接口权限管理。如下,可以通过页面权限按钮控制用户是否拥有查询、新增、编辑的权限,完成后功能如下:在管理登录后给普通用户设置权限,可以是菜单权限,也可以是菜单列表查询权限和列表按钮权限。其中菜单的权限可以只通过代码逻辑控制,而列表查询和按钮接口的权限就可以交给shiro来管理。

 直接上实现步骤吧。

大的步骤一:前期需要准备的表和功能(如果系统已有类似的表或功能,或者不需要菜单管理 只做权限管理的就跳过这一步)

1,sys_menu(菜单表)

2,sys_role_menu(角色菜单关联表<多对多>)

3,sys_authorities (权限表)

4,sys_role_authorities(角色权限关联表<多对多>)

5,sys_role(角色表)、sys_user(用户表)、sys_user_role(用户角色关联表<多对多>)。这三张表一般的系统都会有。

-------------以上是需要准备的表,表关系、字段说明、建表sql已经上传至我的资源,欢迎下载,链接如下-------------

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

其中理解起来可能稍微难的一点是sys_authorities这张表,这张表有两个疑问点,

问一:表中的authority字段,是什么,这个字段的值是从哪来的,作用是什么。

答一:这个字段的值是swagger自动扫描的接口路径,如post:/v1/user/query。获取到接口路径后再把这个字符串存到表中。作用是在用户前端调用接口的时候,将路径加入shiro的权限管理器中比对,校验用户是否有权限。

例一:如下面这个UserController的list接口方法,由于在方法上指定了post类型及路径"/query",所以这个list的接口路径是post:/v1/user/query(而不是post:/v1/user/list),那么swagger扫描出来的路径也是post:/v1/user/query。那么怎么扫描呢,只需要ajax通过get方式访问  /v2/api-docs 这个接口即可。至于swagger是什么,或者怎么配置,传送门:https://blog.csdn.net/nienianzhi1744/article/details/102622722  ( 只需要看文档中的swagger部分的内容即可)

问二: 表中的parent_menu_id、auth_type字段是什么,怎么来,作用是什么。

答二:parent_menu_id是接口所属父级菜单id,auth_type是接口类型(增删改查导出发送等等),方便授权时查看。怎么来的,当然是有个权限管理的列表,有个编辑按钮,手动选择当前权限时属于哪个菜单下的(因为swagger扫描完系统的接口后不会识别接口是属于哪个菜单或者哪个列表的,需要我们手动做功能选择),同时选择接口类型。作用是方便我们在授权管理时将接口权限与菜单关联,做到批量授权,同时也方便理解。如果只做权限管理不做菜单管理的话可以视情况加。

 

大的步骤二:

1,pom文件,添加3个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,目录结构。在com.xxx后面建一个shiro目录,shiro目录下建一个config目录,一个realm目录。

两个目录加起来5个文件,这五个文件的作用是:

ehcache.xml:缓存配置。由于加了接口权限的功能,每次调用接口,shiro都会进行权限比较,但权限数据是存在我们数据库的sys_authorities表和sys_role_authorities表。难道每次访问一个接口都要去数据库中查一遍权限?当然不是的,由于我们上面添加了ehcache的jar包,所以我们登录系统后第一次查权限从数据库中查,然后把查到的权限存到缓存中即ehcache中,在后面就可以从缓存中查询权限。至于将这个把权限存到数据库中再拿出来比较的过程,交给shiro就好了,不需要手动写。(这不重要)

EhCacheManager:缓存配置。内容比较简单,赋值粘贴吧(这不重要)

RetryLimitHashedCredentialsMatcher:并发处理。内容比较简单,赋值粘贴吧(这不重要)

ShiroConfig:shiro配置类。在项目启动的时候会扫描到这个类,与springmvc的been配置文件类似。只要配置处理拦截资源文件过滤器,ehcache缓存管理器,自动登录管理器,realm认证及授权管理器这几个管理器。(这是最重要的文件一)

ShiroRealm:shiro登录认证及权限认证管理器。(这是最重要的文件二)

 

 下面依次将这五个文件贴出来。

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

以上是主要文件了。每个文件是干嘛的都注释的很清楚了,不赘述了。当然,这只是完成了有关shiro的配置方面。那具体怎么应用呢,有几个重要的点:

重点一:登录的时候调用ShiroRealm 中的身份认证的方法,此时不涉及到权限认证,具体如下

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

重点二:这是个力气活,在查询、编辑、新增等接口上增加权限认证,如下,在接口上 RequiresPermissions注解,注解的内的值是当前接口的路径。在前几步已经提到了,此注解的路径必须要与sys_authorities中swagger扫描出来的路径一致 ,如果sys_authorities中的权限路径不是swagger扫描出来的而是手写的(在下佩服),则也必须与手写的一致。加上这个注解后则表示这个接口被加入了shiro权限管理中。如下截图中getResult()方法是被加入了权限管理的,只有有权限的人才能访问,list方法是没有权限认证的,谁都可以调用。

 

 重点三:每次走登录的方法的时候,都会重新将该用户的权限放到ehcache缓存中。缓存中的权限会根据ehcache.xml配置文件中配置的时间而失效。失效后会提示重新登录。

重点四:关于没有没有权限和重新登录的处理。

1,如果调用的接口没有权限会报这个错:

2,如果没有登录认证会报这个错:

我们要是直接把报错显示到页面上,那也太暴力了,当用户打开一个没有权限的页面,我么希望他看到的当然不是这一串红红火火。而是温柔的文字提示。所以我们用到了全局异常捕获处理。如下。此时如果用户没有进行登录认证或是没有权限的话,就可以转换成温馨的提示了。

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

}

 

 到此为止,有关shiro后端的配置已经完成。

与前端相关的html页面或是JavaScript代码与js,与角色授权的后端逻辑中会在近期更新出来。

欢迎提意见,长期在线,编辑不易,转载留名。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值