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>

 

源码下载

posted @ 2018-11-29 15:31  寒爵  阅读(660)  评论(0编辑  收藏  举报