如果單機,使用EHCache就可以的,單如果多節點部署時就不行了,本文主要將Shiro和Redis緩存集成,在上一篇文章Shiro功能應用(六)–登陸失敗重試次數控制代碼基礎進行添加Redis緩存。
代碼實現:
代碼地址:
https://github.com/OooooOz/SpringBoot-Shiro
首先將ShiroConfig關於EHCache的和SessionManager的配置去掉。
ShiroConfig的安全管理器SecurityManager:
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("shiroRealm") MyShiroRealm shiroRealm){
... ... ... ...
securityManager.setCacheManager(redisCacheManager()); //配置 redis緩存管理器
securityManager.setSessionManager(redisSessionManager()); //配置 redissession管理
return securityManager;
}
ShiroConfig的Redis緩存管理器:
//import org.crazycake.shiro.RedisCacheManager;包的RedisCacheManager的對象
@Bean("redisCacheManager")
public RedisCacheManager redisCacheManager(){
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
ShiroConfig的Redis配置管理器:
@Bean
public RedisManager redisManager(){
RedisManager redisManager = new RedisManager();
redisManager.setHost("192.168.2.104");
redisManager.setPort(6379);
//redisManager.setPassword("123456");
return redisManager;
}
ShiroConfig的Redis會話管理器:
@Bean("redisSessionManager")
public SessionManager redisSessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
Collection<SessionListener> listeners = new ArrayList<SessionListener>();
//配置監聽
listeners.add(sessionListener());
sessionManager.setSessionListeners(listeners);
sessionManager.setSessionIdCookie(sessionIdCookie());
sessionManager.setSessionDAO(redisSessionDAO());
sessionManager.setCacheManager(redisCacheManager());
//sessionManager.setGlobalSessionTimeout(60000); //全局會話超時時間(單位毫秒),默認30分鐘 暫時設置爲10秒鐘 用來測試
sessionManager.setDeleteInvalidSessions(true);
//取消url 後面的 JSESSIONID
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
ShiroConfig的redisSessionDao:
@Bean("redisSessionDAO")
public SessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setExpire(3000);//session在redis中的保存時間,最好大於session會話超時時間,單位s
return redisSessionDAO;
}
自定義Realm類修改認證方法返回的info:
SimpleAuthenticationInfo authenticationInfo =
// new SimpleAuthenticationInfo(user,user.getPassword(),ByteSource.Util.bytes(user.getSalt()),"shiroRealm");
new SimpleAuthenticationInfo(user,user.getPassword(),new MySimpleByteSource(user.getSalt()),"shiroRealm");
新增的MySimpleByteSource類:
/**
* 解決:
* shiro 使用緩存時出現:java.io.NotSerializableException: org.apache.shiro.util.SimpleByteSource
* 序列化後,無法反序列化的問題
*/
public class MySimpleByteSource implements ByteSource, Serializable {
private static final long serialVersionUID = 5175082362119580768L;
private byte[] bytes;
private String cachedHex;
private String cachedBase64;
public MySimpleByteSource(){
}
public MySimpleByteSource(byte[] bytes) {
this.bytes = bytes;
}
public MySimpleByteSource(char[] chars) {
this.bytes = CodecSupport.toBytes(chars);
}
public MySimpleByteSource(String string) {
this.bytes = CodecSupport.toBytes(string);
}
public MySimpleByteSource(ByteSource source) {
this.bytes = source.getBytes();
}
public MySimpleByteSource(File file) {
this.bytes = (new BytesHelper()).getBytes(file);
}
public MySimpleByteSource(InputStream stream) {
this.bytes = (new BytesHelper()).getBytes(stream);
}
public static boolean isCompatible(Object o) {
return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
@Override
public byte[] getBytes() {
return this.bytes;
}
@Override
public String toHex() {
if(this.cachedHex == null) {
this.cachedHex = Hex.encodeToString(this.getBytes());
}
return this.cachedHex;
}
@Override
public String toBase64() {
if(this.cachedBase64 == null) {
this.cachedBase64 = Base64.encodeToString(this.getBytes());
}
return this.cachedBase64;
}
@Override
public boolean isEmpty() {
return this.bytes == null || this.bytes.length == 0;
}
@Override
public String toString() {
return this.toBase64();
}
@Override
public int hashCode() {
return this.bytes != null && this.bytes.length != 0? Arrays.hashCode(this.bytes):0;
}
@Override
public boolean equals(Object o) {
if(o == this) {
return true;
} else if(o instanceof ByteSource) {
ByteSource bs = (ByteSource)o;
return Arrays.equals(this.getBytes(), bs.getBytes());
} else {
return false;
}
}
private static final class BytesHelper extends CodecSupport {
private BytesHelper() {
}
public byte[] getBytes(File file) {
return this.toBytes(file);
}
public byte[] getBytes(InputStream stream) {
return this.toBytes(stream);
}
}
主要問題:
1).java.io.NotSerializableException: org.apache.shiro.util.SimpleByteSource異常:
因爲認證方法返回info對象第三個參數ByteSource類型,並沒有實現序列化接口,所以序列化的時候出現異常,
解決: 自定義一個類實現ByteSource和Serializable接口,見上文MySimpleByteSource
2).com.demo.entity.User cannot be cast to com.demo.entity.User轉換異常:
一看,同樣類型爲什麼轉換不成呢?因爲SpringBoot項目配了熱部署。
解決: 註釋掉熱部署依賴。Clear一下就好了。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>true</scope>
</dependency>
3). org.crazycake.shiro.exception.PrincipalInstanceException: class com.demo.entity.User must has getter for field: authCacheKey or id無法獲取緩存中權限redisKey。
因爲shiro-redis3.1.0裏面,有個條件寫死的。本人的User類不滿足條件。
可見,User類要有AuthCacheKey或者Id屬性,也就是要有getAuthCacheKey()或getId()方法,條件才能滿足,下面會通過映射獲取屬性值。不滿足就拋異常了。
解決: 也行換個包可以解決,要麼就修改User類,但改動會有點大,要麼就換一種Redis的實現方式,也就是不用Shiro-Redis的包。可查看下一篇Shiro功能應用(八)–Shiro集成RedisTemplate(SDR)
4).RedisCache纔是主要操作redis緩存的操作類, 代碼中任何出現操作緩存(get、put)之類的,都會到這個類操作