SpringBoot整合Shiro(五)
整合redis作为缓存 本来的整合过程是顺着博客的顺序来的,越往下,集成的越多,由于之前是使用ehcache缓存,现在改为redis,限制登录人数 以及 限制登录次数等 都需要改动,本篇为了简单,目前先将这两个功能下线,配置暂时是注销的,原类保存,在下篇博客中改。 还有之前是使用SessionListener监听session创建来统计在线人数,在本篇中也将改为统计redis中的key数目。 如果是单机,使用ehcache是最快的,项目一般都不是单节点,为了方便之后使用sso单点登录,以及多节点部署,所以使用shiro整合redis。
整合Redis
参考整合Redis
删除Ehcache POM文件修改
去掉shiro-ehcache依赖
删除Ehcache配置
删除ehcache-shiro.xml
创建代理类
因为通过@Bean的方式初始化一些组件会产生业务Bean的污染,需要进行采用代理的方式屏蔽依赖污染。
ApplicationUtils
Application相关工具类,使用getBean的方式来获取实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class ApplicationUtils { private static ApplicationContext appContext = null ; public static void setApplicationContext (ApplicationContext applicationContext) throws BeansException { appContext = applicationContext; } public static Object getBean (String beanId) { Object bean = null ; if (null != appContext) { bean = appContext.getBean(beanId); } return bean; } public static <T> T getBean (Class clazz) { Object bean = null ; if (null != appContext) { bean = appContext.getBean(clazz); } return (T) bean; } public static UserMapper getUserMapper () { return ApplicationUtils.getBean(UserMapper.class); } public static RoleMapper getRoleMapper () { return ApplicationUtils.getBean(RoleMapper.class); } public static PermissionMapper getPermissionMapper () { return ApplicationUtils.getBean(PermissionMapper.class); } public static RedisManagerImpl getRedisManager () { return ApplicationUtils.getBean(RedisManagerImpl.class); } }
ApplicationContexConfig
实现applicationContext的启动注册
1 2 3 4 5 6 7 @Component public class ApplicationContexConfig implements ApplicationContextAware { @Override public void setApplicationContext (ApplicationContext applicationContext) throws BeansException { ApplicationUtils.setApplicationContext(applicationContext); } }
UserServiceAgent
用户操作的代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class UserServiceAgent implements UserService { @Override public User findByUserName (String userName) { return ApplicationUtils.getUserMapper().findByUserName(userName); } @Override public int insert (User user) { return ApplicationUtils.getUserMapper().insert(user); } @Override public int update (User user) { return ApplicationUtils.getUserMapper().update(user); } @Override public int del (String username) { return ApplicationUtils.getUserMapper().del(username); } }
RoleServiceAgent
角色代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class RoleServiceAgent implements RoleService { @Override public Set<Role> findRolesByUserId (Integer uid) { return ApplicationUtils.getRoleMapper().findRolesByUserId(uid); } @Override public void delPermission (int roleId, int permissionId) { ApplicationUtils.getRoleMapper().delPermission(roleId, permissionId); } @Override public void addPermission (int roleId, int permissionId) { ApplicationUtils.getRoleMapper().addPermission(roleId, permissionId); } }
PermissionServiceAgent
权限代理类
1 2 3 4 5 6 7 public class PermissionServiceAgent implements PermissionService { @Override public Set<Permission> findPermissionsByRoleId (Set<Role> roles) { return ApplicationUtils.getPermissionMapper().findPermissionsByRoleId(roles); } }
RedisManagerAgent
为了解决初始化bean 需要依赖redis 造成的 bean过早被初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 public class RedisManagerAgent implements RedisManager { public void expire (String key, long time) { ApplicationUtils.getRedisManager().expire(key, time); } public Boolean hasKey (String key) { return ApplicationUtils.getRedisManager().hasKey(key); } @SuppressWarnings("unchecked") public void del (String... key) { ApplicationUtils.getRedisManager().del(key); } public void del (Collection keys) { ApplicationUtils.getRedisManager().del(keys); } public Object get (String key) { return ApplicationUtils.getRedisManager().get(key); } public void set (String key, Object value) { ApplicationUtils.getRedisManager().set(key, value); } public void set (String key, Object value, long time) { ApplicationUtils.getRedisManager().set(key, value, time); } public Set<String> scan (String key) { return ApplicationUtils.getRedisManager().scan(key); } public Long scanSize (String key) { return ApplicationUtils.getRedisManager().scanSize(key); } }
相关依赖类 ShiroSession
由于SimpleSession lastAccessTime更改后也会调用SessionDao update方法,更新的字段只有LastAccessTime(最后一次访问时间),由于会话失效是由Redis数据过期实现的,这个字段意义不大,为了减少对Redis的访问,降低网络压力,实现自己的Session,在SimpleSession上套一层,增加一个标识位,如果Session除lastAccessTime意外其它字段修改,就标识一下,只有标识为修改的才可以通过doUpdate访问Redis,否则直接返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 public class ShiroSession extends SimpleSession implements Serializable { private boolean isChanged = false ; public ShiroSession () { super (); this .setChanged(true ); } public ShiroSession (String host) { super (host); this .setChanged(true ); } @Override public void setId (Serializable id) { super .setId(id); this .setChanged(true ); } @Override public void setStopTimestamp (Date stopTimestamp) { super .setStopTimestamp(stopTimestamp); this .setChanged(true ); } @Override public void setExpired (boolean expired) { super .setExpired(expired); this .setChanged(true ); } @Override public void setTimeout (long timeout) { super .setTimeout(timeout); this .setChanged(true ); } @Override public void setHost (String host) { super .setHost(host); this .setChanged(true ); } @Override public void setAttributes (Map<Object, Object> attributes) { super .setAttributes(attributes); this .setChanged(true ); } @Override public void setAttribute (Object key, Object value) { super .setAttribute(key, value); this .setChanged(true ); } @Override public Object removeAttribute (Object key) { this .setChanged(true ); return super .removeAttribute(key); } @Override public void stop () { super .stop(); this .setChanged(true ); } @Override protected void expire () { this .stop(); this .setExpired(true ); } public boolean isChanged () { return isChanged; } public void setChanged (boolean isChanged) { this .isChanged = isChanged; } @Override public boolean equals (Object obj) { return super .equals(obj); } @Override protected boolean onEquals (SimpleSession ss) { return super .onEquals(ss); } @Override public int hashCode () { return super .hashCode(); } @Override public String toString () { return super .toString(); } }
SessionInMemory 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class SessionInMemory { private Session session; private Date createTime; public Session getSession () { return session; } public void setSession (Session session) { this .session = session; } public Date getCreateTime () { return createTime; } public void setCreateTime (Date createTime) { this .createTime = createTime; } }
ShiroSessionFactory 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class ShiroSessionFactory implements SessionFactory { private static final Logger logger = LoggerFactory.getLogger(ShiroSessionFactory.class); @Override public Session createSession (SessionContext initData) { ShiroSession session = new ShiroSession (); HttpServletRequest request = (HttpServletRequest)initData.get(DefaultWebSessionContext.class.getName() + ".SERVLET_REQUEST" ); session.setHost(getIpAddress(request)); return session; } public static String getIpAddress (HttpServletRequest request) { String localIP = "127.0.0.1" ; String ip = request.getHeader("x-forwarded-for" ); if (StringUtils.isBlank(ip) || (ip.equalsIgnoreCase(localIP)) || "unknown" .equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP" ); } if (StringUtils.isBlank(ip) || (ip.equalsIgnoreCase(localIP)) || "unknown" .equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP" ); } if (StringUtils.isBlank(ip) || (ip.equalsIgnoreCase(localIP)) || "unknown" .equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } }
ShiroSessionListener
创建Session监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class ShiroSessionListener implements SessionListener { private final AtomicInteger sessionCount = new AtomicInteger (0 ); @Override public void onStart (Session session) { sessionCount.incrementAndGet(); } @Override public void onStop (Session session) { sessionCount.decrementAndGet(); } @Override public void onExpiration (Session session) { sessionCount.decrementAndGet(); } public int getSessionCount () { return sessionCount.get(); } }
重写密码比较器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public class RetryLimitHashedCredentialsMatcher extends SimpleCredentialsMatcher { private static final Logger logger = LoggerFactory.getLogger(RetryLimitHashedCredentialsMatcher.class); public static final String DEFAULT_RETRYLIMIT_CACHE_KEY_PREFIX = "shiro:cache:retrylimit:" ; private String keyPrefix = DEFAULT_RETRYLIMIT_CACHE_KEY_PREFIX; private UserServiceAgent userServiceAgent = new UserServiceAgent (); private RedisManager redisManager; public void setRedisManager (RedisManager redisManager) { this .redisManager = redisManager; } private String getRedisKickoutKey (String username) { return this .keyPrefix + username; } @Override public boolean doCredentialsMatch (AuthenticationToken token, AuthenticationInfo info) { String username = (String) token.getPrincipal(); AtomicInteger retryCount = (AtomicInteger) redisManager.get(getRedisKickoutKey(username)); if (retryCount == null ) { retryCount = new AtomicInteger (0 ); } if (retryCount.incrementAndGet() > 5 ) { User user = userServiceAgent.findByUserName(username); if (user != null && "0" .equals(user.getState())) { user.setState("1" ); userServiceAgent.update(user); } logger.info("锁定用户" + user.getUsername()); throw new LockedAccountException (); } boolean matches = super .doCredentialsMatch(token, info); if (matches) { redisManager.del(getRedisKickoutKey(username)); } { redisManager.set(getRedisKickoutKey(username), retryCount); } return matches; } public void unlockAccount (String username) { User user = userServiceAgent.findByUserName(username); if (user != null ) { user.setState("0" ); userServiceAgent.update(user); redisManager.del(getRedisKickoutKey(username)); } } }
SerializeUtils 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public class SerializeUtils implements RedisSerializer { private static Logger logger = LoggerFactory.getLogger(SerializeUtils.class); public static boolean isEmpty (byte [] data) { return (data == null || data.length == 0 ); } @Override public byte [] serialize(Object object) throws SerializationException { byte [] result = null ; if (object == null ) { return new byte [0 ]; } try ( ByteArrayOutputStream byteStream = new ByteArrayOutputStream (128 ); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteStream) ) { if (!(object instanceof Serializable)) { throw new IllegalArgumentException (SerializeUtils.class.getSimpleName() + " requires a Serializable payload " + "but received an object of type [" + object.getClass().getName() + "]" ); } objectOutputStream.writeObject(object); objectOutputStream.flush(); result = byteStream.toByteArray(); } catch (Exception ex) { logger.error("Failed to serialize" , ex); } return result; } @Override public Object deserialize (byte [] bytes) throws SerializationException { Object result = null ; if (isEmpty(bytes)) { return null ; } try ( ByteArrayInputStream byteStream = new ByteArrayInputStream (bytes); ObjectInputStream objectInputStream = new ObjectInputStream (byteStream) ) { result = objectInputStream.readObject(); } catch (Exception e) { logger.error("Failed to deserialize" , e); } return result; } }
重写并发登录控制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 public class KickoutSessionControlFilter extends AccessControlFilter { private String kickoutUrl; private boolean kickoutAfter = false ; private int maxSession = 1 ; private SessionManager sessionManager; private RedisManager redisManager; public static final String DEFAULT_KICKOUT_CACHE_KEY_PREFIX = "shiro:cache:kickout:" ; private String keyPrefix = DEFAULT_KICKOUT_CACHE_KEY_PREFIX; public void setKickoutUrl (String kickoutUrl) { this .kickoutUrl = kickoutUrl; } public void setKickoutAfter (boolean kickoutAfter) { this .kickoutAfter = kickoutAfter; } public void setMaxSession (int maxSession) { this .maxSession = maxSession; } public void setSessionManager (SessionManager sessionManager) { this .sessionManager = sessionManager; } public void setRedisManager (RedisManager redisManager) { this .redisManager = redisManager; } public String getKeyPrefix () { return keyPrefix; } public void setKeyPrefix (String keyPrefix) { this .keyPrefix = keyPrefix; } private String getRedisKickoutKey (String username) { return this .keyPrefix + username; } @Override protected boolean isAccessAllowed (ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return false ; } @Override protected boolean onAccessDenied (ServletRequest request, ServletResponse response) throws Exception { Subject subject = getSubject(request, response); if (!subject.isAuthenticated() && !subject.isRemembered()) { return true ; } HttpServletRequest httpServletRequest = (HttpServletRequest) request; String path = httpServletRequest.getServletPath(); if (isStaticFile(path)) { return true ; } Session session = subject.getSession(); String username = ((User) subject.getPrincipal()).getUsername(); Serializable sessionId = session.getId(); Deque<Serializable> deque = (Deque<Serializable>) redisManager.get(getRedisKickoutKey(username)); if (deque == null || deque.size() == 0 ) { deque = new LinkedList <Serializable>(); } if (!deque.contains(sessionId) && session.getAttribute("kickout" ) == null ) { deque.push(sessionId); } while (deque.size() > maxSession) { Serializable kickoutSessionId = null ; if (kickoutAfter) { kickoutSessionId = deque.getFirst(); kickoutSessionId = deque.removeFirst(); } else { kickoutSessionId = deque.removeLast(); } try { Session kickoutSession = sessionManager.getSession(new DefaultSessionKey (kickoutSessionId)); if (kickoutSession != null ) { kickoutSession.setAttribute("kickout" , true ); } } catch (Exception e) { e.printStackTrace(); } } redisManager.set(getRedisKickoutKey(username), deque); if (session.getAttribute("kickout" ) != null ) { try { subject.logout(); } catch (Exception e) { } WebUtils.issueRedirect(request, response, kickoutUrl); return false ; } return true ; } private boolean isStaticFile (String path) { return true ; } }
配置Redis管理器 Redis 配置类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Configuration public class RedisConfiguration extends CachingConfigurerSupport { @Bean public RedisTemplate<String, Object> redisTemplate (RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate <>(); SerializeUtils serializeUtils = new SerializeUtils (); template.setValueSerializer(serializeUtils); template.setHashValueSerializer(serializeUtils); template.setKeySerializer(new StringRedisSerializer ()); template.setHashKeySerializer(new StringRedisSerializer ()); template.setConnectionFactory(factory); return template; } }
创建RedisManager接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 public interface RedisManager { public void expire (String key, long time) ; public Boolean hasKey (String key) ; public void del (String... key) ; public void del (Collection keys) ; public Object get (String key) ; public void set (String key, Object value) ; public void set (String key, Object value, long time) ; public Set<String> scan (String key) ; public Long scanSize (String key) ; }
创建RedisManagerImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 @Component("redisManager") public class RedisManagerImpl implements RedisManager { @Autowired private RedisTemplate<String, Object> redisTemplate; public void expire (String key, long time) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } public Boolean hasKey (String key) { return redisTemplate.hasKey(key); } @SuppressWarnings("unchecked") public void del (String... key) { if (key != null && key.length > 0 ) { if (key.length == 1 ) { redisTemplate.delete(key[0 ]); } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } public void del (Collection keys) { redisTemplate.delete(keys); } public Object get (String key) { return redisTemplate.opsForValue().get(key); } public void set (String key, Object value) { redisTemplate.opsForValue().set(key, value); } public void set (String key, Object value, long time) { if (time > 0 ) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } } public Set<String> scan (String key) { Set<String> execute = this .redisTemplate.execute(new RedisCallback <Set<String>>() { @Override public Set<String> doInRedis (RedisConnection connection) throws DataAccessException { Set<String> binaryKeys = new HashSet <>(); Cursor<byte []> cursor = connection.scan(new ScanOptions .ScanOptionsBuilder().match(key).count(1000 ).build()); while (cursor.hasNext()) { binaryKeys.add(new String (cursor.next())); } return binaryKeys; } }); return execute; } public Long scanSize (String key) { long dbSize = this .redisTemplate.execute(new RedisCallback <Long>() { @Override public Long doInRedis (RedisConnection connection) throws DataAccessException { long count = 0L ; Cursor<byte []> cursor = connection.scan(ScanOptions.scanOptions().match(key).count(1000 ).build()); while (cursor.hasNext()) { cursor.next(); count++; } return count; } }); return dbSize; } }
创建创建Redis缓存管理器 创建RedisCache 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 public class RedisCache <K, V> implements Cache <K, V> { private static Logger logger = LoggerFactory.getLogger(RedisCache.class); private RedisManager redisManager; private String keyPrefix = "" ; private int expire = 0 ; private String principalIdFieldName = RedisCacheManager.DEFAULT_PRINCIPAL_ID_FIELD_NAME; public RedisCache (RedisManager redisManager, String prefix, int expire, String principalIdFieldName) { if (redisManager == null ) { throw new IllegalArgumentException ("redisManager cannot be null." ); } this .redisManager = redisManager; if (prefix != null && !"" .equals(prefix)) { this .keyPrefix = prefix; } if (expire != -1 ) { this .expire = expire; } if (principalIdFieldName != null && !"" .equals(principalIdFieldName)) { this .principalIdFieldName = principalIdFieldName; } } @Override public V get (K key) throws CacheException { logger.debug("get key [{}]" , key); if (key == null ) { return null ; } try { String redisCacheKey = getRedisCacheKey(key); Object rawValue = redisManager.get(redisCacheKey); if (rawValue == null ) { return null ; } V value = (V) rawValue; return value; } catch (Exception e) { throw new CacheException (e); } } @Override public V put (K key, V value) throws CacheException { logger.debug("put key [{}]" , key); if (key == null ) { logger.warn("Saving a null key is meaningless, return value directly without call Redis." ); return value; } try { String redisCacheKey = getRedisCacheKey(key); redisManager.set(redisCacheKey, value != null ? value : null , expire); return value; } catch (Exception e) { throw new CacheException (e); } } @Override public V remove (K key) throws CacheException { logger.debug("remove key [{}]" , key); if (key == null ) { return null ; } try { String redisCacheKey = getRedisCacheKey(key); Object rawValue = redisManager.get(redisCacheKey); V previous = (V) rawValue; redisManager.del(redisCacheKey); return previous; } catch (Exception e) { throw new CacheException (e); } } private String getRedisCacheKey (K key) { if (key == null ) { return null ; } return this .keyPrefix + getStringRedisKey(key); } private String getStringRedisKey (K key) { String redisKey; if (key instanceof PrincipalCollection) { redisKey = getRedisKeyFromPrincipalIdField((PrincipalCollection) key); } else { redisKey = key.toString(); } return redisKey; } private String getRedisKeyFromPrincipalIdField (PrincipalCollection key) { String redisKey; Object principalObject = key.getPrimaryPrincipal(); Method pincipalIdGetter = null ; Method[] methods = principalObject.getClass().getDeclaredMethods(); for (Method m : methods) { if (RedisCacheManager.DEFAULT_PRINCIPAL_ID_FIELD_NAME.equals(this .principalIdFieldName) && ("getAuthCacheKey" .equals(m.getName()) || "getId" .equals(m.getName()))) { pincipalIdGetter = m; break ; } if (m.getName().equals("get" + this .principalIdFieldName.substring(0 , 1 ).toUpperCase() + this .principalIdFieldName.substring(1 ))) { pincipalIdGetter = m; break ; } } if (pincipalIdGetter == null ) { throw new PrincipalInstanceException (principalObject.getClass(), this .principalIdFieldName); } try { Object idObj = pincipalIdGetter.invoke(principalObject); if (idObj == null ) { throw new PrincipalIdNullException (principalObject.getClass(), this .principalIdFieldName); } redisKey = idObj.toString(); } catch (IllegalAccessException e) { throw new PrincipalInstanceException (principalObject.getClass(), this .principalIdFieldName, e); } catch (InvocationTargetException e) { throw new PrincipalInstanceException (principalObject.getClass(), this .principalIdFieldName, e); } return redisKey; } @Override public void clear () throws CacheException { logger.debug("clear cache" ); Set<String> keys = null ; try { keys = redisManager.scan(this .keyPrefix + "*" ); } catch (Exception e) { logger.error("get keys error" , e); } if (keys == null || keys.size() == 0 ) { return ; } for (String key : keys) { redisManager.del(key); } } @Override public int size () { Long longSize = 0L ; try { longSize = new Long (redisManager.scanSize(this .keyPrefix + "*" )); } catch (Exception e) { logger.error("get keys error" , e); } return longSize.intValue(); } @SuppressWarnings("unchecked") @Override public Set<K> keys () { Set<String> keys = null ; try { keys = redisManager.scan(this .keyPrefix + "*" ); } catch (Exception e) { logger.error("get keys error" , e); return Collections.emptySet(); } if (CollectionUtils.isEmpty(keys)) { return Collections.emptySet(); } Set<K> convertedKeys = new HashSet <K>(); for (String key : keys) { try { convertedKeys.add((K) key); } catch (Exception e) { logger.error("deserialize keys error" , e); } } return convertedKeys; } @Override public Collection<V> values () { Set<String> keys = null ; try { keys = redisManager.scan(this .keyPrefix + "*" ); } catch (Exception e) { logger.error("get values error" , e); return Collections.emptySet(); } if (CollectionUtils.isEmpty(keys)) { return Collections.emptySet(); } List<V> values = new ArrayList <V>(keys.size()); for (String key : keys) { V value = null ; try { value = (V) redisManager.get(key); } catch (Exception e) { logger.error("deserialize values= error" , e); } if (value != null ) { values.add(value); } } return Collections.unmodifiableList(values); } public String getKeyPrefix () { return keyPrefix; } public void setKeyPrefix (String keyPrefix) { this .keyPrefix = keyPrefix; } public String getPrincipalIdFieldName () { return principalIdFieldName; } public void setPrincipalIdFieldName (String principalIdFieldName) { this .principalIdFieldName = principalIdFieldName; } }
getRedisKeyFromPrincipalIdField()是获取缓存的用户身份信息 和用户权限信息。 里面有一个属性principalIdFieldName 在RedisCacheManager也有这个属性,设置其中一个就可以.是为了给缓存用户身份和权限信息在Redis中的key唯一,登录用户名可能是username 或者 phoneNum 或者是Email中的一个,如 我的User实体类中 有一个 usernane字段,也是登录时候使用的用户名,在redis中缓存的权限信息key 如下, 这个admin 就是 通过getUsername获得的。
创建RedisCacheManager 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 public class RedisCacheManager implements CacheManager { private final Logger logger = LoggerFactory.getLogger(RedisCacheManager.class); private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap <String, Cache>(); private RedisManager redisManager; private static final int DEFAULT_EXPIRE = 1800 ; private int expire = DEFAULT_EXPIRE; public static final String DEFAULT_CACHE_KEY_PREFIX = "shiro:cache:" ; private String keyPrefix = DEFAULT_CACHE_KEY_PREFIX; public static final String DEFAULT_PRINCIPAL_ID_FIELD_NAME = "authCacheKey or id" ; private String principalIdFieldName = DEFAULT_PRINCIPAL_ID_FIELD_NAME; @Override public <K, V> Cache<K, V> getCache (String name) throws CacheException { logger.debug("get cache, name={}" , name); Cache cache = caches.get(name); if (cache == null ) { cache = new RedisCache <K, V>(redisManager, keyPrefix + name + ":" , expire, principalIdFieldName); caches.put(name, cache); } return cache; } public RedisManager getRedisManager () { return redisManager; } public void setRedisManager (RedisManager redisManager) { this .redisManager = redisManager; } public String getKeyPrefix () { return keyPrefix; } public void setKeyPrefix (String keyPrefix) { this .keyPrefix = keyPrefix; } public int getExpire () { return expire; } public void setExpire (int expire) { this .expire = expire; } public String getPrincipalIdFieldName () { return principalIdFieldName; } public void setPrincipalIdFieldName (String principalIdFieldName) { this .principalIdFieldName = principalIdFieldName; } }
创建RedisSessionDAO 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 public class RedisSessionDAO extends AbstractSessionDAO { private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class); private static final String DEFAULT_SESSION_KEY_PREFIX = "shiro:session:" ; private String keyPrefix = DEFAULT_SESSION_KEY_PREFIX; private static final long DEFAULT_SESSION_IN_MEMORY_TIMEOUT = 1000L ; private long sessionInMemoryTimeout = DEFAULT_SESSION_IN_MEMORY_TIMEOUT; private static final int DEFAULT_EXPIRE = -2 ; private static final int NO_EXPIRE = -1 ; private int expire = DEFAULT_EXPIRE; private static final int MILLISECONDS_IN_A_SECOND = 1000 ; private RedisManager redisManager; private static ThreadLocal sessionsInThread = new ThreadLocal (); @Override public void update (Session session) throws UnknownSessionException { try { if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) { return ; } if (session instanceof ShiroSession) { ShiroSession ss = (ShiroSession) session; if (!ss.isChanged()) { return ; } ss.setChanged(false ); } this .saveSession(session); } catch (Exception e) { logger.warn("update Session is failed" , e); } } private void saveSession (Session session) throws UnknownSessionException { if (session == null || session.getId() == null ) { logger.error("session or session id is null" ); throw new UnknownSessionException ("session or session id is null" ); } String key = getRedisSessionKey(session.getId()); if (expire == DEFAULT_EXPIRE) { this .redisManager.set(key, session, (int ) (session.getTimeout() / MILLISECONDS_IN_A_SECOND)); return ; } if (expire != NO_EXPIRE && expire * MILLISECONDS_IN_A_SECOND < session.getTimeout()) { logger.warn("Redis session expire time: " + (expire * MILLISECONDS_IN_A_SECOND) + " is less than Session timeout: " + session.getTimeout() + " . It may cause some problems." ); } this .redisManager.set(key, session, expire); } @Override public void delete (Session session) { if (session == null || session.getId() == null ) { logger.error("session or session id is null" ); return ; } try { redisManager.del(getRedisSessionKey(session.getId())); } catch (Exception e) { logger.error("delete session error. session id= {}" ,session.getId()); } } @Override public Collection<Session> getActiveSessions () { Set<Session> sessions = new HashSet <Session>(); try { Set<String> keys = redisManager.scan(this .keyPrefix + "*" ); if (keys != null && keys.size() > 0 ) { for (String key:keys) { Session s = (Session) redisManager.get(key); sessions.add(s); } } } catch (Exception e) { logger.error("get active sessions error." ); } return sessions; } public Long getActiveSessionsSize () { Long size = 0L ; try { size = redisManager.scanSize(this .keyPrefix + "*" ); } catch (Exception e) { logger.error("get active sessions error." ); } return size; } @Override protected Serializable doCreate (Session session) { if (session == null ) { logger.error("session is null" ); throw new UnknownSessionException ("session is null" ); } Serializable sessionId = this .generateSessionId(session); this .assignSessionId(session, sessionId); this .saveSession(session); return sessionId; } @Override protected Session doReadSession (Serializable sessionId) { if (sessionId == null ) { logger.warn("session id is null" ); return null ; } Session s = getSessionFromThreadLocal(sessionId); if (s != null ) { return s; } logger.debug("read session from redis" ); try { s = (Session) redisManager.get(getRedisSessionKey(sessionId)); setSessionToThreadLocal(sessionId, s); } catch (Exception e) { logger.error("read session error. settionId= {}" ,sessionId); } return s; } private void setSessionToThreadLocal (Serializable sessionId, Session s) { Map<Serializable, SessionInMemory> sessionMap = (Map<Serializable, SessionInMemory>) sessionsInThread.get(); if (sessionMap == null ) { sessionMap = new HashMap <Serializable, SessionInMemory>(); sessionsInThread.set(sessionMap); } SessionInMemory sessionInMemory = new SessionInMemory (); sessionInMemory.setCreateTime(new Date ()); sessionInMemory.setSession(s); sessionMap.put(sessionId, sessionInMemory); } private Session getSessionFromThreadLocal (Serializable sessionId) { Session s = null ; if (sessionsInThread.get() == null ) { return null ; } Map<Serializable, SessionInMemory> sessionMap = (Map<Serializable, SessionInMemory>) sessionsInThread.get(); SessionInMemory sessionInMemory = sessionMap.get(sessionId); if (sessionInMemory == null ) { return null ; } Date now = new Date (); long duration = now.getTime() - sessionInMemory.getCreateTime().getTime(); if (duration < sessionInMemoryTimeout) { s = sessionInMemory.getSession(); logger.debug("read session from memory" ); } else { sessionMap.remove(sessionId); } return s; } private String getRedisSessionKey (Serializable sessionId) { return this .keyPrefix + sessionId; } public RedisManager getRedisManager () { return redisManager; } public void setRedisManager (RedisManager redisManager) { this .redisManager = redisManager; } public String getKeyPrefix () { return keyPrefix; } public void setKeyPrefix (String keyPrefix) { this .keyPrefix = keyPrefix; } public long getSessionInMemoryTimeout () { return sessionInMemoryTimeout; } public void setSessionInMemoryTimeout (long sessionInMemoryTimeout) { this .sessionInMemoryTimeout = sessionInMemoryTimeout; } public int getExpire () { return expire; } public void setExpire (int expire) { this .expire = expire; } }
自定义异常类 PrincipalInstanceException 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class PrincipalInstanceException extends RuntimeException { private static final String MESSAGE = "We need a field to identify this Cache Object in Redis. " + "So you need to defined an id field which you can get unique id to identify this principal. " + "For example, if you use UserInfo as Principal class, the id field maybe userId, userName, email, etc. " + "For example, getUserId(), getUserName(), getEmail(), etc.\n" + "Default value is authCacheKey or id, that means your principal object has a method called \"getAuthCacheKey()\" or \"getId()\"" ; public PrincipalInstanceException (Class clazz, String idMethodName) { super (clazz + " must has getter for field: " + idMethodName + "\n" + MESSAGE); } public PrincipalInstanceException (Class clazz, String idMethodName, Exception e) { super (clazz + " must has getter for field: " + idMethodName + "\n" + MESSAGE, e); } }
PrincipalIdNullException 1 2 3 4 5 6 7 8 public class PrincipalIdNullException extends RuntimeException { private static final String MESSAGE = "Principal Id shouldn't be null!" ; public PrincipalIdNullException (Class clazz, String idMethodName) { super (clazz + " id field: " + idMethodName + ", value is null\n" + MESSAGE); } }
重写ShiroRealm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 public class ShiroRealm extends AuthorizingRealm { private UserServiceAgent userServiceAgent = new UserServiceAgent (); private RoleServiceAgent roleServiceAgent = new RoleServiceAgent (); private PermissionServiceAgent permissionServiceAgent = new PermissionServiceAgent (); @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken; String username = usernamePasswordToken.getUsername(); String password = new String (usernamePasswordToken.getPassword()); User user = userServiceAgent.findByUserName(username); if (user == null ) { throw new UnknownAccountException ("用户名或密码错误!" ); } if ("1" .equals(user.getState())) { throw new LockedAccountException ("账号已被锁定,请联系管理员!" ); } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo (user, user.getPassword(), getName()); return info; } @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { User user = (User) SecurityUtils.getSubject().getPrincipal(); Set<Role> roles = roleServiceAgent.findRolesByUserId(user.getUid()); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo (); for (Role role : roles) { authorizationInfo.addRole(role.getRole()); } Set<Permission> permissions = permissionServiceAgent.findPermissionsByRoleId(roles); for (Permission permission : permissions) { authorizationInfo.addStringPermission(permission.getPermission()); } return authorizationInfo; } @Override public void clearCachedAuthorizationInfo (PrincipalCollection principals) { super .clearCachedAuthorizationInfo(principals); } @Override public void clearCachedAuthenticationInfo (PrincipalCollection principals) { super .clearCachedAuthenticationInfo(principals); } @Override public void clearCache (PrincipalCollection principals) { super .clearCache(principals); } public void clearAllCachedAuthorizationInfo () { getAuthorizationCache().clear(); } public void clearAllCachedAuthenticationInfo () { getAuthenticationCache().clear(); } public void clearAllCache () { clearAllCachedAuthenticationInfo(); clearAllCachedAuthorizationInfo(); } }
ShiroConfig 配置 配置redisManagerAgent 1 2 3 4 @Bean("redisManagerAgent") public RedisManager redisManager () { return new RedisManagerAgent (); }
配置缓存管理器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Bean public RedisCacheManager cacheManager () { RedisCacheManager redisCacheManager = new RedisCacheManager (); redisCacheManager.setRedisManager(redisManager()); redisCacheManager.setPrincipalIdFieldName("username" ); redisCacheManager.setExpire(200000 ); return redisCacheManager; }
配置密码比较器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Bean("credentialsMatcher") public RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher () { RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher = new RetryLimitHashedCredentialsMatcher (); retryLimitHashedCredentialsMatcher.setRedisManager(redisManager()); return retryLimitHashedCredentialsMatcher; }
并发登录控制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Bean public KickoutSessionControlFilter kickoutSessionControlFilter () { KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter (); kickoutSessionControlFilter.setSessionManager(sessionManager()); kickoutSessionControlFilter.setRedisManager(redisManager()); kickoutSessionControlFilter.setKickoutAfter(false ); kickoutSessionControlFilter.setMaxSession(1 ); kickoutSessionControlFilter.setKickoutUrl("/login?kickout=1" ); return kickoutSessionControlFilter; }
配置sessionDAO 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Bean public SessionDAO sessionDAO () { RedisSessionDAO redisSessionDAO = new RedisSessionDAO (); redisSessionDAO.setRedisManager(redisManager()); redisSessionDAO.setExpire(12000 ); return redisSessionDAO; }
配置sessionFactory 1 2 3 4 5 @Bean("sessionFactory") public ShiroSessionFactory sessionFactory () { ShiroSessionFactory sessionFactory = new ShiroSessionFactory (); return sessionFactory; }
配置cacheManager 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Bean public RedisCacheManager cacheManager () { RedisCacheManager redisCacheManager = new RedisCacheManager (); redisCacheManager.setRedisManager(redisManager()); redisCacheManager.setPrincipalIdFieldName("username" ); redisCacheManager.setExpire(200000 ); return redisCacheManager; }
配置Session监听器 1 2 3 4 5 6 7 8 9 10 11 @Bean("sessionListener") public ShiroSessionListener sessionListener () { ShiroSessionListener sessionListener = new ShiroSessionListener (); return sessionListener; }
配置会话管理器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Bean("sessionManager") public SessionManager sessionManager () { ShiroSessionManager sessionManager = new ShiroSessionManager (); Collection<SessionListener> listeners = new ArrayList <SessionListener>(); listeners.add(sessionListener()); sessionManager.setSessionListeners(listeners); sessionManager.setSessionIdCookie(sessionIdCookie()); sessionManager.setSessionDAO(sessionDAO()); sessionManager.setCacheManager(cacheManager()); sessionManager.setSessionFactory(sessionFactory()); sessionManager.setGlobalSessionTimeout(1800000 ); sessionManager.setDeleteInvalidSessions(true ); sessionManager.setSessionValidationSchedulerEnabled(true ); sessionManager.setSessionValidationInterval(3600000 ); sessionManager.setSessionIdUrlRewritingEnabled(false ); return sessionManager; }
安全事务管理器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Bean(name = "securityManager") public SecurityManager securityManager () { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager (); securityManager.setRealm(shiroRealm()); securityManager.setRememberMeManager(rememberMeManager()); securityManager.setCacheManager(cacheManager()); securityManager.setSessionManager(sessionManager()); return securityManager; }