`

springboot:shiro

阅读更多

第一次学习系统学习shiro 并将shiro集成到springboot中
参考了很多同学的文章 这里表示非常感谢
demo东拼西凑 基本想实现的东西都凑齐了 实现了

===========================================
本demo是基于前后端分离的形式写的demo 基于api 没有页面 请不要思考页面在哪
本demo主要包括:

1.基于filter md5 加盐登陆(用户名密码写死 自己修改测试)
2.用户角色roles控制 权限perms控制
3.基于redis的session分布式 基于redis的cache分布式(redis亲测 没有问题)
注:含有ehcache 但整体基于redis 所以将网上所有使用ehcache的demo改成redis形式
4.验证码(写死的验证码 自己修改测试)
5.登陆尝试次数限制(自己修改时间测试 注意expiretime单位是秒)
6.session管理(外加一个kitout 未成功 仅做参考)
7.用户修改密码 修改权限 切换用户(非切换角色)

===========================================
readme.md
===========================================
1.用户登陆 通过拦截器MyFormAuthenticationFilter处理
  访问地址http://localhost:8080/adminlogin
2.用户登陆成功之后 再次访问/adminlogin地址 会直接访问TestController:submitLogin方法
3.用户退出
  访问地址http://localhost:8080/adminlogout
4.MyRoleAuthorizationFilter控制角色
  可修改MyShiroRealm中的doGetAuthorizationInfo
  String role = "admin"; 改为guest或abc等
5.MyPermAuthorizationFilter控制权限
6.MyAccessControlFilter自定义拦截器 配置在/** 表示全部拦截
7.有权限地址http://localhost:8080/admin/manage  
  无权限地址http://localhost:8080/admin/test
8.切换用户(注:是切换用户 不是 切换角色) 登陆后访问 http://localhost:8080/switchuser
  切换指定账户后 要将账户登录名传递过去 这时在MyShiroRealm中的doGetAuthorizationInfo方法
  会获取用户名重新加载传递的用户名的权限
9.修改密码 admin/manage/modifypass
  修改密码后要重新登陆
10.修改权限 admin/manage/modifyauth
  修改完权限 会调用MyShiroRealm中的doGetAuthorizationInfo方法 重新加载权限
11.session对话管理SessionController
12.用户密码加盐(随机字符串) 加密 需要在用户设置密码的时候处理 并把盐入库存储
  用户在登陆的时候 会调用MyShiroRealm中的doGetAuthenticationInfo方法
  在方法中将盐查出 传递 进行授权验证
13.cache包下是处理分布式session和cache的 使用的是redis
14.MyModularRealmAuthenticator自定义ModularRealmAuthenticator 处理多realms
15.RetryLimitCredentialsMatcher 匹配器处理登陆尝试次数
16.由于使用了redis做分布式 ehcache-shiro.xml未使用到
===========================================
pom.xml
===========================================
<!-- Spring Boot Web 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Boot Test 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <!-- shiro权限控制框架 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-quartz</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
===========================================
ehcache-shiro.xml
demo中未使用到 只是测试缓存使用
===========================================
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shiroCache">

    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            />
</ehcache>
===========================================
Application
===========================================
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}
===========================================
CaptchaUsernamePasswordToken
===========================================
public class CaptchaUsernamePasswordToken extends UsernamePasswordToken {

    //验证码字符串
    private String captcha;

    public CaptchaUsernamePasswordToken(String username, String password,
                                        boolean rememberMe, String host, String captcha) {
        super(username, password, rememberMe, host);
        this.captcha = captcha;
    }

    public String getCaptcha() {
        return captcha;
    }

    public void setCaptcha(String captcha) {
        this.captcha = captcha;
    }

}
===========================================
IncorrectCaptchaException
===========================================
public class IncorrectCaptchaException extends AuthenticationException {

    public IncorrectCaptchaException() {
        super();
    }

    public IncorrectCaptchaException(String message, Throwable cause) {
        super(message, cause);
    }

    public IncorrectCaptchaException(String message) {
        super(message);
    }

    public IncorrectCaptchaException(Throwable cause) {
        super(cause);
    }
}
===========================================
MySessionIdGenerator
===========================================
public class MySessionIdGenerator implements SessionIdGenerator {

    /**
     * Ignores the method argument and simply returns
     * {@code UUID}.{@link java.util.UUID#randomUUID() randomUUID()}.{@code toString()}.
     *
     * @param session the {@link Session} instance to which the ID will be applied.
     * @return the String value of the JDK's next {@link UUID#randomUUID() randomUUID()}.
     */
    public Serializable generateId(Session session) {
        //return UUID.randomUUID().toString();
        //TODO
        return null;
    }
}
===========================================
MyShiroRealm
===========================================
public class MyShiroRealm extends AuthorizingRealm {

    private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);


    /**
     * 权限认证,为当前登录的Subject授予角色和权限
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        logger.info("##################执行Shiro权限认证##################");
        //获取当前登录输入的用户名,等价于(String) principalCollection.fromRealm(getName()).iterator().next();
//        String loginName = (String) super.getAvailablePrincipal(principalCollection);
        //到数据库查是否有此对象
//        User user = userDao.findByName(loginName);// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
//        if (user != null) {
//            //权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
//            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//            //用户的角色集合
//            info.setRoles(user.getRolesName());
//            //用户的角色对应的所有权限,如果只使用角色定义访问权限,下面的四行可以不要
//            List<Role> roleList = user.getRoleList();
//            for (Role role : roleList) {
//                info.addStringPermissions(role.getPermissionsName());
//            }
//            return info;
//        }
//        return null;


        //权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
        Subject subject = SecurityUtils.getSubject();

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色
        String role = "admin";
        info.addRole(role);
        //info.addRole("guest");
        //添加权限 admin:manage 对应url /admin/manage*
        info.addStringPermission("admin:manage");
        //info.addStringPermission("admin:modifypass");
        //info.addStringPermission("admin:modifyauth");
        logger.info("已为用户["+
                ShiroSecurityHelper.getCurrentUsername()+"]赋予了["+
                role+"]角色和[admin:manage]权限");
        return info;
    }

    /**
     * 登录认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authenticationToken) throws AuthenticationException {
        //UsernamePasswordToken对象用来存放提交的登录信息
//        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//        //查出是否有此用户
//        User user = userDao.findByName(token.getUsername());
//        if (user != null) {
//            //判断user状态 如果状态被冻结 抛出DisabledAccountException
//            //密码错误 产生IncorrectCredentialsException异常 错误凭证
//            // 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验
//            return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
//        }
//        //返回null产生UnknownAccountException异常 未知账户
//        return null;
//        CaptchaUsernamePasswordToken token = (CaptchaUsernamePasswordToken)authenticationToken;
//        if(!"A1DC".equals(token.getCaptcha())){
//            throw new IncorrectCaptchaException("验证码不正确");
//        }

        //=================================================================
        //此处用md5 迭代5次加密 使用盐 应在注册密码时使用 并将盐值存储在库=========================
        String password = "111111";
        //要与RetryLimitCredentialsMatcher中一致 加密方式
        String hashAlgorithmName = "MD5";
        //要与RetryLimitCredentialsMatcher中一致 循环加密次数
        int hashIterations = 5;
        //盐,随机数,此随机数也在数据库存储
        String salt = "eteokues";
        Object credentials = new SimpleHash(hashAlgorithmName, password, salt, hashIterations);
        System.out.println(credentials);
        //=========================
        //此步骤验证时 应先从库里读出盐 然后进行处理
        return new SimpleAuthenticationInfo("user", credentials, ByteSource.Util.bytes(salt), getName());
        //=================================================================

    }

    /**
     * 清除所有缓存权限
     */
    public void clearAllCachedAuthorizationInfo() {
        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
        if (cache != null) {
            for (Object key : cache.keys()) {
                cache.remove(key);
            }
        }
    }


}
===========================================
RedisCache
===========================================
public class RedisCache<K, V> implements Cache<K, V> {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * The wrapped Jedis instance.
     */
    private RedisManager cache;

    /**
     * The Redis key prefix for the sessions
     */
    private String keyPrefix = "shiro_redis_session:";

    /**
     * Returns the Redis session keys
     * prefix.
     *
     * @return The prefix
     */
    public String getKeyPrefix() {
        return keyPrefix;
    }

    /**
     * Sets the Redis sessions key
     * prefix.
     *
     * @param keyPrefix The prefix
     */
    public void setKeyPrefix(String keyPrefix) {
        this.keyPrefix = keyPrefix;
    }

    /**
     * 通过一个JedisManager实例构造RedisCache
     */
    public RedisCache(RedisManager cache) {
        if (cache == null) {
            throw new IllegalArgumentException("Cache argument cannot be null.");
        }
        this.cache = cache;
    }

    /**
     * Constructs a cache instance with the specified
     * Redis manager and using a custom key prefix.
     *
     * @param cache  The cache manager instance
     * @param prefix The Redis key prefix
     */
    public RedisCache(RedisManager cache, String prefix) {

        this(cache);

        // set the prefix
        this.keyPrefix = prefix;
    }

    /**
     * 获得byte[]型的key
     *
     * @param key
     * @return
     */
    private byte[] getByteKey(K key) {
        if (key instanceof String) {
            String preKey = this.keyPrefix + key;
            return preKey.getBytes();
        } else {
            return SerializeUtils.serialize(key);
        }
    }

    @Override
    public V get(K key) throws CacheException {
        logger.debug("根据key从Redis中获取对象 key [" + key + "]");
        try {
            if (key == null) {
                return null;
            } else {
                byte[] rawValue = cache.get(getByteKey(key));
                @SuppressWarnings("unchecked")
                V value = (V) SerializeUtils.deserialize(rawValue);
                return value;
            }
        } catch (Throwable t) {
            throw new CacheException(t);
        }

    }

    @Override
    public V put(K key, V value) throws CacheException {
        logger.debug("根据key从存储 key [" + key + "]");
        try {
            cache.set(getByteKey(key), SerializeUtils.serialize(value));
            return value;
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    @Override
    public V remove(K key) throws CacheException {
        logger.debug("从redis中删除 key [" + key + "]");
        try {
            V previous = get(key);
            cache.del(getByteKey(key));
            return previous;
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    @Override
    public void clear() throws CacheException {
        logger.debug("从redis中删除所有元素");
        try {
            cache.flushDB();
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    @Override
    public int size() {
        try {
            Long longSize = new Long(cache.dbSize());
            return longSize.intValue();
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public Set<K> keys() {
        try {
            Set<byte[]> keys = cache.keys(this.keyPrefix + "*");
            if (CollectionUtils.isEmpty(keys)) {
                return Collections.emptySet();
            } else {
                Set<K> newKeys = new HashSet<K>();
                for (byte[] key : keys) {
                    newKeys.add((K) key);
                }
                return newKeys;
            }
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    @Override
    public Collection<V> values() {
        try {
            Set<byte[]> keys = cache.keys(this.keyPrefix + "*");
            if (!CollectionUtils.isEmpty(keys)) {
                List<V> values = new ArrayList<V>(keys.size());
                for (byte[] key : keys) {
                    @SuppressWarnings("unchecked")
                    V value = get((K) key);
                    if (value != null) {
                        values.add(value);
                    }
                }
                return Collections.unmodifiableList(values);
            } else {
                return Collections.emptyList();
            }
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    /**
     * 扩展put方法 增加过期时间
     * @param key
     * @param value
     * @param exporeTime
     * @return
     * @throws CacheException
     */
    public V put(K key, V value, int exporeTime) throws CacheException {
        logger.debug("根据key从存储 key [" + key + "]");
        try {
            cache.set(getByteKey(key), SerializeUtils.serialize(value), exporeTime);
            return value;
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }
}

===========================================
SerializeUtils
===========================================
public class SerializeUtils {

    private static Logger logger = LoggerFactory.getLogger(SerializeUtils.class);

    /**
     * 反序列化
     *
     * @param bytes
     * @return
     */
    public static Object deserialize(byte[] bytes) {

        Object result = null;

        if (isEmpty(bytes)) {
            return null;
        }

        try {
            ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
            try {
                ObjectInputStream objectInputStream = new ObjectInputStream(byteStream);
                try {
                    result = objectInputStream.readObject();
                } catch (ClassNotFoundException ex) {
                    throw new Exception("Failed to deserialize object type", ex);
                }
            } catch (Throwable ex) {
                throw new Exception("Failed to deserialize", ex);
            }
        } catch (Exception e) {
            logger.error("Failed to deserialize", e);
        }
        return result;
    }

    public static boolean isEmpty(byte[] data) {
        return (data == null || data.length == 0);
    }

    /**
     * 序列化
     *
     * @param object
     * @return
     */
    public static byte[] serialize(Object object) {

        byte[] result = null;

        if (object == null) {
            return new byte[0];
        }
        try {
            ByteArrayOutputStream byteStream = new ByteArrayOutputStream(128);
            try {
                if (!(object instanceof Serializable)) {
                    throw new IllegalArgumentException(SerializeUtils.class.getSimpleName() + " requires a Serializable payload " +
                            "but received an object of type [" + object.getClass().getName() + "]");
                }
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream);
                objectOutputStream.writeObject(object);
                objectOutputStream.flush();
                result = byteStream.toByteArray();
            } catch (Throwable ex) {
                throw new Exception("Failed to serialize", ex);
            }
        } catch (Exception ex) {
            logger.error("Failed to serialize", ex);
        }
        return result;
    }
}

===========================================
RedisManager
===========================================
public class RedisManager {

    private String host = "127.0.0.1";

    private int port = 6379;

    // 0 - never expire
    private int expire = 0;

    //timeout for jedis try to connect to redis server, not expire time! In milliseconds
    private int timeout = 0;

    private String password = "";

    private static JedisPool jedisPool = null;

    public RedisManager() {

    }

    /**
     * 初始化方法
     */
    public void init() {
        if (jedisPool == null) {
            if (password != null && !"".equals(password)) {
                jedisPool = new JedisPool(new JedisPoolConfig(), host, port, timeout, password);
            } else if (timeout != 0) {
                jedisPool = new JedisPool(new JedisPoolConfig(), host, port, timeout);
            } else {
                jedisPool = new JedisPool(new JedisPoolConfig(), host, port);
            }

        }
    }

    /**
     * get value from redis
     *
     * @param key
     * @return
     */
    public byte[] get(byte[] key) {
        byte[] value = null;
        Jedis jedis = jedisPool.getResource();
        try {
            value = jedis.get(key);
        } finally {
            jedis.close();
        }
        return value;
    }

    /**
     * set
     *
     * @param key
     * @param value
     * @return
     */
    public byte[] set(byte[] key, byte[] value) {
        Jedis jedis = jedisPool.getResource();
        try {
            jedis.set(key, value);
            if (this.expire != 0) {
                jedis.expire(key, this.expire);
            }
        } finally {
            jedis.close();
        }
        return value;
    }

    /**
     * set
     *
     * @param key
     * @param value
     * @param expire
     * @return
     */
    public byte[] set(byte[] key, byte[] value, int expire) {
        Jedis jedis = jedisPool.getResource();
        try {
            jedis.set(key, value);
            if (expire != 0) {
                jedis.expire(key, expire);
            }
        } finally {
            jedis.close();
        }
        return value;
    }

    /**
     * del
     *
     * @param key
     */
    public void del(byte[] key) {
        Jedis jedis = jedisPool.getResource();
        try {
            jedis.del(key);
        } finally {
            jedis.close();
        }
    }

    /**
     * flush
     */
    public void flushDB() {
        Jedis jedis = jedisPool.getResource();
        try {
            jedis.flushDB();
        } finally {
            jedis.close();
        }
    }

    /**
     * size
     */
    public Long dbSize() {
        Long dbSize = 0L;
        Jedis jedis = jedisPool.getResource();
        try {
            dbSize = jedis.dbSize();
        } finally {
            jedis.close();
        }
        return dbSize;
    }

    /**
     * keys
     *
     * @param pattern
     * @return
     */
    public Set<byte[]> keys(String pattern) {
        Set<byte[]> keys = null;
        Jedis jedis = jedisPool.getResource();
        try {
            keys = jedis.keys(pattern.getBytes());
        } finally {
            jedis.close();
        }
        return keys;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public int getExpire() {
        return expire;
    }

    public void setExpire(int expire) {
        this.expire = expire;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}
===========================================
RedisCacheManager
===========================================
public class RedisCacheManager implements CacheManager{

    private static final Logger logger = LoggerFactory
            .getLogger(RedisCacheManager.class);

    // fast lookup by name map
    private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();

    private RedisManager redisManager;

    /**
     * The Redis key prefix for caches
     */
    private String keyPrefix = "shiro_redis_cache:";

    /**
     * Returns the Redis session keys
     * prefix.
     * @return The prefix
     */
    public String getKeyPrefix() {
        return keyPrefix;
    }

    /**
     * Sets the Redis sessions key
     * prefix.
     * @param keyPrefix The prefix
     */
    public void setKeyPrefix(String keyPrefix) {
        this.keyPrefix = keyPrefix;
    }

    @Override
    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        logger.debug("获取名称为: " + name + " 的RedisCache实例");

        Cache c = caches.get(name);

        if (c == null) {

            // initialize the Redis manager instance
            redisManager.init();

            // create a new cache instance
            c = new RedisCache(redisManager, keyPrefix);

            // add it to the cache collection
            caches.put(name, c);
        }
        return c;
    }

    public RedisManager getRedisManager() {
        return redisManager;
    }

    public void setRedisManager(RedisManager redisManager) {
        this.redisManager = redisManager;
    }
}
===========================================
RedisSessionDAO
===========================================
public class RedisSessionDAO extends AbstractSessionDAO {

    private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);
    /**
     * shiro-redis的session对象前缀
     */
    private RedisManager redisManager;

    /**
     * The Redis key prefix for the sessions
     */
    private String keyPrefix = "shiro_redis_session:";

    @Override
    public void update(Session session) throws UnknownSessionException {
        this.saveSession(session);
    }

    /**
     * save session
     *
     * @param session
     * @throws UnknownSessionException
     */
    private void saveSession(Session session) throws UnknownSessionException {
        if (session == null || session.getId() == null) {
            logger.error("session or session id is null");
            return;
        }

        byte[] key = getByteKey(session.getId());
        byte[] value = SerializeUtils.serialize(session);
        session.setTimeout(redisManager.getExpire() * 1000);
        this.redisManager.set(key, value, redisManager.getExpire());
    }

    @Override
    public void delete(Session session) {
        if (session == null || session.getId() == null) {
            logger.error("session or session id is null");
            return;
        }
        redisManager.del(this.getByteKey(session.getId()));

    }

    /**
     * 用来统计当前活动的session
     * @return
     */
    @Override
    public Collection<Session> getActiveSessions() {
        Set<Session> sessions = new HashSet<Session>();

        Set<byte[]> keys = redisManager.keys(this.keyPrefix + "*");
        if (keys != null && keys.size() > 0) {
            for (byte[] key : keys) {
                Session s = (Session) SerializeUtils.deserialize(redisManager.get(key));
                sessions.add(s);
            }
        }

        return sessions;
    }

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = this.generateSessionId(session);
        this.assignSessionId(session, sessionId);
        this.saveSession(session);
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        if (sessionId == null) {
            logger.error("session id is null");
            return null;
        }

        Session s = (Session) SerializeUtils.deserialize(redisManager.get(this.getByteKey(sessionId)));
        return s;
    }

    /**
     * 获得byte[]型的key
     *
     * @param sessionId
     * @return
     */
    private byte[] getByteKey(Serializable sessionId) {
        String preKey = this.keyPrefix + sessionId;
        return preKey.getBytes();
    }

    public RedisManager getRedisManager() {
        return redisManager;
    }

    public void setRedisManager(RedisManager redisManager) {
        this.redisManager = redisManager;

        /**
         * 初始化redisManager
         */
        this.redisManager.init();
    }

    /**
     * Returns the Redis session keys
     * prefix.
     *
     * @return The prefix
     */
    public String getKeyPrefix() {
        return keyPrefix;
    }

    /**
     * Sets the Redis sessions key
     * prefix.
     *
     * @param keyPrefix The prefix
     */
    public void setKeyPrefix(String keyPrefix) {
        this.keyPrefix = keyPrefix;
    }
}

===========================================
RetryLimitCredentialsMatcher
===========================================
public class RetryLimitCredentialsMatcher extends HashedCredentialsMatcher {

    private static final String CACHE_KEY = "password_retry_cache";

    private Cache<String, AtomicInteger> passwordRetryCache;

    //尝试次数 默认3次
    private int tryLimitTimes = 3;

    //tryLimitCount失败后 可重试间隔时间 单位秒 默认5分钟
    private int delayTime = 5 * 60;

    public void setTryLimitTimes(int tryLimitTimes) {
        this.tryLimitTimes = tryLimitTimes;
    }

    public void setDelayTime(int delayTime) {
        this.delayTime = delayTime;
    }

    public RetryLimitCredentialsMatcher(CacheManager cacheManager) {
        //获取密码重试缓存cache组件 存储在一个concurrenthashmap中
        passwordRetryCache = cacheManager.getCache(CACHE_KEY);
    }

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token,
                                      AuthenticationInfo info) {
        String username = (String) token.getPrincipal();
        String key = CACHE_KEY + ":" + username;
        RedisCache cache = (RedisCache)passwordRetryCache;
        // retry count + 1
        AtomicInteger retryCount = passwordRetryCache.get(key);
        if (retryCount == null) {
            retryCount = new AtomicInteger(0);
            cache.put(key, retryCount, delayTime);
        }
        if (retryCount.incrementAndGet() > tryLimitTimes) {
            // if retry count > tryLimitTimes throw
            throw new ExcessiveAttemptsException(String.valueOf(delayTime));
        }

        boolean matches = super.doCredentialsMatch(token, info);
        if (matches) {
            // clear retry count
            cache.remove(key);
        }else{
            cache.put(key, retryCount, delayTime);
        }

        return matches;
    }
}

===========================================
ShiroConfiguration
===========================================
@Configuration
public class ShiroConfiguration {

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
            @Qualifier("securityManager") SecurityManager securityManager
    ) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
        daap.setProxyTargetClass(true);
        return daap;
    }

    /**
     * Shiro生命周期处理器
     *
     * @return
     */
    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        LifecycleBeanPostProcessor postProcessor = new LifecycleBeanPostProcessor();
        return postProcessor;
    }

    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在
     * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
     * <p>
     * Filter Chain定义说明
     * 1、一个URL可以配置多个Filter,使用逗号分隔
     * 2、当设置多个过滤器时,全部验证通过,才视为通过
     * 3、部分过滤器可指定参数,如perms,roles
     * <p>
     * anon org.apache.shiro.web.filter.authc.AnonymousFilter
     * authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
     * authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
     * logout org.apache.shiro.web.filter.authc.LogoutFilter
     * noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter
     * perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
     * port org.apache.shiro.web.filter.authz.PortFilter
     * rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
     * roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
     * ssl org.apache.shiro.web.filter.authz.SslFilter
     * user org.apache.shiro.web.filter.authc.UserFilter
     * <p>
     * /** = anon
     * /page/login.jsp = anon 所有url都都可以匿名访问
     * /page/register/* = anon
     * /page/index.jsp = authc  所有url都必须认证通过才可以访问
     * /page/addItem* = authc,roles[数据管理员]
     * /page/file* = authc,roleOR[普通用户,数据管理员]
     * /page/listItems* = authc,roleOR[数据管理员,普通用户]
     * /page/showItem* = authc,roleOR[数据管理员,普通用户]
     * <p>
     */
    @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(
            @Qualifier("securityManager") SecurityManager securityManager
    ) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        //设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        //如果配置loginUrl 那么loginUrl必须存在
        //不然登陆成功后 未退出再次访问loginUrl(当前adminlogin) 会出现not found 404问题
        shiroFilterFactoryBean.setLoginUrl("/adminlogin");
//      //登录成功后要跳转的链接
//      shiroFilterFactoryBean.setSuccessUrl("/index");
//      //未授权界面;
//      shiroFilterFactoryBean.setUnauthorizedUrl("/403");

//      //如果使用shiro注解 必须将如下代码注释=================================
        //设置自定义filter
        Map<String, Filter> map = shiroFilterFactoryBean.getFilters();
        map.put("authc", new MyFormAuthenticationFilter());//所有配置authc链接都会执行此filter
        map.put("roles", new MyRoleAuthorizationFilter());//所有配roles链接都会执行此filter
        map.put("perms", new MyPermAuthorizationFilter());//所有配置perms链接都会执行此filter
        //自定义可以为access 所有配置access链接都会执行此filter
        MyAccessControlFilter accessControlFilter = new MyAccessControlFilter();
        map.put("access", accessControlFilter);

        //拦截器.
        Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
        // 配置不会被拦截的链接 顺序判断
        //filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/sessions*", "anon");
        filterChainDefinitionMap.put("/adminlogout", "authc");
        filterChainDefinitionMap.put("/switch", "authc");

        /**
         * 1.访问/admin/manage路径 应该具有admin:manage权限
         * 2.访问/admin/manage1路径 应该具有admin:manage1权限
         * 3.在MyShiroRealm的doGetAuthorizationInfo方法中会加载用户权限
         * 4.当前例子中只模拟了有admin:manage权限没有admin:manage1权限
         * */
        filterChainDefinitionMap.put("/admin/manage*", "authc,perms[admin:manage],roles[admin]");
        filterChainDefinitionMap.put("/admin/test", "authc,perms[admin:test");
        // 过滤链定义,从上向下顺序执行,一般将 /** 放在最为下边
        // access表示上面配置的new MyAccessControlFilter() 所有匹配/**的链接都要经过此filter
        filterChainDefinitionMap.put("/**", "authc, access");

        System.out.println("Shiro拦截器工厂类注入成功");
//      //================================================================
        return shiroFilterFactoryBean;
    }

    /**
     * 自定义session监听器
     *
     * @return
     */
    @Bean("sessionListener")
    public MySessionListener sessionListener() {
        return new MySessionListener();
    }

     /**
     * session检测定时调度器
     *
     * @return
     */
    @Bean("sessionValidationScheduler")
    public SessionValidationScheduler sessionValidationScheduler(
            @Qualifier("sessionManager") DefaultWebSessionManager sessionManager
    ) {
        QuartzSessionValidationScheduler scheduler = new QuartzSessionValidationScheduler();
        //设置session的失效扫描间隔,单位为毫秒
        scheduler.setSessionValidationInterval(1800000);
        scheduler.setSessionManager(sessionManager);
        return scheduler;
    }

    /**
     * session管理器
     *
     * @param sessionDAO
     * @param sessionListener
     * @return
     */
    @Bean("sessionManager")
    public DefaultWebSessionManager defaultWebSessionManager(
            //@Qualifier("sessionDAO") EnterpriseCacheSessionDAO sessionDAO
            @Qualifier("redisSessionDAO") RedisSessionDAO sessionDAO,
            @Qualifier("sessionListener") SessionListener sessionListener
    ) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        List<SessionListener> sessionListeners = new LinkedList<SessionListener>();
        sessionListeners.add(sessionListener);
        sessionManager.setSessionListeners(sessionListeners);
        //session 有效时间为半小时(毫秒单位)
        //如果缓存中session过期(如redis) session会自动重新创建
        sessionManager.setGlobalSessionTimeout(1800000);
        //开启调度器检测session有效性 依赖于sessionValidationScheduler
        sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setSessionDAO(sessionDAO);
        sessionManager.setDeleteInvalidSessions(true);
        Cookie sessionIdCookie = sessionManager.getSessionIdCookie();
        //sessionIdCookie.setDomain(".samson.com");
        sessionIdCookie.setPath("/");
        sessionIdCookie.setName("shiro_test_cookie");
        return sessionManager;
    }

    /**
     * 可自定义Authenticator 继承ModularRealmAuthenticator 复写doMultiRealmAuthentication方法
     * 实现多realms处理
     */
//    @Bean
//    public MyModularRealmAuthenticator modularRealmAuthenticator(){
//        MyModularRealmAuthenticator authenticator = new MyModularRealmAuthenticator();
//        authenticator.setRealms();
//        FirstSuccessfulStrategy strategy = new FirstSuccessfulStrategy();
//        authenticator.setAuthenticationStrategy(strategy);
//        return authenticator;
//    }

    /**
     * 安全管理器
     *
     * @param realm
     * @param cacheManager
     * @param sessionManager
     * @return
     */
    @Bean("securityManager")
    public SecurityManager securityManager(
            @Qualifier("myShiroRealm") MyShiroRealm realm,
            //@Qualifier("EhCacheManager") EhCacheManager cacheManager,
            @Qualifier("redisCacheManager") RedisCacheManager cacheManager,
            @Qualifier("sessionManager") DefaultWebSessionManager sessionManager
    ) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        securityManager.setCacheManager(cacheManager);
        securityManager.setSessionManager(sessionManager);
        CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
        rememberMeManager.getCookie().setName("shiro_rememberme");
        //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
        //rememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
        rememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
        //rememberMeManager.getCookie().setDomain(".samson.com");
        rememberMeManager.getCookie().setPath("/");
        rememberMeManager.getCookie().setMaxAge(604800);//7天有效期
        securityManager.setRememberMeManager(rememberMeManager);
        //管理多个realms时 默认第一个成功便结束
        //如果有更多需求可以 自定义Authenticator 复写doMultiRealmAuthentication方法
        //设置realms 不设置realm 自定义Authenticator 实现自处理多realms
        //securityManager.setRealms();
        //securityManager.setAuthenticator();
        return securityManager;
    }


    /**
     * 尝试次数限制匹配器
     *
     * @param cacheManager
     * @return
     */
    @Bean("retryLimitCredentialsMatcher")
    public RetryLimitCredentialsMatcher retryLimitCredentialsMatcher(
            @Qualifier("redisCacheManager") RedisCacheManager cacheManager
    ) {
        RetryLimitCredentialsMatcher matcher = new RetryLimitCredentialsMatcher(cacheManager);
        //失败后30秒后可重试
        matcher.setDelayTime(30);
        //密码错误 尝试次数
        matcher.setTryLimitTimes(2);
        //使用md5加密
        matcher.setHashAlgorithmName("MD5");
        //循环执行5次加密
        matcher.setHashIterations(5);
        return matcher;
    }

    /**
     * 自定义realm(账号密码校验、权限加载等)
     *
     * @return
     */
    @Bean("myShiroRealm")
    public MyShiroRealm myShiroRealm(
            //@Qualifier("EhCacheManager") EhCacheManager cacheManager
            @Qualifier("redisCacheManager") RedisCacheManager cacheManager,
            @Qualifier("retryLimitCredentialsMatcher")CredentialsMatcher credentialsMatcher
    ) {
        MyShiroRealm realm = new MyShiroRealm();
        realm.setCacheManager(cacheManager);
        realm.setCredentialsMatcher(credentialsMatcher);
        return realm;
    }

    //分布式redis管理缓存和session start=======================================

    @Bean("redisCache")
    public RedisCache redisCache(
            @Qualifier("redisManager") RedisManager redisManager
    ) {
        RedisCache redisCache = new RedisCache(redisManager);
        return redisCache;
    }

    @Bean("redisManager")
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost("172.16.30.57");
        redisManager.setPassword("mapollo2@15");
        redisManager.setPort(6379);
        //连接超时时间 单位毫秒
        redisManager.setTimeout(2000);
        //过期时间 单位秒
        //当session共享时 过期后缓存session会被清除 如果未到session超时时间 会自动重新创建session
        //使用含有exipre参数的set方法 expire时间不受全局控制
        //redisManager.setExpire(15);
        return redisManager;
    }

    @Bean("redisSessionDAO")
    public RedisSessionDAO redisSessionDAO(
            @Qualifier("redisManager") RedisManager redisManager
    ) {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager);
        return redisSessionDAO;
    }

    @Bean("redisCacheManager")
    public RedisCacheManager redisCacheManager(
            @Qualifier("redisManager") RedisManager redisManager
    ) {
        RedisCacheManager cacheManager = new RedisCacheManager();
        cacheManager.setRedisManager(redisManager);
        return cacheManager;
    }
    //分布式redis管理缓存和session end=======================================

    /**
     * 默认sessionDAO
     */
//    @Bean("sessionDAO")
//    public EnterpriseCacheSessionDAO enterpriseCacheSessionDAO(){
//        return new EnterpriseCacheSessionDAO();
//    }

    /**
     * ehcacheManger做缓存 单机使用
     *
     * @return
     */
//    @Bean("EhCacheManager")
//    public EhCacheManager getEhCacheManager() {
//        EhCacheManager em = new EhCacheManager();
//        em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
//        return em;
//    }


}

===========================================
ShiroSecurityHelper
===========================================
public class ShiroSecurityHelper {

    /**
     * 获得当前用户名
     *
     * @return
     */
    public static String getCurrentUsername() {
        Subject subject = getSubject();
        PrincipalCollection collection = subject.getPrincipals();
        if (null != collection && !collection.isEmpty()) {
            return (String) collection.iterator().next();
        }
        return null;
    }

    /**
     * 获取当前session
     *
     * @return
     */
    public static Session getSession(boolean created) {
        return SecurityUtils.getSubject().getSession(created);
    }

    /**
     * 获取当前sessionId
     *
     * @return
     */
    public static String getSessionId() {
        Session session = getSession(false);
        if (null == session) {
            return null;
        }
        return getSession(true).getId().toString();
    }

    /**
     * 判断当前用户是否已通过认证
     *
     * @return
     */
    public static boolean hasAuthenticated() {
        return getSubject().isAuthenticated();
    }

    private static Subject getSubject() {
        return SecurityUtils.getSubject();
    }

    public static void setAttribute(String key, Object value) {
        getSession(false).setAttribute(key, value);
    }

    public static Object getAttribute(String key) {
        return getSession(false).getAttribute(key);
    }

    /**
     * 获取指定类型realm
     *
     * @param clazz
     * @return
     */
    public static Realm getRealm(Class clazz) {
        RealmSecurityManager rsm = (RealmSecurityManager) SecurityUtils.getSecurityManager();
        Collection<Realm> realms = rsm.getRealms();
        if (CollectionUtils.isNotEmpty(realms)) {
            Iterator<Realm> iterator = realms.iterator();
            while (iterator.hasNext()) {
                Realm realm = iterator.next();
                if (realm.getClass() == clazz) {
                    return realm;
                }
            }
        }
        return null;
    }

}
===========================================
MySessionListener
===========================================
public class MySessionListener implements SessionListener {

    /**
     * 会话创建触发 已进入shiro的过滤连就触发这个方法
     *
     * @param session
     */
    @Override
    public void onStart(Session session) {
        // TODO Auto-generated method stub
        System.out.println("会话创建:" + session.getId());
    }

    /**
     * 退出
     *
     * @param session
     */
    @Override
    public void onStop(Session session) {
        // TODO Auto-generated method stub
        System.out.println("退出会话:" + session.getId());
    }

    /**
     * 会话过期时触发
     *
     * @param session
     */
    @Override
    public void onExpiration(Session session) {//
        // TODO Auto-generated method stub
        System.out.println("会话过期:" + session.getId());
    }

}
===========================================
Constants
===========================================
public final class Constants {

    private Constants(){}

    public static final String SESSION_FORCE_LOGOUT_KEY = "SESSION_FORCE_LOGOUT_KEY";

}


===========================================
SessionController
===========================================
@Controller
@RequestMapping("/sessions")
public class SessionController {

    @Autowired
    private SessionDAO sessionDAO;

    /**
     * 获取列表
     *
     * @return
     */
    @RequestMapping("list")
    @ResponseBody
    public Map<String, Object> list() {
        Collection<Session> sessions = sessionDAO.getActiveSessions();
        //Page<Session> getActiveSessions(int pageNumber, int pageSize);
        String sessionId = null;
        int size = 0;
        if (CollectionUtils.isNotEmpty(sessions)) {
            sessionId = (String) sessions.iterator().next().getId();
            size = sessions.size();
        }
        Map<String, Object> resultMap = new LinkedHashMap<String, Object>();
        resultMap.put("status", 200);
        resultMap.put("message", sessionId + " " + size);
        return resultMap;
    }

    /**
     * 强制退出
     *
     * @param sessionId
     * @return
     */
    @RequestMapping("/{sessionId}/forceLogout")
    @ResponseBody
    public Map<String, Object> forceLogout(@PathVariable("sessionId") String sessionId) {
        Map<String, Object> resultMap = new LinkedHashMap<String, Object>();
        try {
            //查询session是否存在
            Session session = sessionDAO.readSession(sessionId);
            if (session != null) {
                //sessionDAO.delete(session);
                //当前通过id获取的session 告知删除 filter会根据含有logout_key的session 清除
                session.setAttribute(Constants.SESSION_FORCE_LOGOUT_KEY, Boolean.TRUE);
                resultMap.put("status", 200);
                resultMap.put("message", "OK");
                return resultMap;
            }
        } catch (Exception e) {
        }
        resultMap.put("status", 500);
        resultMap.put("message", "FAIL");
        return resultMap;
    }
}

===========================================
TestController
===========================================
@RestController
public class TestController {

    private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);

    //FIXME 将登陆操作迁移到MyFormAuthenticationFilter中 增加验证码验证
//    @RequestMapping(value = "/adminlogin", method = RequestMethod.GET)
//    @ResponseBody
//    public Map<String, Object> submitLogin(String username, String password, HttpServletRequest request) {
//        Map<String, Object> resultMap = new LinkedHashMap<String, Object>();
//
//        CaptchaUsernamePasswordToken token = new CaptchaUsernamePasswordToken(
//                "admin", "111111", true, Util.getClientIP(request), "A1DC");
//        //获取当前的Subject
//        Subject currentUser = SecurityUtils.getSubject();
//        try {
//            //在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
//            //每个Realm都能在必要时对提交的AuthenticationTokens作出反应
//            //所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
//            logger.info("对用户[" + username + "]进行登录验证..验证开始");
//            currentUser.login(token);
//            logger.info("对用户[" + username + "]进行登录验证..验证通过");
//            //设置session超时 为负数时表示永不超时
//            //SecurityUtils.getSubject().getSession().setTimeout(30000);
//        } catch (IncorrectCaptchaException ice) {
//            logger.info("对用户[" + username + "]进行登录验证..验证码不正确");
//            resultMap.put("status", 500);
//            resultMap.put("message", "验证码不正确");
//            return resultMap;
//        } catch (UnknownAccountException uae) {
//            logger.info("对用户[" + username + "]进行登录验证..验证未通过,未知账户");
//            resultMap.put("status", 500);
//            resultMap.put("message", "未知账户");
//            return resultMap;
//        } catch (IncorrectCredentialsException ice) {
//            logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误的凭证");
//            resultMap.put("status", 500);
//            resultMap.put("message", "密码不正确");
//            return resultMap;
//        } catch (LockedAccountException lae) {
//            logger.info("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定");
//            resultMap.put("status", 500);
//            resultMap.put("message", "账户已锁定");
//            return resultMap;
//        } catch (ExcessiveAttemptsException eae) {
//            logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误次数过多");
//            resultMap.put("status", 500);
//            resultMap.put("message", "用户名或密码错误次数过多");
//            return resultMap;
//        } catch (DisabledAccountException ex) {
//            logger.info("对用户[" + username + "]进行登录验证..验证未通过,帐号已经禁止");
//            resultMap.put("status", 500);
//            resultMap.put("message", "帐号已经禁止");
//            return resultMap;
//        } catch (AuthenticationException ae) {
//            //通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景
//            logger.info("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下");
//            ae.printStackTrace();
//            resultMap.put("status", 500);
//            resultMap.put("message", "用户名或密码不正确");
//            return resultMap;
//        }
//        //验证是否登录成功
//        if (currentUser.isAuthenticated()) {
//            logger.info("用户[" + username + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)");
//            resultMap.put("status", 200);
//            resultMap.put("message", "登陆成功");
//            return resultMap;
//        } else {
//            token.clear();
//            resultMap.put("status", 500);
//            resultMap.put("message", "未授权");
//            return resultMap;
//        }
//    }

    /**
     * 这个地址必须存在 不然设置remember 登陆一次成功后 再调用adminlogin会出现not found404问题
     *
     * @return
     */
    @RequestMapping(value = "/adminlogin", method = RequestMethod.GET)
    @ResponseBody
    public Map<String, Object> submitLogin() {
        ShiroSecurityHelper.setAttribute("key", Boolean.TRUE);
        Map<String, Object> resultMap = new LinkedHashMap<String, Object>();
        resultMap.put("status", 200);
        resultMap.put("message", "你好");
        return resultMap;
    }

    /**
     * 退出
     *
     * @return
     */
    @RequestMapping(value = "adminlogout", method = RequestMethod.GET)
    @ResponseBody
    public Map<String, Object> logout() {

        //退出
        Subject subject = SecurityUtils.getSubject();
        subject.logout();

        Map<String, Object> resultMap = new LinkedHashMap<String, Object>();
        resultMap.put("status", 200);
        resultMap.put("message", "退出成功");
        return resultMap;
    }

    /**
     * 修改密码
     *
     * @return
     */
    //@RequiresPermissions(value = {"admin:manage"}, logical = Logical.OR)
    @RequestMapping(value = "admin/manage/modifypass", method = RequestMethod.GET)
    @ResponseBody
    public Map<String, Object> modifypass() {

        //FIXME 修改密码
        //修改密码成功 退出 重新登录
        Subject subject = SecurityUtils.getSubject();
        subject.logout();

        Map<String, Object> resultMap = new LinkedHashMap<String, Object>();
        resultMap.put("status", 200);
        resultMap.put("message", "修改成功 请重新登录");
        return resultMap;


    }

    /**
     * 修改权限
     *
     * @return
     */
    //@RequiresPermissions(value = {"admin:manage"}, logical = Logical.OR)
    @RequestMapping(value = "admin/manage/modifyauth", method = RequestMethod.GET)
    @ResponseBody
    public Map<String, Object> modifyauth() {

        //修改admin权限 重新修改权限后清除缓存,会调用doGetAuthorizationInfo重新取用户角色的权限信息
        MyShiroRealm shiroRealm = (MyShiroRealm) ShiroSecurityHelper.getRealm(MyShiroRealm.class);
        //subject为当前操作人
        Subject subject = SecurityUtils.getSubject();
        //获取realm名称
        String realmName = subject.getPrincipals().getRealmNames().iterator().next();

        //第一个参数为要修改权限的用户名,第二个参数为realmName
        String modifyAuthUserName = "user";
        SimplePrincipalCollection principals = new SimplePrincipalCollection(modifyAuthUserName, realmName);
        subject.runAs(principals);
        shiroRealm.getAuthorizationCache().remove(subject.getPrincipals());
        shiroRealm.getAuthorizationCache().remove(modifyAuthUserName);
        subject.releaseRunAs();

        Map<String, Object> resultMap = new LinkedHashMap<String, Object>();
        resultMap.put("status", 200);
        resultMap.put("message", "修改成功");
        return resultMap;

    }

    /**
     * 切换用户 由user用户切换成user1账户 具有user1的所有权限
     *
     * @return
     */
    @RequestMapping(value = "switchuser", method = RequestMethod.GET)
    @ResponseBody
    public Map<String, Object> switchUser() {

        Subject subject = SecurityUtils.getSubject();
        //user1表示要切换成的用户名
        subject.runAs(new SimplePrincipalCollection("user1", ""));

        Map<String, Object> resultMap = new LinkedHashMap<String, Object>();
        resultMap.put("status", 200);
        resultMap.put("message", "切换用户成功");
        return resultMap;
    }


    //==================================================================

    /**
     * 测试有权限
     *
     * @return
     */
//    @RequiresPermissions(value = {"admin:manage"}, logical = Logical.OR)
//    @RequiresUser
    @RequestMapping(value = "admin/manage", method = RequestMethod.GET)
    @ResponseBody
    public Map<String, Object> adminManage() {
        //throw new UnauthenticatedException();
        System.out.println(ShiroSecurityHelper.getAttribute("key"));

        Map<String, Object> resultMap = new LinkedHashMap<String, Object>();

        resultMap.put("status", 200);
        resultMap.put("message", "有权限");

        return resultMap;
    }

    /**
     * 测试有了admin:manage权限 可以访问admin/manage/edit
     *
     * @return
     */
    //@RequiresRoles()
//    @RequiresPermissions(value = {"admin:manage"}, logical = Logical.OR)
//    @RequiresUser
    @RequestMapping(value = "admin/manage/edit", method = RequestMethod.GET)
    @ResponseBody
    public Map<String, Object> adminManageEdit() {
        //throw new UnauthenticatedException();
        Map<String, Object> resultMap = new LinkedHashMap<String, Object>();

        resultMap.put("status", 200);
        resultMap.put("message", "有权限");

        return resultMap;
    }

    /**
     * 测试没有权限
     * MyShiroRealm 为添加admin:manage1权限给用户
     *
     * @return
     */
    //@RequiresRoles()
//    @RequiresPermissions("admin:test")
//    @RequiresUser
    @RequestMapping(value = "admin/test", method = RequestMethod.GET)
    @ResponseBody
    public Map<String, Object> test() {
        Map<String, Object> resultMap = new LinkedHashMap<String, Object>();

        resultMap.put("status", 200);
        resultMap.put("message", "有权限");

        return resultMap;
    }

    //=====================================================================================

   /**
     * 使用注解 起作用 必须先注释拦截器(filter)配置 登录认证异常
     * @RequiresPermissions
     * @RequiresRoles
     * @RequiresUser
     */
    @ExceptionHandler({UnauthenticatedException.class, AuthenticationException.class})
    @ResponseBody
    public Map<String, Object> authenticationException(HttpServletRequest request, HttpServletResponse response) {
        // 输出JSON
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code", "-999");
        map.put("message", "未登录");
        return map;
    }

    /**
     * 使用注解 起作用 必须先注释拦截器(filter)配置 权限异常
     * @RequiresPermissions
     * @RequiresRoles
     * @RequiresUser
     */
    @ExceptionHandler({UnauthorizedException.class, AuthorizationException.class})
    @ResponseBody
    public Map<String, Object> authorizationException(HttpServletRequest request, HttpServletResponse response) {
        // 输出JSON
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code", "-998");
        map.put("message", "无权限");
        return map;
    }


}
===========================================
MyAccessControlFilter
===========================================
public class MyAccessControlFilter extends AccessControlFilter {

    /**
     * 返回false会继续执行onAccessDenied方法
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     * @throws Exception
     */
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        Subject subject = getSubject(request, response);
        Session session = subject.getSession();
        if(session == null) {
            return true;
        }
        return session.getAttribute(Constants.SESSION_FORCE_LOGOUT_KEY) == null;
    }

    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        Subject subject = getSubject(request, response);
        try {
            //强制退出
            subject.logout();
        } catch (Exception e){
            e.printStackTrace();
        }
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        Util.writeJson("已经被踢出,请重新登录", 200, httpResponse);
        return false;
    }

}

===========================================
MyFormAuthenticationFilter
===========================================
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {

    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //isLoginRequest 验证登陆url和配置的loginurl是否一致
        if (isLoginRequest(request, response)) {
            //fixme isLoginSubmission 验证请求是否为post请求 当前测试为get方式 所以将此代码注释
            // if (isLoginSubmission(request, response)) {
            //                return executeLogin(request, response);
            //            } else {
            //                return true;
            //            }
            return executeLogin(request, response);
        } else {

            HttpServletResponse httpResponse = (HttpServletResponse) response;
//            Session session = getSubject(request, response).getSession(false);
//            if(session.getAttribute(Constants.SESSION_FORCE_LOGOUT_KEY) != null){
//                Util.writeJson("已被强制线下,请登录!", httpResponse);
//            }else{
//                Util.writeJson("请登录!", httpResponse);
//            }
            Util.writeJson("请登录!", 500,  httpResponse);
            return false;
        }

    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        CaptchaUsernamePasswordToken token = new CaptchaUsernamePasswordToken(
                "user", "111211", true, "127.0.0.1", "A1DC");
        try {
            //验证验证码正确性
            doCaptchaValidate(httpServletRequest, token);
            Subject subject = getSubject(request, response);
            subject.login(token);
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            return onLoginFailure(token, e, request, response);
        }
    }

    /**
     * 验证码校验
     *
     * @param request
     * @param token
     */
    protected void doCaptchaValidate(HttpServletRequest request,
                                     CaptchaUsernamePasswordToken token) {
        //session中的图形码字符串
//        String captcha = (String) request.getSession().getAttribute(
//                com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
        String captcha = "A1DC";
        //比对
        if (captcha != null && !captcha.equalsIgnoreCase(token.getCaptcha())) {
            throw new IncorrectCaptchaException("验证码错误!");
        }else{
            //清除验证码
            //request.getSession().removeAttribute("");
        }
    }

    /**
     * 当登录成功
     *
     * @param token
     * @param subject
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
                                     ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        Util.writeJson("登陆成功", 200, httpResponse);
        return false;
    }

    /**
     * 当登录失败
     *
     * @param token
     * @param e
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
                                     ServletRequest request, ServletResponse response) {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        String ex = e.getClass().getSimpleName();
        if ("IncorrectCredentialsException".equals(ex)) {
            Util.writeJson("密码错误", 500, httpResponse);
        } else if ("UnknownAccountException".equals(ex)) {
            Util.writeJson("账号不存在", 500, httpResponse);
        } else if ("LockedAccountException".equals(ex)) {
            Util.writeJson("账号被锁定", 500, httpResponse);
        } else if ("IncorrectCaptchaException".equals(ex)) {
            Util.writeJson("验证码不正确", 500, httpResponse);
        } else if("ExcessiveAttemptsException".equals(ex)){
            Util.writeJson("尝试次数过多,请"+e.getMessage()+"秒后再试", 500, httpResponse);
        }else {
            Util.writeJson("未知错误", 500, httpResponse);
        }
        return false;
    }

}
===========================================
MyPermAuthorizationFilter
===========================================
public class MyPermAuthorizationFilter extends PermissionsAuthorizationFilter {

    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        Subject subject = getSubject(request, response);

//        if (subject.getPrincipal() == null) {
//            if (com.silvery.utils.WebUtils.isAjax(httpRequest)) {
//                com.silvery.utils.WebUtils.sendJson(httpResponse, JsonUtils.toJSONString(new ViewResult(false,
//                        "您尚未登录或登录时间过长,请重新登录!")));
//            } else {
//                saveRequestAndRedirectToLogin(request, response);
//            }
//        } else {
//            if (com.silvery.utils.WebUtils.isAjax(httpRequest)) {
//                com.silvery.utils.WebUtils.sendJson(httpResponse, JsonUtils.toJSONString(new ViewResult(false,
//                        "您没有足够的权限执行该操作!")));
//            } else {
//                String unauthorizedUrl = getUnauthorizedUrl();
//                if (StringUtils.hasText(unauthorizedUrl)) {
//                    WebUtils.issueRedirect(request, response, unauthorizedUrl);
//                } else {
//                    WebUtils.toHttp(response).sendError(401);
//                }
//            }
//        }

        if (subject.getPrincipal() == null) {
            Util.writeJson("您尚未登录或登录时间过长,请重新登录!", 500, httpResponse);
        } else {
            Util.writeJson("您没有足够的权限执行该操作!", 500, httpResponse);
        }
        return false;
    }

}
===========================================
MyRoleAuthorizationFilter
===========================================
public class MyRoleAuthorizationFilter extends RolesAuthorizationFilter {

    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        Subject subject = getSubject(request, response);

        if (subject.getPrincipal() == null) {
            Util.writeJson("您尚未登录或登录时间过长,请重新登录!", 500, httpResponse);
        } else {
            Util.writeJson("您的角色没有足够的权限执行该操作!", 500, httpResponse);
        }
        return false;
    }

}

===========================================
Util
===========================================
public class Util {

    public static void writeJson(String msg, int status, HttpServletResponse response) {
        PrintWriter out = null;
        try {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            out = response.getWriter();
            //out.write(JsonUtil.mapToJson(map));
            out.write(msg);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }

}
分享到:
评论

相关推荐

    SpringBoot整合Shiro示例实现动态权限加载更新+Session共享+单点登录

    SpringBoot整合Shiro示例实现动态权限加载更新+Session共享+单点登录 SpringBoot整合Shiro示例实现动态权限加载更新+Session共享+单点登录 SpringBoot整合Shiro示例实现动态权限加载更新+Session共享+单点登录 ...

    基于springboot+shiro+jwt+vue+redis的后台管理系统源码.zip

    1、基于springboot+shiro+jwt+vue+redis的后台管理系统源码.zip 2、该资源包括项目的全部源码,下载可以直接使用! 3、本项目适合作为计算机、数学、电子信息等专业的课程设计、期末大作业和毕设项目,作为参考资料...

    Springboot+shiro单点登录实现.md

    Springboot+shiro单点登录实现,本文档是单点登录的全部源代码。

    springboot+shiro+redis整合

    在IT行业中,SpringBoot、Shiro和Redis是三个非常重要的技术组件,它们分别在不同的领域发挥着关键作用。本文将详细讲解如何将这三个组件整合在一起,实现一个高效、安全的Web应用。 首先,SpringBoot是Spring框架...

    springboot 与shiro安全框架的整合

    - 配置Shiro:创建一个自定义的Shiro配置类,配置Realm以连接数据库进行身份验证和授权。 - 配置Web安全:定义Shiro的Web过滤器,并在SpringBoot的`WebSecurityConfig`类中添加Shiro的过滤链。 - 创建 Realm:...

    SpringBoot与Shiro整合-权限管理实战源码.zip

    在IT行业中,SpringBoot和Apache Shiro是两个非常重要的框架,它们在开发高效、安全的Web应用程序中扮演着核心角色。SpringBoot简化了Spring应用程序的配置和开发过程,而Shiro则是一个强大的安全管理框架,专注于...

    springboot 集成shiro代码实例

    - 使用Shiro标签库:在视图层可以使用Shiro提供的标签,如`@shiro:user`,`@shiro:hasRole`等,进行权限控制。 5. **实战示例**:`spring-boot-shiro`这个文件可能包含了完整的集成示例,包括配置文件、 Realm实现...

    springboot-shiro

    《SpringBoot与Shiro构建细粒度动态权限管理系统详解》 在现代Web开发中,权限管理是不可或缺的一部分,尤其对于企业级应用来说更是如此。SpringBoot以其轻量级、便捷的特性,已经成为Java开发的首选框架之一。而...

    springboot+shiro+layuimini实现后台管理系统的权限控制

    本项目采用SpringBoot、Apache Shiro以及LayuiMini框架来实现这一目标。接下来,我们将深入探讨这三个技术栈如何协同工作,构建出高效、安全的权限管理体系。 **SpringBoot** SpringBoot是Spring框架的轻量级衍生品...

    SpringBoot + Shiro实现前后端全分离接口安全框架

    SpringBoot与Shiro的结合为开发者提供了一个高效、便捷的实现前后端全分离接口安全的框架。本文将深入探讨如何利用这两个强大的工具构建安全的Web服务。 首先,SpringBoot是Spring框架的一个轻量级版本,它简化了...

    SpringBoot+Shiro学习之密码加密和登录失败次数限制示例

    SpringBoot+Shiro学习之密码加密和登录失败次数限制示例 ...该示例可以帮助开发者更好地理解 SpringBoot 和 Shiro 框架的整合,并且提供了一个基于 SpringBoot 和 Shiro 框架的密码加密和登录失败次数限制的解决方案。

    基于SpringBoot +Shiro+ Layui 构建的商城商店系统.zip

    基于SpringBoot +Shiro+ Layui 构建的商城商店系统 基于SpringBoot +Shiro+ Layui 构建的商城商店系统 基于SpringBoot +Shiro+ Layui 构建的商城商店系统 基于SpringBoot +Shiro+ Layui 构建的商城商店系统 基于...

    springboot整合shiro

    **SpringBoot整合Shiro**是将流行的Java Web框架Spring Boot与安全管理框架Apache Shiro结合,以构建高效、安全的Web应用程序。Spring Boot以其简洁的配置和快速的开发体验深受开发者喜爱,而Shiro则提供了全面的...

    采用SpringBoot+Shiro+MyBatis+EasyUI实现的一个进销存管理系统,适合web全栈学习,毕业设计,课设

    采用SpringBoot+Shiro+MyBatis+EasyUI实现的一个进销存管理系统,适合web全栈学习,毕业设计,课设 项目经过严格测试,确保可以运行! # 企业级进销存管理系统 进销存管理系统,采用SpringBoot+Shiro+MyBatis+...

    SpringBoot集成Shiro、Jwt和Redis

    2. 配置Shiro:编写Shiro的配置类,包括 Realm 的实现,用于身份验证和授权;配置过滤器链,定义哪些URL需要拦截并进行权限校验。 3. 实现Jwt:创建Token生成和验证服务,设置密钥,处理JWT的签发和解析。 4. 集成...

    springboot+shiro.zip

    SpringBoot和Shiro是两个非常重要的Java开发框架,它们在构建高效、简洁的Web应用程序时起着关键作用。SpringBoot简化了Spring应用的初始搭建以及开发过程,而Apache Shiro则是一个强大且易用的Java安全框架,负责...

    springboot-shiro权限管理系统.zip

    《SpringBoot-Shiro权限管理系统详解》 在现代的Web开发中,权限管理和用户认证是不可或缺的重要环节。SpringBoot作为一款轻量级的Java框架,以其快速开发、易于配置的特性深受开发者喜爱。Shiro则是Apache组织提供...

    springboot_shiro.zip

    《SpringBoot整合Shiro实战详解》 在当前的IT领域,SpringBoot因其简洁的配置、快速的开发效率以及丰富的生态支持,已经成为企业级应用开发的首选框架。而Apache Shiro作为一款强大的安全管理框架,用于处理身份...

    基于springboot+shiro+jwt+vue+redis的后台管理系统.zip

    基于springboot+shiro+jwt+vue+redis的后台管理系统 基于springboot+shiro+jwt+vue+redis的后台管理系统 基于springboot+shiro+jwt+vue+redis的后台管理系统 基于springboot+shiro+jwt+vue+redis的后台管理系统 基于...

    springboot +shiro+redis实现session共享(方案二)1

    "Spring Boot + Shiro + Redis 实现 Session 共享方案二" 1. 概述 本文档旨在介绍如何使用 Spring Boot、Shiro 和 Redis 实现分布式 session 共享,以解决 Web 应用程序的登录 session 统一问题。 2. 相关依赖 ...

Global site tag (gtag.js) - Google Analytics