欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

springboot:shiro

程序员文章站 2022-06-16 08:04:04
...

第一次学习系统学习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