• spring 整合 shiro


       该架构采用spring + springMVC + shiro + ehcache搭建有哪里不对的地方请大神指明,万分感谢!!

        先来个标题

    <description>Shiro安全配置</description>  //别以为没啥用,这行代码代表功能的开始搭建,虽然对功能没什么软用。。。

      接下来是 shiroFilter  这个名称和要 web.xml 里面配置的 shiroFilter  相对应,

      说到这里了,顺便说下 web.xml 里面 shiroFilter 拦截和 springMVC 拦截

      按说 / /* 都是拦截,区别就在与一个有返回,一个没有返回,

      但是 springMVC 必须配置 / ,而 shiroFilter 必须配置 /* ,否则 shiroFilter 会拦截不到,

      看网上好多都说 shiroFiter 必须在 springMVC 前面,经测试,两者间的顺序没有讲究,

      在一个,登陆时候会拦截三次,按照本人的想法是,硬编码一次,springMVC 一次,shiroFilter 一次,具体原因因为没有测试,所以不明。

       <!-- 启用shrio授权注解拦截方式 -->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
         <!-- 这个属性是必须的,否侧会报 SecurityManager property must be set --> <property name="securityManager" ref="securityManager"/>
         <!-- 登陆地址 --> <property name="loginUrl" value="/login"/>
         <!-- 登陆成功地址 --> <property name="successUrl" value="/user"/>
         <!--
            还有个配置,是没有权限的登陆地址,感觉没有什么用,所以没有配置,
            登陆成功之后一般都是固定到上次访问的路径,而上次访问的路径都是不需要的验证的,而在页面上显示的都是该用户可以访问的路径,
            没有权限访问的路径一般都不会显示,所以我觉的没有必要配置,或者是本人没有碰到需要权限才能访问的的情况吧,总之这里没有配置。
         -->
         <!-- 未经授权的 -->
         <!-- <property name="unauthorizedUrl" value="/login/unauthorized" /> -->
          <!--

            anon,authc,authcBasic,user 是第一组认证过滤器
            perms,port,rest,roles,ssl 是第二组授权过滤器
            logout 退出, noSessionCreation 缓存
            相对应的功能可以上网查查,这里不细究了

          --> <property name="filterChainDefinitions"> <value> /captcha.jpg = anon /static/** = anon /logout = anon /login = anon /member/** = anon /manage/** = user </value> </property>
          <-- 也可以自定义 Filter -->
          <--
            <property name="filters">
              <map>
                <entry key="backstage" value-ref="backstageAuthenticationFilter"/>
              </map>
          --> </bean>
        <!-- 对应的filters的bean -->
        <!--
          <bean id="backstageAUthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
              <property name="loginUrl" value="/manage/login"/>
              <property name="successUrl" value="/backstage/manage/company/index"/>
          </bean>
        -->

      接下来是安全管理器,三个属性

       <!--安全管理器 -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
         <!-- 自定义realm --> <property name="realm" ref="myRealm"/> <!-- 定义缓存管理器 --> <property name="cacheManager" ref="shiroCacheManager"/> <!-- 会话管理 --> <property name="sessionManager" ref="sessionManager"/> </bean>

      然后是管理器里面的三个属性

       <!-- 自定realm -->
        <bean id="myRealm" class="com.conferencerooms.utils.shiro.MyRealm">
            <property name="credentialsMatcher" ref="credentialsMatcher"/>
            <property name="cachingEnabled" value="true"/>
            <property name="authenticationCachingEnabled" value="true"/>
            <property name="authenticationCacheName" value="authenticationCache"/>
            <property name="authorizationCachingEnabled" value="true"/>
            <property name="authorizationCacheName" value="authorizationCache"/>
        </bean>
       <!-- 定义缓存管理器 -->
        <!-- 默认 -->
        <!-- <bean id="shiroCacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" /> -->
        <!-- spring-ehcache -->
        <bean id="shiroCacheManager" class="com.conferencerooms.utils.shiro.cache.ShiroCacheManager">
            <property name="cacheManager" ref="cacheManager"/>
        </bean>
       <!-- 会话管理 -->
        <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
            <!-- session的失效时长,单位毫秒 -->
            <property name="globalSessionTimeout" value="600000"/>
            <!-- 删除失效的session -->
            <property name="deleteInvalidSessions" value="true"/>
            <!-- 可选属性,不加该属性,在服务器重启的时候会报sessionId错误,但不影响正常登陆 -->
            <!-- session 是创建在服务器,当服务器重启,服务器session会初始化,而页面cookic会保存,
                    当没有正常退出而再次请求的时候服务器sessionId不存在,所以会报空指针异常 -->
            <property name="sessionIdCookie" ref="sessionIdCookie"/>
        </bean>

      对应会话管理的最后一条属性,代码里有注释,建议看看

      <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
            <constructor-arg name="name" value="ycyintang.session.id"/>
        </bean>

      会话DAO  

       <!-- 会话DAO 用于会话的CRUD 待细研究 -->
        <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
            <property name="activeSessionsCacheName" value="activeSessionCache"/>
            <property name="cacheManager" ref="shiroCacheManager"/>
        </bean>

       这个是生命周期

       <!-- 生命周期 -->
        <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

      shiro密码加密配置

       <!-- shiro密码加密配置 -->
        <bean id="passwordHash" class="com.conferencerooms.utils.shiro.PasswordHash">
         <!-- 这里使用的是md5 也可以使用sha --> <property name="algorithmName" value="md5"/>
         <!-- 这里可以更改加密次数,已达到密码的复杂程度 --> <property name="hashIterations" value="1"/> <!-- storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码 --> <!-- <property name="storedCredentialsHexEncoded" value="true"/> --> </bean>

      密码错误一定次数后锁定账号

      <!-- 密码错误5次锁定半小时 自定义 -->
        <bean id="credentialsMatcher" class="com.conferencerooms.utils.shiro.RetryLimitCredentialsMatcher">
            <constructor-arg ref="shiroCacheManager"/>
            <property name="retryLimitCacheName" value="halfHour"/>
            <property name="passwordHash" ref="passwordHash"/>
        </bean>

      shiro记住密码功能

       <!-- 记住密码Cookie -->
        <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
            <constructor-arg value="rememberMe"/>
            <property name="httpOnly" value="true" />
            <property name="maxAge" value="604800"/>
        </bean>
    
        <!-- rememberMe管理器,记住密码用的是AES加密,生成16位的key 功能是实现了,具体怎么实现的不是很清楚,带研究 -->
        <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
            <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('5aaC5qKm5oqA5pyvAAAAAA==')}"/>
            <property name="cookie" ref="rememberMeCookie"/>
        </bean>

      注入securityManager

      <!-- 在方法中 注入 securityManager ,进行代理控制 具体有什么功能待细研究 -->
        <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
            <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
            <property name="arguments" ref="securityManager"/>
        </bean>

      <!-- ajax session 超时处理 -->

       <!-- ajax session超时时处理 -->
        <bean id="ajaxSessionFilter" class="com.conferencerooms.utils.shiro.ShiroAjaxSessionFilter"/>

      <! ---------------------------------------------------------------------------------------------------- -->

      下面是配置文件需要的一些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
        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 ShiroCache<K, V>(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 ShiroCache<K, V> implements org.apache.shiro.cache.Cache<K, V> {
    
        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<K> keys() {
            return Collections.emptySet();
        }
    
        @Override
        public Collection<V> 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 Cache<String, AtomicInteger> passwordRetryCache;
        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了

      其中有几个地方不是很明白,在注释中也名提了下,如果哪位大神可以告知,感激不尽。

  • 相关阅读:
    设计模式-装饰模式(Decorator Pattern)
    死锁分析与解决
    事务原理与开发
    SQL注入与防范
    数据库连接池
    JDBC基础知识
    算法复习
    计算机网络基础知识
    Caused by: org.gradle.api.internal.plugins.PluginApplicationException: Faile
    手写算法
  • 原文地址:https://www.cnblogs.com/foreign-student/p/7592189.html
Copyright © 2020-2023  润新知