博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring 整合 Shiro
阅读量:5164 次
发布时间:2019-06-13

本文共 25232 字,大约阅读时间需要 84 分钟。

一、引入依赖

org.springframework
spring-test
4.3.18.RELEASE
test
org.springframework
spring-webmvc
4.3.18.RELEASE
org.springframework
spring-jdbc
4.3.18.RELEASE
org.springframework
spring-context-support
4.3.18.RELEASE
org.apache.shiro
shiro-spring
1.4.0
net.sf.ehcache
ehcache-core
2.6.11

二、创建 ehcache 缓存配置文件 

文件:ehchache.xml

View Code

四、Spring 整合 Shiro

1. 创建文件 spring-shiro.xml

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 
Cache
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
(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
implements org.apache.shiro.cache.Cache
{ 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
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
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
elementMap = ehcache.getAll(keys); List
values = new ArrayList<>(); for (net.sf.ehcache.Element element : elementMap.values()) { values.add(element.getObjectValue()); } return (Collection
) values; } } return Collections.emptySet(); } @Override public String toString() { return "ShiroSpringCache [" + this.cache.getName() + "]"; }}
View Code

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

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
deptIds; /** * 部门名称集 */ private List
deptNames; /** * 角色id集 */ private List
roleIds; /** * 角色名称集 */ private List
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
getDeptIds() { return deptIds; } public void setDeptIds(List
deptIds) { this.deptIds = deptIds; } public List
getDeptNames() { return deptNames; } public void setDeptNames(List
deptNames) { this.deptNames = deptNames; } public List
getRoleIds() { return roleIds; } public void setRoleIds(List
roleIds) { this.roleIds = roleIds; } public List
getRoleNames() { return roleNames; } public void setRoleNames(List
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
roleIds = shiroUser.getRoleIds(); Set
permissionSet = new HashSet<>(); Set
roleAliasSet = new HashSet<>(); for (Long roleId : roleIds) { List
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 配置中

3. 添加会话管理器

4. 添加Remember管理器

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

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

7. 添加拦截器

/login = anon /captcha = anon /resources/** = anon /** = user
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的安全管理器

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 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
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
getDeptDataScope() { Long deptId = getUser().getDeptId();// List
subDeptIds = ConstantFactory.me().getSubDeptId(deptId);// subDeptIds.add(deptId); return subDeptIds; }*/ /** * 判断当前用户是否是超级管理员 */ /*public static boolean isAdmin() { Set
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的过滤器

shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
shiroFilter
/*
REQUEST
FORWARD

 

转载于:https://www.cnblogs.com/Jimc/p/10038635.html

你可能感兴趣的文章
jquery实现全选、反选、不选
查看>>
logback配置
查看>>
ECMA5中定义的对象属性特性和方法
查看>>
2、zabbix工作原理及安装配置
查看>>
2、Docker基础用法
查看>>
PostgreSQL 自增主键
查看>>
加快linux ssh登录的方法
查看>>
ORACLE表空间详解
查看>>
BZOJ5190 Usaco2018 Jan Stamp Painting(动态规划)
查看>>
保留小数点三位
查看>>
JavaEE之注解
查看>>
飞机大战项目
查看>>
JZYZOJ1383 [usaco2003feb]impster 位运算 最短路
查看>>
poj_3627Bookshelf
查看>>
java输入输入流图解
查看>>
html5改良的input元素的种类
查看>>
python人脸识别开源库face_recognition
查看>>
【神经网络与深度学习】转-caffe安装吐血总结
查看>>
【VS开发】进程线程及堆栈关系的总结
查看>>
vue三、示例
查看>>