该架构采用spring + springMVC + shiro + ehcache搭建有哪里不对的地方请大神指明,万分感谢!!
先来个标题
Shiro安全配置 //别以为没啥用,这行代码代表功能的开始搭建,虽然对功能没什么软用。。。
接下来是 shiroFilter 这个名称和要 web.xml 里面配置的 shiroFilter 相对应,
说到这里了,顺便说下 web.xml 里面 shiroFilter 拦截和 springMVC 拦截
按说 / 和 /* 都是拦截,区别就在与一个有返回,一个没有返回,
但是 springMVC 必须配置 / ,而 shiroFilter 必须配置 /* ,否则 shiroFilter 会拦截不到,
看网上好多都说 shiroFiter 必须在 springMVC 前面,经测试,两者间的顺序没有讲究,
在一个,登陆时候会拦截三次,按照本人的想法是,硬编码一次,springMVC 一次,shiroFilter 一次,具体原因因为没有测试,所以不明。
<-- 也可以自定义 Filter --> <-- /captcha.jpg = anon /static/** = anon /logout = anon /login = anon /member/** = anon /manage/** = user -->
接下来是安全管理器,三个属性
然后是管理器里面的三个属性
对应会话管理的最后一条属性,代码里有注释,建议看看
会话DAO
这个是生命周期
shiro密码加密配置
密码错误一定次数后锁定账号
shiro记住密码功能
注入securityManager
<!-- ajax session 超时处理 -->
<! ---------------------------------------------------------------------------------------------------- -->
下面是配置文件需要的一些java类
myRealm 自定义realm
package com.conferencerooms.utils.shiro;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.apache.shiro.authc.AccountException;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.SimpleAuthenticationInfo;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.apache.shiro.subject.SimplePrincipalCollection;import org.springframework.beans.factory.annotation.Autowired;import com.conferencerooms.entity.User;import com.conferencerooms.service.UserService;/** * 自定义realm * * @author ForeignStudent * @version 2017/9/20 */public class MyRealm extends AuthorizingRealm { private static final Logger LOGGER = LogManager.getLogger(MyRealm.class); @Autowired private UserService userService; public MyRealm(CacheManager cacheManager, CredentialsMatcher matcher) { super(cacheManager, matcher); } /** * Shiro登录认证(原理:用户提交 用户名和密码 --- shiro 封装令牌 ---- realm 通过用户名将密码查询返回 ---- * shiro 自动去比较查询出密码和用户输入密码是否一致---- 进行登陆控制 ) */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { LOGGER.info("Shiro开始登录认证 **********doGetAuthenticationInfo**************"); UsernamePasswordToken token = (UsernamePasswordToken) authcToken; String loginName = token.getUsername(); if (loginName == null) { throw new AccountException("用户名不能为空"); } User user = userService.getUsersByLoginName(loginName); if (user != null && user.getDelFlag() != 0 && user.getStatus() != -1) { ShiroUsers shiroUser = new ShiroUsers(user.getUserId(), user.getLoginName(), user.getUserName(), user); return new SimpleAuthenticationInfo(shiroUser, user.getPassword(), ShiroByteSource.of(user.getSalt()), getName()); } else { throw new AccountException("没有此用户"); } } /** * Shiro权限认证 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { ShiroUsers shiroUsers = (ShiroUsers) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); /* * info.setRoles(shiroUsers.getRoles()); * info.addStringPermissions(shiroUsers.getCodes()); */ return null; } @Override public void onLogout(PrincipalCollection principals) { super.clearCachedAuthorizationInfo(principals); ShiroUsers shiroUser = (ShiroUsers) principals.getPrimaryPrincipal(); removeUserCache(shiroUser); } /** * 清除用户缓存 * * @param shiroUser */ public void removeUserCache(ShiroUsers shiroUsers) { removeUserCache(shiroUsers.getLoginName()); } /** * 清除用户缓存 * * @param loginName */ public void removeUserCache(String loginName) { SimplePrincipalCollection principals = new SimplePrincipalCollection(); principals.add(loginName, super.getName()); super.clearCachedAuthenticationInfo(principals); }}
ShiroCacheManager 自定义缓存管理器
package com.conferencerooms.utils.shiro.cache;import org.apache.logging.log4j.LogManager;import org.apache.logging.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;public class ShiroCacheManager implements CacheManager, Destroyable { private static final Logger logger = LogManager.getLogger(ShiroCacheManager.class); private org.springframework.cache.CacheManager cacheManager; public org.springframework.cache.CacheManager getCacheManager() { return cacheManager; } public void setCacheManager(org.springframework.cache.CacheManager cacheManager) { this.cacheManager = cacheManager; } @Override publicCache 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 ShiroCache (cache); } @Override public void destroy() throws Exception { cacheManager = null; }}
ShiroCacheManager 需要的一个类
ShiroCache
package com.conferencerooms.utils.shiro.cache;import java.util.Collection;import java.util.Collections;import java.util.Set;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.apache.shiro.cache.CacheException;import org.springframework.cache.Cache;import org.springframework.cache.Cache.ValueWrapper;@SuppressWarnings("unchecked")public class ShiroCacheimplements org.apache.shiro.cache.Cache { private static final Logger logger = LogManager.getLogger(ShiroCache.class); private final org.springframework.cache.Cache cache; public ShiroCache(Cache cache) { if (cache == null) { throw new IllegalArgumentException("Cache argument cannot be null."); } this.cache = cache; } @Override public V get(K key) throws CacheException { if (logger.isTraceEnabled()) { logger.trace("Getting object from cache [" + this.cache.getName() + "] for key [" + key + "]key type:" + key.getClass()); } ValueWrapper valueWrapper = cache.get(key); if (valueWrapper == null) { if (logger.isTraceEnabled()) { logger.trace("Element for [" + key + "] is null."); } return null; } return (V) valueWrapper.get(); } @Override public V put(K key, V value) throws CacheException { if (logger.isTraceEnabled()) { logger.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 (logger.isTraceEnabled()) { logger.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 (logger.isTraceEnabled()) { logger.trace("Clearing all objects from cache [" + this.cache.getName() + "]"); } cache.clear(); } @Override public int size() { return 0; } @Override public Set keys() { return Collections.emptySet(); } @Override public Collection values() { return Collections.emptySet(); } @Override public String toString() { return "ShiroSpringCache [" + this.cache.getName() + "]"; }}
PasswordHash 密码加密配置
package com.conferencerooms.utils.shiro;import org.apache.shiro.crypto.hash.SimpleHash;import org.springframework.beans.factory.InitializingBean;import org.springframework.util.Assert;/** * shiro密码加密配置 * * @author ForeignStudent * @version 2017/9/20 */public class PasswordHash implements InitializingBean { private String algorithmName; private int hashIterations; @Override public void afterPropertiesSet() throws Exception { Assert.hasLength(algorithmName, "algorithmName mast be MD5、SHA-1、SHA-256、SHA-384、SHA-512"); } public String toHex(Object source, Object salt) { return new SimpleHash(algorithmName, source, salt, hashIterations).toHex(); } public String getAlgorithmName() { return algorithmName; } public void setAlgorithmName(String algorithmName) { this.algorithmName = algorithmName; } public int getHashIterations() { return hashIterations; } public void setHashIterations(int hashIterations) { this.hashIterations = hashIterations; }}
RetryLimitCredentialsMatcher 锁定 这里锁定的是5次
package com.conferencerooms.utils.shiro;import java.util.concurrent.atomic.AtomicInteger;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;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;/** * 密码错误5次锁定半小时 * * @author ForeignStudent * @version 2017/9/20 */public class RetryLimitCredentialsMatcher extends HashedCredentialsMatcher implements InitializingBean { private final static Logger logger = LogManager.getLogger(RetryLimitCredentialsMatcher.class); private final static String DEFAULT_CHACHE_NAME = "retryLimitCache"; private final CacheManager cacheManager; private String retryLimitCacheName; private CachepasswordRetryCache; private PasswordHash passwordHash; public RetryLimitCredentialsMatcher(CacheManager cacheManager) { this.cacheManager = cacheManager; this.retryLimitCacheName = DEFAULT_CHACHE_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 authcToken, AuthenticationInfo info) { String username = (String) authcToken.getPrincipal(); // retry count + 1 AtomicInteger retryCount = passwordRetryCache.get(username); if (retryCount == null) { retryCount = new AtomicInteger(0); passwordRetryCache.put(username, retryCount); } if (retryCount.incrementAndGet() > 5) { logger.warn("username: " + username + " tried to login more than 5 times in period"); throw new ExcessiveAttemptsException("用户名: " + username + " 密码连续输入错误超过5次,锁定半小时!"); } boolean matches = super.doCredentialsMatch(authcToken, info); if (matches) { 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); }}
<!-- ajax session超时时处理 -->
package com.conferencerooms.utils.shiro;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.apache.shiro.web.filter.authc.UserFilter;import org.apache.shiro.web.util.WebUtils;import com.conferencerooms.utils.StringUtils;/** * ajax超时处理类 * * @author ForeignStudent * @version 2017/9/20 */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 (StringUtils.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); }}
shiro配置到此为之差不多了,以上大部分内容是网上整理来的 都是经过测试可用的,其中多处都是手打,测试时多注意下就OK了
其中有几个地方不是很明白,在注释中也名提了下,如果哪位大神可以告知,感激不尽。