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了

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

转载于:https://www.cnblogs.com/foreign-student/p/7592189.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值