zoukankan      html  css  js  c++  java
  • Spring 整合 Shiro

    一、引入依赖

    <!-- spring start -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>4.3.18.RELEASE</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>4.3.18.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.3.18.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>4.3.18.RELEASE</version>
    </dependency>
    <!-- spring end -->
    
    <!-- shiro begin -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.0</version>
    </dependency>
    <!-- shiro end -->
    
    <!-- ehcache start -->
    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache-core</artifactId>
        <version>2.6.11</version>
    </dependency>
    <!-- ehcache end -->

    二、创建 ehcache 缓存配置文件 

    文件:ehchache.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache updateCheck="false" dynamicConfig="false">
        <diskStore path="java.io.tmpdir"/>
    
        <cache name="authenticationCache"
               maxEntriesLocalHeap="2000"
               eternal="false"
               timeToIdleSeconds="1800"
               timeToLiveSeconds="1800"
               overflowToDisk="false"
               statistics="true"/>
    
        <cache name="authorizationCache"
               maxEntriesLocalHeap="2000"
               eternal="false"
               timeToIdleSeconds="1800"
               timeToLiveSeconds="1800"
               overflowToDisk="false"
               statistics="true"/>
    
        <cache name="activeSessionCache"
               maxEntriesLocalHeap="2000"
               eternal="false"
               timeToIdleSeconds="1800"
               timeToLiveSeconds="1800"
               overflowToDisk="false"
               statistics="true">
        </cache>
    
        <!-- 缓存半小时 -->
        <cache name="halfHour"
               maxElementsInMemory="10000"
               maxElementsOnDisk="100000"
               eternal="false"
               timeToIdleSeconds="1800"
               timeToLiveSeconds="1800"
               overflowToDisk="false"
               diskPersistent="false" />
    
        <!-- 缓存一小时 -->
        <cache name="hour"
               maxElementsInMemory="10000"
               maxElementsOnDisk="100000"
               eternal="false"
               timeToIdleSeconds="3600"
               timeToLiveSeconds="3600"
               overflowToDisk="false"
               diskPersistent="false" />
    
        <!-- 缓存一天 -->
        <cache name="oneDay"
               maxElementsInMemory="10000"
               maxElementsOnDisk="100000"
               eternal="false"
               timeToIdleSeconds="86400"
               timeToLiveSeconds="86400"
               overflowToDisk="false"
               diskPersistent="false" />
    
        <!--
            name:缓存名称。
            maxElementsInMemory:缓存最大个数。
            eternal:对象是否永久有效,一但设置了,timeout将不起作用。
            timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
            timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
            overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
            diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
            maxElementsOnDisk:硬盘最大缓存个数。
            diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
            diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
            memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
            clearOnFlush:内存数量最大时是否清除。
        -->
    
        <defaultCache name="defaultCache"
                      maxElementsInMemory="10000"
                      eternal="false"
                      timeToIdleSeconds="120"
                      timeToLiveSeconds="120"
                      overflowToDisk="false"
                      maxElementsOnDisk="100000"
                      diskPersistent="false"
                      diskExpiryThreadIntervalSeconds="120"
                      memoryStoreEvictionPolicy="LRU"/>
    </ehcache>
    View Code

    四、Spring 整合 Shiro

    1. 创建文件 spring-shiro.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    </beans>

    2. 将 shiro 的缓存管理器交给 spring-cache 管理

    package com.beovo.dsd.common.shiro.cache;
    
    import org.apache.log4j.LogManager;
    import org.apache.log4j.Logger;
    import org.apache.shiro.cache.Cache;
    import org.apache.shiro.cache.CacheException;
    import org.apache.shiro.cache.CacheManager;
    import org.apache.shiro.util.Destroyable;
    import org.springframework.util.ClassUtils;
    
    /**
     * 使用spring-cache作为shiro缓存- 缓存管理器
     * @author Jimc.
     * @since 2018/11/22.
     */
    public class ShiroSpringCacheManager implements CacheManager, Destroyable {
        private static final Logger logger = LogManager.getLogger(ShiroSpringCacheManager.class);
        private org.springframework.cache.CacheManager cacheManager;
        private final boolean hasEhcache;
    
        public ShiroSpringCacheManager() {
            hasEhcache = ClassUtils.isPresent("net.sf.ehcache.Ehcache", this.getClass().getClassLoader());
        }
    
        public org.springframework.cache.CacheManager getCacheManager() {
            return cacheManager;
        }
    
        public void setCacheManager(org.springframework.cache.CacheManager cacheManager) {
            this.cacheManager = cacheManager;
        }
    
        @Override
        public <K, V> Cache<K, V> getCache(String name) throws CacheException {
            if (logger.isTraceEnabled()) {
                logger.trace("Acquiring ShiroSpringCache instance named [" + name + "]");
            }
            org.springframework.cache.Cache cache = cacheManager.getCache(name);
            return new ShiroSpringCache<K, V>(cache, hasEhcache);
        }
    
        @Override
        public void destroy() throws Exception {
            cacheManager = null;
        }
    
    }
    View Code
    package com.beovo.dsd.common.shiro.cache;
    
    import cn.hutool.log.Log;
    import cn.hutool.log.LogFactory;
    import org.apache.shiro.cache.CacheException;
    import org.springframework.cache.Cache;
    import org.springframework.cache.Cache.ValueWrapper;
    
    import java.util.*;
    
    /**
     * 使用spring-cache作为shiro缓存
     * @author Jimc.
     * @since 2018/11/22.
     */
    public class ShiroSpringCache<K, V> implements org.apache.shiro.cache.Cache<K, V> {
    
        private static final Log log = LogFactory.get();
    
        private final Cache cache;
    
        private final boolean hasEhcache;
    
        public ShiroSpringCache(Cache cache, boolean hasEhcache) {
            if (cache == null) {
                throw new IllegalArgumentException("Cache argument cannot be null.");
            }
            this.cache = cache;
            this.hasEhcache = hasEhcache;
        }
    
        @Override
        public V get(K key) throws CacheException {
            if (log.isTraceEnabled()) {
                log.trace("Getting object from cache [" + this.cache.getName() + "] for key [" + key + "]key type:" + key.getClass());
            }
            ValueWrapper valueWrapper = cache.get(key);
            if (valueWrapper == null) {
                if (log.isTraceEnabled()) {
                    log.trace("Element for [" + key + "] is null.");
                }
                return null;
            }
            return (V) valueWrapper.get();
        }
    
        @Override
        public V put(K key, V value) throws CacheException {
            if (log.isTraceEnabled()) {
                log.trace("Putting object in cache [" + this.cache.getName() + "] for key [" + key + "]key type:" + key.getClass());
            }
            V previous = get(key);
            cache.put(key, value);
            return previous;
        }
    
        @Override
        public V remove(K key) throws CacheException {
            if (log.isTraceEnabled()) {
                log.trace("Removing object from cache [" + this.cache.getName() + "] for key [" + key + "]key type:" + key.getClass());
            }
            V previous = get(key);
            cache.evict(key);
            return previous;
        }
    
        @Override
        public void clear() throws CacheException {
            if (log.isTraceEnabled()) {
                log.trace("Clearing all objects from cache [" + this.cache.getName() + "]");
            }
            cache.clear();
        }
    
        @Override
        public int size() {
            if (hasEhcache) {
                Object nativeCache = cache.getNativeCache();
                if (nativeCache instanceof net.sf.ehcache.Ehcache) {
                    net.sf.ehcache.Ehcache ehcache = (net.sf.ehcache.Ehcache) nativeCache;
                    return ehcache.getSize();
                }
            }
            return 0;
        }
    
        @Override
        public Set<K> keys() {
            if (hasEhcache) {
                Object nativeCache = cache.getNativeCache();
                if (nativeCache instanceof net.sf.ehcache.Ehcache) {
                    net.sf.ehcache.Ehcache ehcache = (net.sf.ehcache.Ehcache) nativeCache;
                    return new HashSet<>(ehcache.getKeys());
                }
            }
            return Collections.emptySet();
        }
    
        @Override
        public Collection<V> values() {
            if (hasEhcache) {
                Object nativeCache = cache.getNativeCache();
                if (nativeCache instanceof net.sf.ehcache.Ehcache) {
                    net.sf.ehcache.Ehcache ehcache = (net.sf.ehcache.Ehcache) nativeCache;
                    List keys = ehcache.getKeys();
                    Map<Object, net.sf.ehcache.Element> elementMap = ehcache.getAll(keys);
                    List<Object> values = new ArrayList<>();
                    for (net.sf.ehcache.Element element : elementMap.values()) {
                        values.add(element.getObjectValue());
                    }
                    return (Collection<V>) values;
                }
            }
            return Collections.emptySet();
        }
    
        @Override
        public String toString() {
            return "ShiroSpringCache [" + this.cache.getName() + "]";
        }
    }
    View Code

    将 缓存管理器交给 Spring 来管理,在 spring-shiro.xml 添加配置如下:

       <!-- 用户授权信息Cache, 采用spring-cache -->
        <bean id="shiroSpringCacheManager" class="com.beovo.dsd.common.shiro.cache.ShiroSpringCacheManager">
            <property name="cacheManager" ref="cacheManager"/>
        </bean>

    3. 自定义 Realm

    (1)创建一个存放用户信息的bean

    package com.beovo.dsd.common.shiro;
    
    import java.io.Serializable;
    import java.util.List;
    
    /**
     * 自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息
     * @author Jimc.
     * @since 2018/11/23.
     */
    public class ShiroUser implements Serializable {
    
        private static final long serialVersionUID = -1373760725780840091L;
    
        /**
         * 主键ID
         */
        private Long id;
    
        /**
         * 账号
         */
        private String account;
    
        /**
         * 姓名
         */
        private String name;
    
        /**
         * 部门id集
         */
        private List<Long> deptIds;
    
        /**
         * 部门名称集
         */
        private List<String> deptNames;
    
        /**
         * 角色id集
         */
        private List<Long> roleIds;
    
        /**
         * 角色名称集
         */
        private List<String> roleNames;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getAccount() {
            return account;
        }
    
        public void setAccount(String account) {
            this.account = account;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public List<Long> getDeptIds() {
            return deptIds;
        }
    
        public void setDeptIds(List<Long> deptIds) {
            this.deptIds = deptIds;
        }
    
        public List<String> getDeptNames() {
            return deptNames;
        }
    
        public void setDeptNames(List<String> deptNames) {
            this.deptNames = deptNames;
        }
    
        public List<Long> getRoleIds() {
            return roleIds;
        }
    
        public void setRoleIds(List<Long> roleIds) {
            this.roleIds = roleIds;
        }
    
        public List<String> getRoleNames() {
            return roleNames;
        }
    
        public void setRoleNames(List<String> roleNames) {
            this.roleNames = roleNames;
        }
    }
    View Code

    (2)首先创建一个继承 AuthorizingRealm 的类

    package com.beovo.dsd.common.shiro;
    
    import cn.hutool.core.collection.CollUtil;
    import cn.hutool.core.convert.Convert;
    import cn.hutool.core.util.StrUtil;
    import cn.hutool.log.Log;
    import cn.hutool.log.LogFactory;
    import com.beovo.dsd.po.User;
    import com.beovo.dsd.service.UserAuthService;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authc.credential.CredentialsMatcher;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.cache.CacheManager;
    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;
    
    /**
     * shiro权限认证
     * @author Jimc.
     * @since 2018/11/22.
     */
    public class ShiroDbRealm extends AuthorizingRealm {
    
        private static final Log log = LogFactory.get();
    
        @Autowired
        private UserAuthService userAuthService;
    
        public ShiroDbRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
            super(cacheManager, matcher);
        }
    
        /**
         * Shiro登录认证(原理:用户提交 用户名和密码  --- shiro 封装令牌 ---- realm 通过用户名将密码查询返回 ---- shiro 自动去比较查询出密码和用户输入密码是否一致---- 进行登陆控制)
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
            log.info("Shiro开始登录认证");
            UsernamePasswordToken token = Convert.convert(UsernamePasswordToken.class, authcToken);
            User user = userAuthService.getUser(token.getUsername());
            ShiroUser shiroUser = userAuthService.getShiroUser(user);
    
            // 认证缓存信息
            return userAuthService.info(shiroUser, user, getName());
        }
    
        /**
         * Shiro权限认证
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            ShiroUser shiroUser = Convert.convert(ShiroUser.class, principals.getPrimaryPrincipal());
            List<Long> roleIds = shiroUser.getRoleIds();
    
            Set<String> permissionSet = new HashSet<>();
            Set<String> roleAliasSet = new HashSet<>();
    
            for (Long roleId : roleIds) {
                List<String> permissions = userAuthService.getPermissionsByRoleId(roleId);
                if (CollUtil.isNotEmpty(permissions)) {
                    for (String permission : permissions) {
                        if (StrUtil.isNotEmpty(permission)) {
                            permissionSet.add(permission);
                        }
                    }
                }
                String roleAlias = userAuthService.getRoleAliasByRoleId(roleId);
                roleAliasSet.add(roleAlias);
            }
    
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.addStringPermissions(permissionSet);
            info.addRoles(roleAliasSet);
            return info;
        }
    }
    View Code

    (3)加入到 spring-shiro.xml 配置中

    <!-- 項目自定义的Realm -->
        <bean id="shiroDbRealm" class="com.beovo.dsd.common.shiro.ShiroDbRealm">
            <constructor-arg index="0" name="cacheManager" ref="shiroSpringCacheManager"/>
            <constructor-arg index="1" name="matcher" ref="credentialsMatcher"/>
            <!-- 启用身份验证缓存,即缓存AuthenticationInfo信息,默认false -->
            <property name="authenticationCachingEnabled" value="true"/>
            <!-- 缓存AuthenticationInfo信息的缓存名称 -->
            <property name="authenticationCacheName" value="authenticationCache"/>
            <!-- 缓存AuthorizationInfo信息的缓存名称 -->
            <property name="authorizationCacheName" value="authorizationCache"/>
        </bean>

    3. 添加会话管理器

    <!-- 会话管理器 -->
        <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
            <!-- 设置全局会话超时时间 半小时 -->
            <property name="globalSessionTimeout" value="#{30 * 60 * 1000}"/>
            <!-- url上带sessionId 默认为true -->
            <property name="sessionIdUrlRewritingEnabled" value="false"/>
            <property name="sessionDAO" ref="sessionDAO"/>
        </bean>
    
        <!-- 会话DAO 用于会话的CRUD -->
        <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
            <!-- Session缓存名字,默认就是shiro-activeSessionCache -->
            <property name="activeSessionsCacheName" value="activeSessionCache"/>
            <property name="cacheManager" ref="shiroSpringCacheManager"/>
        </bean>

    4. 添加Remember管理器

     <!-- 记住密码Cookie -->
        <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
            <constructor-arg value="rememberMe"/>
            <property name="httpOnly" value="true"/>
            <!-- 7天,采用spring el计算方便修改 -->
            <property name="maxAge" value="#{7 * 24 * 60 * 60}"/>
        </bean>
    
        <!-- rememberMe管理器,cipherKey生成见{@code Base64Test.java} -->
        <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
            <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('amltYwAAAAAAAAAAAAAAAA==')}"/>
            <property name="cookie" ref="rememberMeCookie"/>
        </bean>

    5. 添加密码加密配置

    package com.beovo.dsd.common.shiro;
    
    /**
     * shiro密码加密配置
     *
     * @author Jimc.
     * @since 2018/11/22.
     */
    public class PasswordHash {
    
        /**
         * 加密算法名称
         */
        private String algorithmName;
    
        /**
         * 密码hash次数
         */
        private Integer hashIterations;
    
        public String getAlgorithmName() {
            return algorithmName;
        }
    
        public void setAlgorithmName(String algorithmName) {
            this.algorithmName = algorithmName;
        }
    
        public Integer getHashIterations() {
            return hashIterations;
        }
    
        public void setHashIterations(Integer hashIterations) {
            this.hashIterations = hashIterations;
        }
    
        /**
         * 密码加密
         *
         * @param source 加密源对象
         * @param salt   加密盐
         * @return 加密后的字符
         */
        public String encrypt(Object source, Object salt) {
            return ShiroKit.encrypt(algorithmName, source, salt, hashIterations);
        }
    }
    View Code
    <!-- shiro密码加密配置 -->
        <bean id="passwordHash" class="com.beovo.dsd.common.shiro.PasswordHash">
            <!-- 密码加密 2次md5,增强密码可修改此处 -->
            <property name="algorithmName" value="md5"/>
            <property name="hashIterations" value="2"/>
        </bean>

    6. 添加密码错误次数锁定功能

    package com.beovo.dsd.common.shiro;
    
    import cn.hutool.log.Log;
    import cn.hutool.log.LogFactory;
    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 org.springframework.beans.factory.InitializingBean;
    import org.springframework.util.Assert;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 输错5次密码锁定半小时,ehcache.xml配置
     * @author Jimc.
     * @since 2018/11/22.
     */
    public class RetryLimitCredentialsMatcher extends HashedCredentialsMatcher implements InitializingBean {
        private static final Log log = LogFactory.get();
        private final static String DEFAULT_CACHE_NAME = "retryLimitCache";
        
        private final CacheManager cacheManager;
        private String retryLimitCacheName;
        private Cache<String, AtomicInteger> passwordRetryCache;
        private PasswordHash passwordHash;
        
        public RetryLimitCredentialsMatcher(CacheManager cacheManager) {
            this.cacheManager = cacheManager;
            this.retryLimitCacheName = DEFAULT_CACHE_NAME;
        }
        
        public String getRetryLimitCacheName() {
            return retryLimitCacheName;
        }
    
        public void setRetryLimitCacheName(String retryLimitCacheName) {
            this.retryLimitCacheName = retryLimitCacheName;
        }
        
        public void setPasswordHash(PasswordHash passwordHash) {
            this.passwordHash = passwordHash;
        }
    
        @Override
        public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
            String username = (String) token.getPrincipal();
            //retry count + 1
            AtomicInteger retryCount = passwordRetryCache.get(username);
            if(retryCount == null) {
                retryCount = new AtomicInteger(0);
                passwordRetryCache.put(username, retryCount);
            }
            if(retryCount.incrementAndGet() > 5) {
                //if retry count > 5 throw
                String message = "用户名: " + username + " 密码连续输入错误超过5次,锁定半小时!";
                log.warn(message);
                throw new ExcessiveAttemptsException(message);
            } else {
                passwordRetryCache.put(username, retryCount);
            }
    
            boolean matches = super.doCredentialsMatch(token, info);
            if(matches) {
                //clear retry data
                passwordRetryCache.remove(username);
            }
            return matches;
        }
        
        @Override
        public void afterPropertiesSet() throws Exception {
            Assert.notNull(passwordHash, "you must set passwordHash!");
            super.setHashAlgorithmName(passwordHash.getAlgorithmName());
            super.setHashIterations(passwordHash.getHashIterations());
            this.passwordRetryCache = cacheManager.getCache(retryLimitCacheName);
        }
    }
    View Code
     <!-- 密码错误5次锁定半小时 -->
        <bean id="credentialsMatcher" class="com.beovo.dsd.common.shiro.RetryLimitCredentialsMatcher">
            <constructor-arg ref="shiroSpringCacheManager"/>
            <property name="retryLimitCacheName" value="halfHour"/>
            <property name="passwordHash" ref="passwordHash"/>
        </bean>

    7. 添加拦截器

    <!-- Shiro Filter -->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <!-- 安全管理器 -->
            <property name="securityManager" ref="securityManager"/>
            <!-- 默认的登陆访问url -->
            <property name="loginUrl" value="/login"/>
            <!-- 登陆成功后跳转的url -->
            <property name="successUrl" value="/index"/>
            <!-- 没有权限跳转的url -->
            <property name="unauthorizedUrl" value="/unauth"/>
            <property name="filterChainDefinitions">
                <value>
                    <!--
                        anon  不需要认证
                        authc 需要认证
                        user  验证通过或RememberMe登录的都可以
                    -->
                    /login = anon
                    /captcha = anon
                    /resources/** = anon
                    /** = user
                </value>
            </property>
            <property name="filters">
                <map>
                    <entry key="user" value-ref="ajaxSessionFilter"/>
                </map>
            </property>
        </bean>
    
        <!-- ajax session超时时处理 -->
        <bean id="ajaxSessionFilter" class="com.beovo.dsd.common.shiro.filter.ShiroAjaxSessionFilter"/>
    package com.beovo.dsd.common.shiro.filter;
    
    import cn.hutool.core.util.StrUtil;
    import org.apache.shiro.web.filter.authc.UserFilter;
    import org.apache.shiro.web.util.WebUtils;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * ajax shiro session超时统一处理
     * @author Jimc.
     * @since 2018/11/23.
     */
    public class ShiroAjaxSessionFilter extends UserFilter {
    
        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
            HttpServletRequest req = WebUtils.toHttp(request);
            String xmlHttpRequest = req.getHeader("X-Requested-With");
            if (StrUtil.isNotBlank(xmlHttpRequest)) {
                if (xmlHttpRequest.equalsIgnoreCase("XMLHttpRequest")) {
                    HttpServletResponse res = WebUtils.toHttp(response);
                    // 采用res.sendError(401);在Easyui中会处理掉error,$.ajaxSetup中监听不到
                    res.setHeader("oauthstatus", "401");
                    return false;
                }
            }
            return super.onAccessDenied(request, response);
        }
    }
    View Code

    8. 将自定义Realm、缓存管理器、会话管理器以及Remember管理器交给shiro的安全管理器

    <!-- 安全管理器 -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <!-- 设置自定义Realm -->
            <property name="realm" ref="shiroDbRealm"/>
            <!-- 将缓存管理器,交给安全管理器 -->
            <property name="cacheManager" ref="shiroSpringCacheManager"/>
            <!-- 记住密码管理 -->
            <property name="rememberMeManager" ref="rememberMeManager"/>
            <!-- session管理器 -->
            <property name="sessionManager" ref="sessionManager"/>
        </bean>

    9. 提供一个shiro的工具类

    package com.beovo.dsd.common.shiro;
    
    import cn.hutool.core.collection.CollUtil;
    import cn.hutool.core.convert.Convert;
    import cn.hutool.core.util.ObjectUtil;
    import cn.hutool.core.util.RandomUtil;
    import cn.hutool.core.util.StrUtil;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.crypto.hash.Md5Hash;
    import org.apache.shiro.crypto.hash.SimpleHash;
    import org.apache.shiro.session.Session;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.util.ByteSource;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
    
    import java.util.Collection;
    
    /**
     * shiro工具类
     *
     * @author Jimc.
     * @since 2018/11/23.
     */
    public class ShiroKit {
    
        private static final String NAMES_DELIMITER = ",";
    
        /**
         * 密码盐长度
         */
        private static final int SALT_LENGTH = 16;
    
        /**
         * shiro密码加密
         *
         * @param algorithmName  算法
         * @param source         源对象(密码)
         * @param salt           密码盐
         * @param hashIterations hash次数
         * @return 加密后的字符
         */
        public static String encrypt(String algorithmName, Object source, Object salt, int hashIterations) {
            ByteSource saltSource = new Md5Hash(salt);
            return new SimpleHash(algorithmName, source, saltSource, hashIterations).toString();
        }
    
        /**
         * 获取随机16位盐值
         */
        public static String getRandomSalt() {
            return RandomUtil.randomString(SALT_LENGTH);
        }
    
        /**
         * 获取当前 Subject
         *
         * @return Subject
         */
        public static Subject getSubject() {
            return SecurityUtils.getSubject();
        }
    
        /**
         * 获取封装的 ShiroUser
         *
         * @return ShiroUser
         */
        public static ShiroUser getUser() {
            if (isGuest()) {
                return null;
            } else {
                return Convert.convert(ShiroUser.class, getSubject().getPrincipals().getPrimaryPrincipal());
            }
        }
    
        /**
         * 从shiro获取session
         */
        public static Session getSession() {
            return getSubject().getSession();
        }
    
        /**
         * 获取shiro指定的sessionKey
         */
        @SuppressWarnings("unchecked")
        public static <T> T getSessionAttr(String key) {
            Session session = getSession();
            return ObjectUtil.isNotNull(session) ? (T) session.getAttribute(key) : null;
        }
    
        /**
         * 设置shiro指定的sessionKey
         */
        public static void setSessionAttr(String key, Object value) {
            Session session = getSession();
            session.setAttribute(key, value);
        }
    
        /**
         * 移除shiro指定的sessionKey
         */
        public static void removeSessionAttr(String key) {
            Session session = getSession();
            if (ObjectUtil.isNotNull(session)) {
                session.removeAttribute(key);
            }
        }
    
        /**
         * 验证当前用户是否属于该角色?,使用时与lacksRole 搭配使用
         *
         * @param roleName 角色名
         * @return 属于该角色:true,否则false
         */
        public static boolean hasRole(String roleName) {
            return ObjectUtil.isNotNull(getSubject()) && StrUtil.isNotEmpty(roleName)
                    && getSubject().hasRole(roleName);
        }
    
        /**
         * 与hasRole标签逻辑相反,当用户不属于该角色时验证通过。
         *
         * @param roleName 角色名
         * @return 不属于该角色:true,否则false
         */
        public static boolean lacksRole(String roleName) {
            return !hasRole(roleName);
        }
    
        /**
         * 验证当前用户是否属于以下任意一个角色。
         *
         * @param roleNames 角色列表
         * @return 属于:true,否则false
         */
        public static boolean hasAnyRoles(String roleNames) {
            boolean hasAnyRole = false;
            Subject subject = getSubject();
            if (ObjectUtil.isNotNull(getSubject()) && StrUtil.isNotEmpty(roleNames)) {
                for (String role : roleNames.split(NAMES_DELIMITER)) {
                    if (subject.hasRole(role.trim())) {
                        hasAnyRole = true;
                        break;
                    }
                }
            }
            return hasAnyRole;
        }
    
        /**
         * 验证当前用户是否属于以下所有角色。
         *
         * @param roleNames 角色列表
         * @return 属于:true,否则false
         */
        public static boolean hasAllRoles(String roleNames) {
            boolean hasAllRole = true;
            Subject subject = getSubject();
            if (ObjectUtil.isNotNull(getSubject()) && StrUtil.isNotEmpty(roleNames)) {
                for (String role : roleNames.split(NAMES_DELIMITER)) {
                    if (!subject.hasRole(role.trim())) {
                        hasAllRole = false;
                        break;
                    }
                }
            }
            return hasAllRole;
        }
    
        /**
         * 验证当前用户是否拥有指定权限,使用时与lacksPermission 搭配使用
         *
         * @param permission 权限名
         * @return 拥有权限:true,否则false
         */
        public static boolean hasPermission(String permission) {
            return ObjectUtil.isNotNull(getSubject()) && StrUtil.isNotEmpty(permission)
                    && getSubject().isPermitted(permission);
        }
    
        /**
         * 与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过。
         *
         * @param permission 权限名
         * @return 拥有权限:true,否则false
         */
        public static boolean lacksPermission(String permission) {
            return !hasPermission(permission);
        }
    
        /**
         * 已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。与notAuthenticated搭配使用
         *
         * @return 通过身份验证:true,否则false
         */
        public static boolean isAuthenticated() {
            return ObjectUtil.isNotNull(getSubject()) && getSubject().isAuthenticated();
        }
    
        /**
         * 未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。。
         *
         * @return 没有通过身份验证:true,否则false
         */
        public static boolean notAuthenticated() {
            return !isAuthenticated();
        }
    
        /**
         * 认证通过或已记住的用户。与guset搭配使用。
         *
         * @return 用户:true,否则 false
         */
        public static boolean isUser() {
            return ObjectUtil.isNotNull(getSubject()) && ObjectUtil.isNotNull(getSubject().getPrincipal());
        }
    
        /**
         * 验证当前用户是否为“访客”,即未认证(包含未记住)的用户。用user搭配使用
         *
         * @return 访客:true,否则false
         */
        public static boolean isGuest() {
            return !isUser();
        }
    
        /**
         * 输出当前用户信息,通常为登录帐号信息。
         *
         * @return 当前用户信息
         */
        public static String principal() {
            Subject subject = getSubject();
            if (ObjectUtil.isNotNull(subject)) {
                Object principal = subject.getPrincipal();
                return principal.toString();
            }
            return "";
        }
    
        /**
         * 单用户登录时,判断用户是否已经登录
         */
        public static boolean isLogin(String username) {
            Collection<Session> sessions = ((DefaultWebSessionManager) (((DefaultWebSecurityManager) SecurityUtils
                    .getSecurityManager()).getSessionManager())).getSessionDAO().getActiveSessions();
            if (CollUtil.isNotEmpty(sessions)) {
                for (Session session : sessions) {
                    String _username = Convert.toStr(session.getAttribute("username"));
                    if (StrUtil.isNotEmpty(_username) && StrUtil.equals(username, _username)) {
                        return true;
                    }
                }
            }
            return false;
        }
    
        /**
         * 获取当前用户的部门数据范围的集合
         */
        /*public static List<Integer> getDeptDataScope() {
            Long deptId = getUser().getDeptId();
    //        List<Integer> subDeptIds = ConstantFactory.me().getSubDeptId(deptId);
    //        subDeptIds.add(deptId);
            return subDeptIds;
        }*/
    
        /**
         * 判断当前用户是否是超级管理员
         */
         /*public static boolean isAdmin() {
           Set<Long> roles = ShiroKit.getUser().getRoles();
            for (Long role : roles) {
                String singleRoleTip = ConstantFactory.me().getSingleRoleTip(role);
                if (singleRoleTip.equals(Const.ADMIN_NAME)) {
                    return true;
                }
            }
            return false;
        }*/
    }
    View Code

     五、在web.xml添加shiro的过滤器

     <!--Shiro过滤器-->
        <filter>
            <filter-name>shiroFilter</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
            <init-param>
                <param-name>targetFilterLifecycle</param-name>
                <param-value>true</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>shiroFilter</filter-name>
            <url-pattern>/*</url-pattern>
            <dispatcher>REQUEST</dispatcher>
            <dispatcher>FORWARD</dispatcher>
        </filter-mapping>

    源码下载

  • 相关阅读:
    Nginx配置简单说明
    JVM需要知道的一些设置参数
    Maven仓库(私服)中上传第三方jar包
    maven本地资源库设置
    sweetalert2使用教程
    《算法导论》学习笔记——二叉搜索树
    散列表
    跨域请求
    mvn常用命令
    Springmvc配置笔记
  • 原文地址:https://www.cnblogs.com/Jimc/p/10038635.html
Copyright © 2011-2022 走看看