前言
前段時(shí)間做了一個(gè)圖床的小項(xiàng)目仁讨,安全框架使用的是Shiro
贱纠。為了使用戶7x24
小時(shí)訪問(wèn)乌奇,決定把項(xiàng)目由單機(jī)升級(jí)為集群部署架構(gòu)驶鹉。但是安全框架shiro
只有單機(jī)存儲(chǔ)的SessionDao
,盡管Shrio
有基于Ehcache-rmi
的組播/廣播實(shí)現(xiàn)絮识,然而集群的分布往往是跨網(wǎng)段的绿聘,甚至是跨地域的,所以尋求新的方案次舌。
架構(gòu)
方案
使用 redis
集中存儲(chǔ)熄攘,實(shí)現(xiàn)分布式集群共享用戶信息,這里我們采用第三方開(kāi)源插件crazycake
來(lái)實(shí)現(xiàn)彼念,pom.xml
引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.3</version>
</dependency>
配置 application.properties
:
# Redis
# 數(shù)據(jù)庫(kù)索引(默認(rèn)為0)
redis.database=0
# 服務(wù)器地址 變更為自己的
redis.host=127.0.0.1
# 服務(wù)器連接端口
redis.port=6379
# 服務(wù)器連接密碼挪圾,如果不設(shè)置密碼注釋掉即可
# redis.password=
# 連接超時(shí)時(shí)間(毫秒)
redis.timeout=30000
本來(lái)crazycake
插件已經(jīng)實(shí)現(xiàn)了RedisManager
,但是參數(shù)不可配逐沙,這里我們需要自己重寫(xiě)一下:
public class RedisManager extends WorkAloneRedisManager implements IRedisManager {
private RedisProperties redis;
private JedisPool jedisPool;
public RedisManager(RedisProperties redis) {
this.redis = redis;
}
private void init() {
synchronized(this) {
if (this.jedisPool == null) {
this.jedisPool = new JedisPool(this.getJedisPoolConfig(), redis.getHost(), redis.getPort(),
redis.getTimeout(), redis.getPassword(), redis.getDatabase());
}
}
}
@Override
protected Jedis getJedis() {
if (this.jedisPool == null) {
this.init();
}
return this.jedisPool.getResource();
}
}
參數(shù)配置 RedisProperties
:
@Data
@ConfigurationProperties(prefix = "redis")
public class RedisProperties {
private String host;
private int port;
private int timeout;
private String password;
private int database;
}
配置 ShiroConfig
:
/**
* Shiro權(quán)限配置
* 一定要配置 @Configuration 和 @EnableConfigurationProperties 注解
*/
@Configuration
@EnableConfigurationProperties({RedisProperties.class})
public class ShiroConfig {
private RedisProperties redis;
public ShiroConfig(RedisProperties redis) {
this.redis = redis;
}
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean (SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/index.html");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
// 攔截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
/**
* 靜態(tài)文件
*/
filterChainDefinitionMap.put("/file/**","anon");
/**
* 登錄注冊(cè)
*/
filterChainDefinitionMap.put("/register.shtml","anon");
filterChainDefinitionMap.put("/login.shtml","anon");
/**
* 管理后臺(tái)
*/
filterChainDefinitionMap.put("/sys/**", "roles[admin]");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SessionsSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
securityManager.setCacheManager(cacheManager());
securityManager.setSessionManager(sessionManager());
return securityManager;
}
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionIdUrlRewritingEnabled(false);
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
/**
* cacheManager 緩存 redis實(shí)現(xiàn)
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* 配置shiro redisManager
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager(redis);
return redisManager;
}
/**
* RedisSessionDAO shiro sessionDao層的實(shí)現(xiàn)
* 原理就是重寫(xiě) AbstractSessionDAO
* 有興趣的小伙伴自行閱讀源碼
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
}
小結(jié)
是不是很爽哲思,以后重啟應(yīng)用再也不用擔(dān)心用戶投訴了?