[color=red]
第一次学习系统学习shiro 并将shiro集成到springboot中
参考了很多同学的文章 这里表示非常感谢
demo东拼西凑 基本想实现的东西都凑齐了 实现了
[/color]
===========================================
本demo是基于前后端分离的形式写的demo 基于api 没有页面 请不要思考页面在哪
本demo主要包括:
[color=red]
1.基于filter md5 加盐登陆(用户名密码写死 自己修改测试)
2.用户角色roles控制 权限perms控制
3.基于redis的session分布式 基于redis的cache分布式(redis亲测 没有问题)
注:含有ehcache 但整体基于redis 所以将网上所有使用ehcache的demo改成redis形式
4.验证码(写死的验证码 自己修改测试)
5.登陆尝试次数限制(自己修改时间测试 注意expiretime单位是秒)
6.session管理(外加一个kitout 未成功 仅做参考)
7.用户修改密码 修改权限 切换用户(非切换角色)
[/color]
===========================================
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();
}
}
}
}
第一次学习系统学习shiro 并将shiro集成到springboot中
参考了很多同学的文章 这里表示非常感谢
demo东拼西凑 基本想实现的东西都凑齐了 实现了
[/color]
===========================================
本demo是基于前后端分离的形式写的demo 基于api 没有页面 请不要思考页面在哪
本demo主要包括:
[color=red]
1.基于filter md5 加盐登陆(用户名密码写死 自己修改测试)
2.用户角色roles控制 权限perms控制
3.基于redis的session分布式 基于redis的cache分布式(redis亲测 没有问题)
注:含有ehcache 但整体基于redis 所以将网上所有使用ehcache的demo改成redis形式
4.验证码(写死的验证码 自己修改测试)
5.登陆尝试次数限制(自己修改时间测试 注意expiretime单位是秒)
6.session管理(外加一个kitout 未成功 仅做参考)
7.用户修改密码 修改权限 切换用户(非切换角色)
[/color]
===========================================
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();
}
}
}
}