Shiro步步为营--奇淫技巧

偏执的太偏执、 2021-12-14 09:49 422阅读 0赞

目录

会话管理

启用会话管理

自定义SessionDAO

会话验证

关于缓存

记住我

认证VS记住我

启用记住我


会话管理

Shiro 提供了完整的企业级会话管理功能,不依赖于底层容器(如web容器tomcat),不管 JavaSE 还是 JavaEE 环境都可以使用,提供了会话管理、会话事件监听、会话存储/持久化、容器无关的集群、失效/过期支持、对Web 的透明支持、 SSO 单点登录的支持等特性。

为了方便我们使用Session,Shiro提供了下面几个类:

20190705134455298.png

• AbstractSessionDAO 提供了 SessionDAO 的基础实现,如生成会话ID等

• CachingSessionDAO 提供了对开发者透明的会话缓存的功能,需要设置相应的 CacheManager

• MemorySessionDAO 直接在内存中进行会话维护

• EnterpriseCacheSessionDAO 提供了带缓存功能的会话维护,默认情况下使用 MapCache 实现,内部使用ConcurrentHashMap 保存缓存的会话。

由于 AbstractSessionDAO 需要使用它内部的 SessionIdGenerator 实例来生成会话 ID。因此,在创建SessionDAO 时必须为其指定一个 SessionIdGenerator ,可以使用 Shiro 为我们提供的下面这两个实现类。

20190705134625895.png

启用会话管理

  1. /**
  2. * 第一步 创建 SessionDAO 的同时设置其 SessionIdGenerator 属性
  3. */
  4. @Bean
  5. public SessionDAO sessionDAO() {
  6. EnterpriseCacheSessionDAO sessionDAO = new EnterpriseCacheSessionDAO();
  7. // 设置 SessionIdGenerator
  8. sessionDAO.setSessionIdGenerator(new JavaUuidSessionIdGenerator());
  9. // 需要使用缓存时,设置 CacheManager
  10. EhCacheManager em = new EhCacheManager();
  11. em.setCacheManager(CacheManager.create());
  12. sessionDAO.setCacheManager(em);
  13. return sessionDAO;
  14. }
  15. /**
  16. * 第二步 创建 SessionManager 的同时设置其 SessionDAO 属性
  17. */
  18. @Bean
  19. public DefaultSessionManager sessionManager() {
  20. DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
  21. sessionManager.setGlobalSessionTimeout(1800 * 1000);
  22. sessionManager.setDeleteInvalidSessions(true);
  23. sessionManager.setSessionValidationSchedulerEnabled(true);
  24. sessionManager.setSessionDAO(sessionDAO());
  25. // 添加监听器 统计在线人数
  26. Collection<SessionListener> listeners = new ArrayList<SessionListener>();
  27. listeners.add(new BDSessionListener());
  28. sessionManager.setSessionListeners(listeners);
  29. return sessionManager;
  30. }
  31. /**
  32. * 第三步 创建 SecurityManager 的同时设置其 SessionManager 属性
  33. */
  34. @Bean
  35. public SecurityManager securityManager() {
  36. DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  37. // 1.CacheManager
  38. securityManager.setCacheManager(ehCacheManager());
  39. // 2. Authenticator
  40. securityManager.setAuthenticator(authenticator());
  41. // 3.Realm
  42. List<Realm> realms = new ArrayList<Realm>(16);
  43. realms.add(loginRealm());
  44. realms.add(userRealm());
  45. securityManager.setRealms(realms);
  46. // 4.SessionManager
  47. securityManager.setSessionManager(sessionManager());
  48. return securityManager;
  49. }

BDSessionListener 用来监听当前 Session 的存活数量,实现在线人数统计的目的。

  1. import java.util.concurrent.atomic.AtomicInteger;
  2. import org.apache.shiro.session.Session;
  3. import org.apache.shiro.session.SessionListener;
  4. public class BDSessionListener implements SessionListener {
  5. private final AtomicInteger sessionCount = new AtomicInteger(0);
  6. @Override
  7. public void onStart(Session session) {
  8. sessionCount.incrementAndGet();
  9. }
  10. @Override
  11. public void onStop(Session session) {
  12. sessionCount.decrementAndGet();
  13. }
  14. @Override
  15. public void onExpiration(Session session) {
  16. sessionCount.decrementAndGet();
  17. }
  18. public int getSessionCount() {
  19. return sessionCount.get();
  20. }
  21. }

自定义SessionDAO

自定义SessionDAO可以直接继承EnterpriseCacheSessionDAO,覆写它的doReadSession()、doUpdate()、doDelete() 三个空实现方法即可。

  1. import java.io.Serializable;
  2. import java.util.List;
  3. import org.apache.shiro.session.Session;
  4. import org.apache.shiro.session.mgt.ValidatingSession;
  5. import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.jdbc.core.JdbcTemplate;
  8. public class MySessionDao extends EnterpriseCacheSessionDAO {
  9. @Autowired
  10. private JdbcTemplate jdbcTemplate = null;
  11. @Override
  12. protected Serializable doCreate(Session session) {
  13. Serializable sessionId = generateSessionId(session);
  14. assignSessionId(session, sessionId);
  15. String sql = "insert into sessions(id, session) values(?,?)";
  16. jdbcTemplate.update(sql, sessionId, SerializableUtils.serialize(session));
  17. return session.getId();
  18. }
  19. @Override
  20. protected Session doReadSession(Serializable sessionId) {
  21. String sql = "select session from sessions where id=?";
  22. List<String> sessionStrList = jdbcTemplate.queryForList(sql, String.class, sessionId);
  23. if (sessionStrList.size() == 0)
  24. return null;
  25. return SerializableUtils.deserialize(sessionStrList.get(0));
  26. }
  27. @Override
  28. protected void doUpdate(Session session) {
  29. if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
  30. return;
  31. }
  32. String sql = "update sessions set session=? where id=?";
  33. jdbcTemplate.update(sql, SerializableUtils.serialize(session), session.getId());
  34. }
  35. @Override
  36. protected void doDelete(Session session) {
  37. String sql = "delete from sessions where id=?";
  38. jdbcTemplate.update(sql, session.getId());
  39. }
  40. }
  41. import org.apache.shiro.codec.Base64;
  42. import org.apache.shiro.session.Session;
  43. import java.io.ByteArrayInputStream;
  44. import java.io.ByteArrayOutputStream;
  45. import java.io.ObjectInputStream;
  46. import java.io.ObjectOutputStream;
  47. public class SerializableUtils {
  48. public static String serialize(Session session) {
  49. try {
  50. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  51. ObjectOutputStream oos = new ObjectOutputStream(bos);
  52. oos.writeObject(session);
  53. return Base64.encodeToString(bos.toByteArray());
  54. } catch (Exception e) {
  55. throw new RuntimeException("serialize session error", e);
  56. }
  57. }
  58. public static Session deserialize(String sessionStr) {
  59. try {
  60. ByteArrayInputStream bis = new ByteArrayInputStream(Base64.decode(sessionStr));
  61. ObjectInputStream ois = new ObjectInputStream(bis);
  62. return (Session) ois.readObject();
  63. } catch (Exception e) {
  64. throw new RuntimeException("deserialize session error", e);
  65. }
  66. }
  67. }

会话验证

• Shiro 提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话;

• 出于性能考虑,一般情况下都是获取会话时来验证会话是否过期并停止会话的;但是如在 web 环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,Shiro 提供了会话验证调度器SessionValidationScheduler ;

• Shiro 也提供了使用Quartz会话验证调度器:QuartzSessionValidationScheduler ;

关于缓存

Shiro 会自动检测其内部组件(如Realm)是否实现了CacheManagerAware 接口,并自动为其注入相应的CacheManager。CachingRealm 为我们提供了缓存的一些基础实现,前面身份验证提到的AuthenticatingRealm 以及授权中的 AuthorizingRealm 就继承了 CachingRealm 。

因此,若 SecurityManager 实现了 SessionSecurityManager,Shiro 会判断其内部的 SessionManager 是否实现了CacheManagerAware 接口,如果实现了会把 CacheManager 设置给它。同时, SessionManager 也会判断其内部的 SessionDAO(如继承自CachingSessionDAO)是否实现了CacheManagerAware,如果实现了会把 CacheManager设置给它。设置了缓存的 SessionManager,查询时会先查缓存,如果找不到才查数据库。

记住我

Shiro 提供了记住我(RememberMe)的功能,比如访问如淘宝等一些网站时,关闭了浏览器,下次再打开时还是能记住你是谁,下次访问时无需再登录即可访问。

认证VS记住我

• subject.isAuthenticated() 表示用户进行了身份验证登录的,即使用 Subject.login 进行了登录;

• subject.isRemembered():表示用户是通过记住我登录的,此时可能并不是真正的你在访问(可能是你的朋友使用你的电脑,或者你的cookie 被窃取);

• 两者二选一,即 subject.isAuthenticated()==true,则 subject.isRemembered()==false;反之一样。

启用记住我

  1. /**
  2. * 第一步 创建 SimpleCookie
  3. */
  4. @Bean
  5. public SimpleCookie simpleCookie() {
  6. SimpleCookie simpleCookie = new SimpleCookie();
  7. simpleCookie.setName("shiro-cookies");
  8. simpleCookie.setHttpOnly(true);
  9. // 设置 Cookies 的过期时间
  10. simpleCookie.setMaxAge(60);
  11. return simpleCookie;
  12. }
  13. /**
  14. * 第二步 创建 CookieRememberMeManager 的同时设置其 Cookie 属性
  15. */
  16. @Bean
  17. public CookieRememberMeManager cookieRememberMeManager() {
  18. CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
  19. cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
  20. cookieRememberMeManager.setCookie(simpleCookie());
  21. return cookieRememberMeManager;
  22. }
  23. /**
  24. * 第三步 创建 SecurityManager 的同时设置其 CookieRememberMeManager 属性
  25. */
  26. @Bean
  27. public SecurityManager securityManager() {
  28. DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  29. // 1.CacheManager
  30. securityManager.setCacheManager(ehCacheManager());
  31. // 2. Authenticator
  32. securityManager.setAuthenticator(authenticator());
  33. // 3.Realm
  34. List<Realm> realms = new ArrayList<Realm>(16);
  35. realms.add(loginRealm());
  36. realms.add(userRealm());
  37. securityManager.setRealms(realms);
  38. // 4.SessionManager
  39. securityManager.setSessionManager(sessionManager());
  40. // 5. CookieRememberMeManager
  41. securityManager.setRememberMeManager(cookieRememberMeManager());
  42. return securityManager;
  43. }

在用户登录校验时,还需要对 UsernamePasswordToken 进行如下设置,才能启用记住我。

  1. // 将用户名和密码封装成 UsernamePasswordToken 对象
  2. UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
  3. token.setRememberMe(true);

在谷歌浏览器中查看用户登录后生成的 cookie 截图如下:

20190705141457697.png

发表评论

表情:
评论列表 (有 0 条评论,422人围观)

还没有评论,来说两句吧...

相关阅读

    相关 IDEA技巧

    IDEA是目前市场上最好用的IDE,我说的! ![format_png][] 前几年eclipse在市场上非常流行,因此大多数人都习惯了eclipse的一些快捷键。近年来,