一、引入依赖

<!-- 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>

四、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;
} }
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() + "]";
}
}

将 缓存管理器交给 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;
}
}

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

(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);
}
}
<!-- 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);
}
}
 <!-- 密码错误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);
}
}

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

五、在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>

源码下载

Spring 整合 Shiro的更多相关文章

  1. Spring整合Shiro做权限控制模块详细案例分析

    1.引入Shiro的Maven依赖 <!-- Spring 整合Shiro需要的依赖 --> <dependency> <groupId>org.apache.sh ...

  2. Spring整合Shiro并扩展使用EL表达式

    Shiro是一个轻量级的权限控制框架,应用非常广泛.本文的重点是介绍Spring整合Shiro,并通过扩展使用Spring的EL表达式,使@RequiresRoles等支持动态的参数.对Shiro的介 ...

  3. Spring整合Shiro 权限 角色 用户关系分析

    Spring整合Shiro 权限 角色 用户关系分析 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 前置内容 之前我们学习了,使用注解的方式去完成权限的控制,当然,也是静态的,也就 ...

  4. spring整合shiro框架

    上一篇文章已经对shiro框架做了一定的介绍,这篇文章讲述使用spring整合shiro框架,实现用户认证已经权限控制 1.搭建环境 这里不在赘述spring环境的搭建,可以简单的搭建一个ssm框架, ...

  5. Spring整合Shiro

    apache shiro 是一个安全认证框架,和 spring security 相比,在于他使用了比较简洁易懂的 认证和授权方式.其提供的 native-session(即把用户认证后的授权信息保存 ...

  6. 【shiro】2.spring整合shiro,注解控制shiro用户/角色/权限And/OR,没有权限跳转到固定页面

    这几天粗浅的把shiro整合到spring中,并且注解控制shiro用户/角色/权限And/OR 步骤: 1.首先maven搭建web项目 2.创建数据库 user/role/authority 其中 ...

  7. 【原】Spring整合Shiro基础搭建[3]

    1.前言 上个Shiro Demo基础搭建是基于官方的快速入门版本,没有集成其他框架,只是简单的通过Main方法来执行Shiro工作流程,并测试一下比较核心的函数:但在企业开发中一般都会集成Sprin ...

  8. spring整合shiro配置BUG,Tomcat启动不了:Error during artifact deployment. See server log for details

    现象 spring配置shiro权限控制之后,项目无法启动 [2019-08-09 09:00:35,800] Artifact export_web_manager:war exploded: Er ...

  9. spring 整合 shiro框架

    shiro是用来干嘛的?从它的官网上(http://shiro.apache.org/)基本可以了解到,她主要提供以下功能: (1)Authentication(认证) (2)Authorizatio ...

随机推荐

  1. javascript dom 操作详解 js加强

    js加强操作实现代码. 1 .文档里的每个节点都有属性 nodeName . nodeValue . nodeType nodeName 文本节点的的节点名是 #text , nodeName 是只读 ...

  2. 基于bootstrap模态框的二次封装

    一.参数设置 $.beamDialog(options); var defaults = { title:'标题', content:'内容', showCloseButton:true, //显示关 ...

  3. Java中获取classpath路径下的资源文件

    ClassLoader 提供了两个方法用于从装载的类路径中取得资源: public URL  getResource (String name); public InputStream  getRes ...

  4. 使用pako.js实现gzip的压缩和解压

    poko.js可至Github下载:https://github.com/nodeca/pako 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...

  5. 用keytool制作证书并在tomcat配置https服务(四)

    用keytool制作证书并在tomcat配置https服务(一) 用keytool制作证书并在tomcat配置https服务(二) 用keytool制作证书并在tomcat配置https服务(三) 上 ...

  6. java中equals以及==的用法(简单介绍)

    简单介绍 equals方法是java.lang.Object类的方法 有两种用法说明: 一.对于字符串变量来说,使用“==”和“equals()”方法比较字符串时,其比较方法不同. 1.“==”比较两 ...

  7. 【luogu P1903 [国家集训队]数颜色】 题解

    题目链接:https://www.luogu.org/problemnew/show/P1903 裸的...带修莫队... 比较麻烦吧(对我来说是的) 两个变量分开记录查询和修改操作. #includ ...

  8. ContentProvider 、 ContentResolver 、 ContentObserver

    说说ContentProvider . ContentResolver . ContentObserver 之间的关系**a. ContentProvider 内容提供者,用于对外提供数据 b. Co ...

  9. 冒泡排序_C语言_数组

    冒泡排序_C语言_数组 #include <stdio.h> //冒泡排序 小->大 void sort(int * pArray, int len); int main(int a ...

  10. cornerstone提示“SQLite-database disk image is malformed”

    当点击workingCopy时错误如下 google了一下,有是有解决的办法,可是这些都是直接使用sqlite时产生的问题. sqlite错误 The database disk image is m ...