前面我们已经整合了简单的shiro框架,并使用3DES对密码进行加密。接下来,我们将添加上基于redis的cache缓存管理和session会话管理。
简单的说下cache和session
缓存cache是什么呢?
一个使用缓存Cache的站点会监听客户端向服务器端发出的请求,并保存服务器端的回应。 比如HTML页面、图片等文件。接着,如果有另外一个使用相同URL发送请求,他能够使用之前已经保存下来的反馈文件,而不是再次向服务器发出请求。
那么,缓存又有什么用呢?
缓存能减少延迟。 因为所发出的网页请求是指向更接近客户端的缓存而不再是源服务器端,因此请求所花费时间更短,这让网站看上去反应更快。
缓存还能降低网络负荷。 因为缓存文件可以重复使用,节省了不少的带宽.这也给用户省了不少流量。
会话session是什么呢?
session是通过以cookie的方式向客户端发送随机字符串,每次客户端发起请求时只要携带该随机字符串便可很容易进行验证。
那么会话的原理是什么呢?
1.用户发出登录请求
2.判断账户密码是否正确。
3.如正确,则返回数据并在cookie中写随机字符串(sessionID),并且在服务端以{随机字符串:{‘k’:‘v’}}的形式存储用户相关数据。
4.下次同个客户发送请求,携带cookie(包含sessionID)
5.服务端会判断cookie是否包含sessionID,如有再去服务器内存中查询该sessionID 是否有对应的数据,如果有,则说明是登录过的用户,返回数据。
1. 添加redis依赖
pom.xml:
<!-- Shiro-redis插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>
2. 添加redis配置
application.yml:
spring:
redis:
host: 127.0.0.1
port: 6379
timeout: 6000
jedis:
pool:
max-active: 1000
max-wait: -1
min-idle: 5
max-idle: 10
password: xxxxxx
3. shiro相关类
shiroConfig.class:
@Configuration
public class ShiroConfig {
private final String CACHE_KEY = "shiro:cache:";
private final String SESSION_KEY = "shiro:session:";
private final int EXPIRE = 1800;
//Redis配置
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.password}")
private String password;
/**
* 创建ShiroFilterFactoryBean
*/
/**
* 创建DefaultWebSecurityManager
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(
@Qualifier("sessionManager") SessionManager sessionManager,
@Qualifier("cacheManager") RedisCacheManager cacheManager,
@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setSessionManager(sessionManager);
defaultWebSecurityManager.setCacheManager(cacheManager);
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
/**
* 创建Realm
*/
/**
* 配置redis管理器
* @return
*/
@Bean(name = "redisManager")
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setTimeout(timeout);
redisManager.setPassword(password);
return redisManager;
}
/**
* 配置cache管理器
* @param redisManager
* @return
*/
@Bean(name = "cacheManager")
public RedisCacheManager cacheManager(@Qualifier("redisManager") RedisManager redisManager) {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager);
redisCacheManager.setKeyPrefix(CACHE_KEY);
// 配置缓存的话要求放在session里面的实体类必须有个id标识
redisCacheManager.setPrincipalIdFieldName("userId");
return redisCacheManager;
}
/**
* sessionId生成器
* @return
*/
@Bean(name = "sessionIdGenerator")
public ShiroSessionIdGenerator sessionIdGenerator() {
return new ShiroSessionIdGenerator();
}
/**
* 配置RedisSessionDAO
* @param redisManager
* @param sessionIdGenerator
* @return
*/
@Bean(name = "redisSessionDao")
public RedisSessionDAO redisSessionDAO(
@Qualifier("redisManager") RedisManager redisManager,
@Qualifier("sessionIdGenerator") ShiroSessionIdGenerator sessionIdGenerator) {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager);
redisSessionDAO.setSessionIdGenerator(sessionIdGenerator);
redisSessionDAO.setKeyPrefix(SESSION_KEY);
redisSessionDAO.setExpire(EXPIRE);
return redisSessionDAO;
}
@Bean(name = "sessionManager")
public SessionManager sessionManager(@Qualifier("redisSessionDao") RedisSessionDAO redisSessionDAO) {
MySessionManager mySessionManager = new MySessionManager();
mySessionManager.setSessionDAO(redisSessionDAO);
return mySessionManager;
}
}
其中调用对象有两种方法,一种是像我们上面那样,通过指定bean的name,用@Qualifier来获取对象;还有一种是直接用方法调用,如mySessionManager.setSessionDAO(redisSessionDAO()) 。
自定义会话管理器MySessionManager.class:
public class MySessionManager extends DefaultWebSessionManager {
//前端请求头传这个
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public MySessionManager() {
super();
this.setDeleteInvalidSessions(true);
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
//如果请求头中有 Authorization 则其值为sessionId
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}
shiro会话id生成器ShiroSessionIdGenerator.class:
public class ShiroSessionIdGenerator implements SessionIdGenerator {
@Override
public Serializable generateId(Session session) {
Serializable sessionId = new JavaUuidSessionIdGenerator().generateId(session);
return String.format(RedisConstant.REDIS_PREFIX_LOGIN, sessionId);
}
}
redis常量类RedisConstant.class:
public interface RedisConstant {
/**
* TOKEN前缀
*/
String REDIS_PREFIX_LOGIN = "login_token_%s";
/**
* 过期时间2小时
*/
Integer REDIS_EXPIRE_TWO = 7200;
/**
* 过期时间15分
*/
Integer REDIS_EXPIRE_EMAIL = 900;
/**
* 过期时间5分钟
*/
Integer REDIS_EXPIRE_KAPTCHA = 300;
/**
* 暂无过期时间
*/
Integer REDIS_EXPIRE_NULL = -1;
}
shiro的工具类ShiroUtils.class:
public class ShiroUtils {
/** 私有构造器 **/
private ShiroUtils(){ }
private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);
/**
* 获取当前用户Session
* @Author Clarence1
* @Return SysUserEntity 用户信息
*/
public static Session getSession() {
return SecurityUtils.getSubject().getSession();
}
/**
* 用户登出
* @Author Clarence1
*/
public static void logout() {
SecurityUtils.getSubject().logout();
}
/**
* 获取当前用户信息
* @Author Clarence1
* @Return SysUserEntity 用户信息
*/
public static User getUserInfo() {
return (User) SecurityUtils.getSubject().getPrincipal();
}
/**
* 删除用户缓存信息
* @Author Clarence1
* @Param username 用户名称
* @Param isRemoveSession 是否删除Session
* @Return void
*/
public static void deleteCache(String username, boolean isRemoveSession){
//从缓存中获取Session
Session session = null;
Collection<Session> sessions = redisSessionDAO.getActiveSessions();
User user;
Object attribute = null;
for(Session sessionInfo : sessions){
//遍历Session,找到该用户名称对应的Session
attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (attribute == null) {
continue;
}
user = (User) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();
if (user == null) {
continue;
}
if (Objects.equals(user.getUsername(), username)) {
session=sessionInfo;
}
}
if (session == null||attribute == null) {
return;
}
//删除session
if (isRemoveSession) {
redisSessionDAO.delete(session);
}
//删除Cache,在访问受限接口时会重新授权
DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
Authenticator authc = securityManager.getAuthenticator();
((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute);
}
}
SpringUtil.class:
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext context;
/**
* Spring在bean初始化后会判断是不是ApplicationContextAware的子类
* 如果该类是,setApplicationContext()方法,会将容器中ApplicationContext作为参数传入进去
* @Author Clarence1
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
/**
* 通过Name返回指定的Bean
* @Author Clarence1
*/
public static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
}
需要注意的,由于session的对象需要序列化,所以我们必须要给每一个实体类加上implements Serializable。 其实Serializable接口不提供任何方法,只是为该标识可以序列化而已。
public class Permission implements Serializable {
private static final long serialVersionUID = 1L;
........
}
我们还需要添加,当验证成功后清除cache和session。
在UserRealm的AuthenticationInfo方法中修改
//判断密码
try {
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, threeDES.decryptThreeDESECB(user.getPassword(), key), "");
//验证成功开始踢人(清除缓存和Session)
ShiroUtils.deleteCache(token.getUsername(),true);
return simpleAuthenticationInfo;
} catch (Exception e) {
e.printStackTrace();
return null;
}
到这里,我们的整合完整结束。
前面说到我们的测试是使用swagger2的形式,为此我决定再写一篇关于swagger和一些我遇到的错误点的文章。
如果有什么需要改进的,还请多加指教。
&spm=1001.2101.3001.5002&articleId=100134270&d=1&t=3&u=5491273f834741e2b71e0ed1d14e2456)
3870

被折叠的 条评论
为什么被折叠?



