會話
會話,即使用者保持和服務端之間的聯系,保證使用者在下一次通路服務端時不必在送出使用者身份資訊。而服務端可以通過使用者送出的sessionId判斷使用者身份。
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession()
可以通過以上方法擷取目前登入用
/**
*Subject.getSession(true),即如果目前沒有建立session對象會建立一個;
*Subject.getSession(false),如果目前沒有建立session對象則傳回null。
*/
Subject.getSession();//等價于Subject.getSession(true)
session.getId();//擷取目前會話的唯一辨別。
session.getHost();//擷取目前會話的主機位址。
session.getTimeout() & session.setTimeout(毫秒);//設定/擷取目前Session的過期時間。
/**擷取會話的啟動時間及最後通路時間;
*如果是J2SE環境需要自己定期調用session.touch()去更新最後通路時間;
*如果是Web環境,每次進入ShiroFilter都會自動調用session.touch()來更新最後通路時間。
*/
session.getStartTimestamp() & session.getLastAccessTime();
/**
*更新會話最後通路時間以及銷毀會話;
*Subject.logout()會自動調用session.stop()。
*在Web應用中,調用HttpSession.invalidate()也會自動調用session.stop()來銷毀shiro的會話。
*/
session.touch() & session.stop();
session.setAttribute(key,val) & session.getAttribute(key) & session.removeAttribute(key);//設定/擷取/删除 會話屬性。
會話管理
shiro中的會話管理器管理所有Subject的會話的建立、删除、驗證、失效等。shiro中會話管理的結構圖如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2Lc1DNXlFcKhVZshmMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TOxIDMzEDM2EjMxcDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
發現SecurityManager繼承了SessionManager接口,而SecurityManager結構圖如下:
發現SessionSecurityManager實作了接口SecurityManager,實質SessionSecurityManager實作了把會話管理委托給對應的SessionManager。而DefaultSecurityManager和DefaultWebSecurityManager都繼承了SessionSecurityManager
會話監聽器
自定義類實作SessionListener接口,并實作其中的方法。SessionListener方法如下:
- onStart(Session):建立會話時觸發該事件
- onStop(Session):銷毀會話時觸發該事件
- onExpiration(Session):會話過期時觸發該事件
Session的CURD
shiro中SessionDao的結構圖
- AbstractSessionDAO 提供了 SessionDAO 的基礎實作,如生成會話 ID 等;
- CachingSessionDAO 提供了對開發者透明的會話緩存的功能,隻需要在SecurityManager設定相應的 CacheManager即可
- MemorySessionDAO 直接在記憶體中進行會話維護;
- EnterpriseCacheSessionDAO提供了緩存功能的會話維護,預設情況下使用 MapCache 實作,内部使用ConcurrentHashMap 儲存緩存的會話
以上是shiro中會話管理的基本介紹,接下來看看在shiro中如何自定義會話管理:
自定義sessionDao:
import com.heyu.framework.service.RedisService;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
public class RedisSessionDao extends EnterpriseCacheSessionDAO {
@Autowired
private RedisService<String,Object> redisService;
private static final String PREFIX = "shiro_session";
private static Long expire = 3600L;
@Override
protected void doUpdate(Session session) {
super.doUpdate(session);
String key = PREFIX + session.getId().toString();
if(!redisService.exists(key)){
redisService.set(key,session);
}
redisService.expire(key,expire,TimeUnit.SECONDS);
}
@Override
protected void doDelete(Session session) {
super.doDelete(session);
redisService.delete(PREFIX + session.getId());
}
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = super.doCreate(session);
redisService.set(PREFIX + sessionId.toString(),session);
return sessionId;
}
@Override
protected Session doReadSession(Serializable serializable) {
Session session = super.doReadSession(serializable);
if(session == null){
session = (Session) redisService.get(PREFIX + serializable.toString());
}
return session;
}
}
通過該類中的方法名便知道,什麼時候觸發該方法的調用
配置會話管理:
@Bean
public RedisSessionDao redisSessionDao(){
RedisSessionDao redisSessionDao = new RedisSessionDao();
return redisSessionDao;
}
@Bean
public SessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDao());
return sessionManager;
}
再将執行個體化SecurityManager中的SessionManager屬性即可:
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//内部方法調用realm的getAuthenticationInfo擷取認證資訊
securityManager.setRealm(authRealm());
securityManager.setCacheManager(redisCacheManager());
securityManager.setSessionManager(sessionManager());
return securityManager;
}
緩存管理
shiro中提供Cache的接口:
public interface Cache<K, V> {
//根據Key擷取緩存中的值
public V get(K key) throws CacheException;
//往緩存中放入key-value,傳回緩存中之前的值
public V put(K key, V value) throws CacheException;
//移除緩存中key對應的值,傳回該值
public V remove(K key) throws CacheException;
//清空整個緩存
public void clear() throws CacheException;
//傳回緩存大小
public int size();
//擷取緩存中所有的key
public Set<K> keys();
//擷取緩存中所有的value
public Collection<V> values();
}
提供的 CacheManager接口:
public interface CacheManager {
//根據緩存名字擷取一個Cache
public <K, V> Cache<K, V> getCache(String name) throws CacheException;
}
在shiro的内部元件SecurityManager中提供了CachingSecurityManager接口,内部元件Realm也有子接口CachingRealm。
shiro内部的緩存管理結構:
但是更多的是實作CacheManager接口,實作其中的getCache()方法,然後在建立Cache的實作類,getCche()方法傳回該實作類的執行個體
以下示範利用redis自定義緩存管理:
Cache實作類:
import com.heyu.framework.service.RedisService;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
import java.util.Set;
public class RedisCache<K,V> implements Cache<K,V> {
@Autowired
private RedisService redisService;
private String cacheKey;
private static Long expire = 3600L;
private static final String PREFIX = "SHIRO_CACHE";
public RedisCache(String name,RedisService redisService){
this.redisService = redisService;
this.cacheKey = PREFIX + name;
}
public K getCacheKey(Object k) {
return (K)(this.cacheKey+k);
}
public void setCacheKey(String cacheKey) {
this.cacheKey = cacheKey;
}
@Override
public V get(K k) throws CacheException {
return (V) redisService.get(getCacheKey(k));
}
@Override
public V put(K k, V v) throws CacheException {
redisService.set(getCacheKey(k),v,expire);
return v;
}
@Override
public V remove(K k) throws CacheException {
V v = (V) redisService.get(getCacheKey(k));
redisService.delete(getCacheKey(k));
return v;
}
@Override
public void clear() throws CacheException {
}
@Override
public int size() {
return 0;
}
@Override
public Set<K> keys() {
return null;
}
@Override
public Collection<V> values() {
return null;
}
}
redis操作類RedisSerivce:
import com.heyu.framework.exception.CommonException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Service
public class RedisService<K,V> {
private RedisTemplate<K,V> redisTemplate;
@Autowired
public void setRedisTemplate(RedisTemplate<K, V> redisTemplate) {
RedisSerializer redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
this.redisTemplate = redisTemplate;
}
/**
* 将資料存入緩存
* @param key
* @param value
* @return
*/
public boolean set(K key,V value){
boolean result = false;
try {
ValueOperations<K,V> operations = redisTemplate.opsForValue();
operations.set(key,value);
result = true;
}catch (Exception e){
e.printStackTrace();
throw new CommonException(e.getMessage());
}
return result;
}
/**
* 将資料存入緩存,并設定有效期,機關為秒
* @param key
* @param value
* @param expire
* @return
*/
public boolean set(K key,V value,Long expire){
boolean result = false;
try {
ValueOperations<K,V> operations = redisTemplate.opsForValue();
operations.set(key,value,expire,TimeUnit.SECONDS);
result = true;
}catch (Exception e){
e.printStackTrace();
throw new CommonException(e.getMessage());
}
return result;
}
/**
* 從緩存中擷取資料
* @param key
* @return
*/
public Object get(K key){
Object result = null;
ValueOperations<K,V> operations = redisTemplate.opsForValue();
result = operations.get(key);
return result;
}
/**
* 根據key從緩存中删除該key-value
* @param key
*/
public void delete(K key){
if(exists(key)){
redisTemplate.delete(key);
}
}
/**
* 批量删除
* @param pattern
*/
public void deletePattern(K pattern){
Set<K> keys = redisTemplate.keys(pattern);
if (keys != null && keys.size() > 0){
redisTemplate.delete(keys);
}
}
/**
* 判斷緩存中 是否存在該key
* @param key
* @return
*/
public boolean exists(K key){
return redisTemplate.hasKey(key);
}
public void expire(K key,Long expireTime,TimeUnit unit){
redisTemplate.expire(key,expireTime,unit);
}
}
CacheManager實作類:
import com.heyu.framework.service.RedisService;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 自定義shiro中的緩存管理器,使用redis緩存
*/
public class RedisCacheManager implements CacheManager {
@Autowired
private RedisService<String,Object> redisService;
@Override
public <K, V> Cache<K, V> getCache(String name) throws CacheException {
return new RedisCache<>(name,redisService);
}
public RedisService<String, Object> getRedisService() {
return redisService;
}
public void setRedisService(RedisService<String, Object> redisService) {
this.redisService = redisService;
}
}
以上就完成了shiro中緩存的自定義,接下來隻需執行個體化SecurityManager中的CacheManager屬性:
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//内部方法調用realm的getAuthenticationInfo擷取認證資訊
securityManager.setRealm(authRealm());
securityManager.setCacheManager(redisCacheManager());
securityManager.setSessionManager(sessionManager());
return securityManager;
}
@Bean
public RedisCacheManager redisCacheManager(){
RedisCacheManager cacheManager = new RedisCacheManager();
return cacheManager;
}