Shiro Security是非常不错的Security框架
最近在我的项目中进行相关整合,shiro不难,难就难在如何对已经成熟的系统进行整合
作为相关切入点,我也考虑了很久,整体运用上了如张开涛大佬所说
对于Subject我们一般这么使用:
1、身份验证(login)
2、授权(hasRole*/isPermitted*或checkRole*/checkPermission*)
3、将相应的数据存储到会话(Session)
4、切换身份(RunAs)/多线程身份传播
5、退出
回归标题,正常整合过后,基本可以正确的进行登录与登出
那么开始进行细节休整
大体介绍我们的系统架构是springmvc进行开发,一个项目里分出了两套系统,系统与系统间的区分仅仅只是 通过url路径上的不同,来表现。那么现在就出现了一种情况,系统1基本用户都能登入,而系统2却只有相关权限人才能登入。
表面上视乎能在登陆上做控制,比如login的时候通过权限判断就可以做到。那么这时候考虑的是如果用户之间通过url强行进入呢。
比如系统1用户登录,直接修改url进入系统2。此时属于非法访问。
正常我们会在filter内做过滤,也好做,在相关登录逻辑内对session赋予标记,在filter做过滤就over了,不符合直接logout。
想到这里,作为一个shiro框架使用者是不是感觉shiro貌似没起作用。
于是思考一个比较合理的方案,于是决定当用户登陆系统时,对sessionid进行赋值,系统1则在sessionid前+上相关字符串标记session为系统1用户。系统2则在sessionid前+上相关字符串标记session为系统2用户。
需求清晰,那么进行可行性分。
那么结合shiro中的session管理,开始做起了调查。
首先进行是的shiro如何修改sessionid,这能找却不能满足我的需求。因为市面上的他们都是以单系统,或者双项目双系统进行写的。完全不能符合我想要的。
一般都是这么做的
<!-- 会话Cookie模板 -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="sid"/> **
<!--设置Cookie名字,默认为JSESSIONID-->
<property name="name" value="WEBSID" />** </bean>
上面的解决方案是对不同项目进行不同的jsessionid名的配置
但我一个项目里怎么可能出现两个shiro,不符合我的要求
于是考虑shiro内置处理session我要做点手脚。
首先是查到了sessionid生成器
自定义了一个id生成器
public class SysSessionIdGenerator implements SessionIdGenerator { @Override public Serializable generateId(Session session) { if(session.getAttribute("sysType")!=null){ return session.getAttribute("sysType").toString()+"_"+UUID.randomUUID().toString(); } return UUID.randomUUID().toString(); } }
主要实现SessionIdGenerator generateId的方法。这里可以看见我吧sysType加到uuid前面。
然后就是注入给shiro使用
配置需要加入
<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
<property name="sessionIdGenerator" ref="sessionIdGenerator"/>
</bean>
<bean id="sessionIdGenerator" class="***.SysSessionIdGenerator"/>
sessionDAO 也要记得注入给 sessionManager 这里我就不写了
那么问题又来了,逻辑上不上应该生成session的时候调用吗,那么shiro的session是在什么时候生成的呢。
我翻了翻源码
subject.getSession()
这个get方法实现的
public Session getSession() { return getSession(true); } public Session getSession(boolean create) { if (log.isTraceEnabled()) { log.trace("attempting to get session; create = " + create + "; session is null = " + (this.session == null) + "; session has id = " + (this.session != null && session.getId() != null)); } if (this.session == null && create) { //added in 1.2: if (!isSessionCreationEnabled()) { String msg = "Session creation has been disabled for the current subject. This exception indicates " + "that there is either a programming error (using a session when it should never be " + "used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " + "for the current Subject. See the " + DisabledSessionException.class.getName() + " JavaDoc " + "for more."; throw new DisabledSessionException(msg); } log.trace("Starting session for host {}", getHost()); SessionContext sessionContext = createSessionContext(); Session session = this.securityManager.start(sessionContext); this.session = decorate(session); } return this.session; }
可以看出,当你不传参数的时候默认进行调用ture,
简单的 说当getsession(true)时,会判断现在是否有session,如果没有,则新生成一个,有则就用现有的。false则是如果没有,就不生成了返回null。
发现一个问题,当我刚刚进入登入页面的时候,此时shiro已经生成了一个session,于是在登陆校验时候不会生成新的session了。于是考虑了各种办法,比如登入的时候先logout一下等等,当然这些都叫做歪门邪道。后来发现了这么一篇文章
我用了他的方法反正是没成功,系统还变的有点混乱。
仔细一看他的文章中有这么一段:
使用过程中发现Shiro在登录之后不会生成新的Jessionid。这显然会出现Session_Fixation。
Shiro自己说会在下一个版本1.3 fix这个问题。
我shiro起步是张开涛大大文章里的版本,所以是1.2.2的。尝试性的换个版本,看了下官网的版本是1.3.2
先换了再说。
发现确实登陆之前与之后sessionid变了,看来在1.3.2的时候会在getsession重新获得session。
但是这一点我并不明确,只能推测是这样。
但是这还不是我的正道。重新明确技术细节,发现我需要重载getsession方法,在getsession的时候把sysTpye(系统标记)字符串传递进去。
还是刚刚上面的代码有这么一行
SessionContext sessionContext = createSessionContext(); Session session = this.securityManager.start(sessionContext);
它吧sessionContext传递进去创建了。那么我似乎可以在这里做文章,查阅资料后发现SessionContext继承了Map。那么我就可以直接对它进行put了。
那么继续往下挖掘源码。
这篇挖掘的文章可以看看,我反正看完思路清晰了一点,毕竟自己debug比较混乱。
此时考虑到sessionContext对象还不是最终目标session,那么我赋予的值要么shiro会对其进行全部输出到session里,要么什么也不做
public class SimpleSessionFactory implements SessionFactory { /** * Creates a new {@link SimpleSession SimpleSession} instance retaining the context's * {@link SessionContext#getHost() host} if one can be found. * * @param initData the initialization data to be used during {@link Session} creation. * @return a new {@link SimpleSession SimpleSession} instance */ public Session createSession(SessionContext initData) { if (initData != null) { String host = initData.getHost(); if (host != null) { return new SimpleSession(host); } } return new SimpleSession(); } }
最后扒到这里,它只是取了host 然后赋值,生成simplesession对象。
看来这里是SessionContext的数据终点,那么我systpye也得在这里进行操作了。
查询 SessionFactory相关资料后,发现原来我们自己也可以定义自己的SessionFactory
对象。于是自定义了一个SessionFactory
public class HrsystemSessionFactory implements SessionFactory { @Override public Session createSession(SessionContext initData) { Session session = null; if (initData != null) { String host = initData.getHost(); if (host != null) { session = new SimpleSession(host); } if(initData.get("sysType")!=null){ session.setAttribute("sysType", initData.get("sysType")); } }else{ session = new SimpleSession(); } return session; } }
这里做的是把sysType的值赋值给session,然后配置文件注入。
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!-- session的失效时长,单位毫秒 --> <property name="globalSessionTimeout" value="1800000"/> <!-- 删除失效的session --> <property name="deleteInvalidSessions" value="true"/> <property name="sessionFactory" ref="sessionFactory"/> <property name="sessionDAO" ref="sessionDAO"/> </bean> <bean id="sessionFactory" class="***.HrsystemSessionFactory"/>
那么与刚刚的SysSessionIdGenerator对接上了。
接下来就是重头戏,重载getsession();以上都有废话之嫌,长话短说。
首先拓展suject接口
public interface SysSubject extends Subject { Session getSession(String sysType); }
这里我直接继承Subject接口;
public static Subject getSubject() { Subject subject = ThreadContext.getSubject(); if (subject == null) { subject = (new Subject.Builder()).buildSubject(); ThreadContext.bind(subject); } return subject; }
Subject实例是使用ThreadLocal模式来获取,若没有则创建一个并绑定到当前线程。此时创建使用的是Subject内部类Builder来创建的,Builder会创建一个SubjectContext接口的实例DefaultSubjectContext,最终会委托securityManager来根据SubjectContext信息来创建一个Subject
上面代码就是前面介绍源码的文章里有讲的。
那么主要的就是需要进行实例化编写了。
这里有点回到原点了,从创建session变成了如何创建subject对象。
但是仔细剖析方法结构,会发现其实subject获取模式与session获取模式是一样的。
但是重写subjectFactory在网络与张开涛里面都没有触及或者很少。这可能需要阅读源码才理解。
代码如下
public class HrsystemSubjectFactory extends DefaultWebSubjectFactory { public HrsystemSubjectFactory() { super(); } public Subject createSubject(SubjectContext context) { if (!(context instanceof WebSubjectContext)) { return super.createSubject(context); } WebSubjectContext wsc = (WebSubjectContext) context; SecurityManager securityManager = wsc.resolveSecurityManager(); Session session = wsc.resolveSession(); boolean sessionEnabled = wsc.isSessionCreationEnabled(); PrincipalCollection principals = wsc.resolvePrincipals(); boolean authenticated = wsc.resolveAuthenticated(); String host = wsc.resolveHost(); ServletRequest request = wsc.resolveServletRequest(); ServletResponse response = wsc.resolveServletResponse(); return new HrsystemSubject(principals, authenticated, host, session, sessionEnabled, request, response, securityManager); } /** * @deprecated since 1.2 - override {@link #createSubject(org.apache.shiro.subject.SubjectContext)} directly if you * need to instantiate a custom {@link Subject} class. */ @Deprecated protected Subject newSubjectInstance(PrincipalCollection principals, boolean authenticated, String host, Session session, ServletRequest request, ServletResponse response, SecurityManager securityManager) { return new WebDelegatingSubject(principals, authenticated, host, session, true, request, response, securityManager); } }
主要是在createSubject方法中实例化HrsystemSubject对象将它传递出去。
而这个HrsystemSubject实例化SysSubject接口 与继承WebDelegatingSubject对象
public class HrsystemSubject extends WebDelegatingSubject implements SysSubject{ public HrsystemSubject(PrincipalCollection principals, boolean authenticated, String host, Session session, boolean sessionEnabled, ServletRequest request, ServletResponse response, SecurityManager securityManager) { super(principals, authenticated, host, session, sessionEnabled, request, response, securityManager); } public Session getSession(String type) { SessionContext sessionContext = createSessionContext(); sessionContext.put("sysType", type); Session session = this.securityManager.start(sessionContext); super.session = decorate(session); return super.session; } }
ok那么此时还没完,我们需要把HrsystemSubjectFactory注入到shiro中去。
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="authenticator" ref="authenticator"></property> <property name="subjectFactory" ref="subjectFactory"/> <property name="realms"> <list> <ref bean="hrRealm" /> <ref bean="bizRealm"/> </list> </property> <property name="sessionManager" ref="sessionManager" /> </bean> <bean id="subjectFactory" class="***HrsystemSubjectFactory"/>
这里我完全是模仿着session套路感觉注入的了,因为并没有文章这么做。(或者我没看到吧)
于是此时SecurityUtils.getSubject();get的出来的对象就是我们的HrsystemSubject了。
但是这里运用了下父子继承原理,get对象实际是Subject,内部对象的实例其实是HrsystemSubject
那么我们在对其强制装换成我们刚刚定的(SysSubject)SecurityUtils.getSubject();
于是关于getsession的重载就完成了。
那么逻辑上输入getsession(string sysType)那么就可以对sessionid进行我想要的值了。也能做逻辑控制了。运用场景还是挺广的。
相关推荐
通过使用 Redis 作为 Session 存储,多个应用服务器可以共享同一个 Session,从而实现分布式 session 共享。 7. 优点 本方案的优点包括: * 实现分布式 session 共享 * 提高系统的可扩展性和灵活性 * 简化了登录 ...
而Redis则是一个高性能的键值存储系统,常用于实现分布式环境下的session共享,以解决单体应用或微服务架构中的session粘滞性问题。 描述中提到的"数据库、redis改为本地"意味着在配置中,Redis服务器可能被设置为...
SSM项目集成Shiro搭建session共享是一个常见的需求,特别是在构建分布式系统时,为了实现用户登录状态在多个服务器间的一致性。在这个项目中,我们使用了SpringMvc4.3、Spring4.3、Mybatis3.4作为基础框架,Shiro1.4...
一个服务器上搭建了多个tomcat或者weblogic,端口不一样,同时启动访问时session丢失。如:A,B两个服务,在浏览器中登录访问A后,当前打开的浏览器上在开一个选项卡访问B服务后,回过来点击访问A时session丢失,...
权限管理Shiro系统-Apache开源顶级项目shiro-SpringMVC_Shiro项目
Apache Shiro 是一个强大且易用的Java安全框架,提供了认证、授权、加密和会话管理功能,可以非常轻松地开发出足够安全的应用。在本项目实例中,我们将深入探讨如何将Shiro与Spring框架整合,以实现更高效、灵活的...
在这种情况下,如何实现用户登录状态(即session)在多个服务器间的共享就成为一个重要的问题。"nginx+tomcat shiro实现多tomcat下session共享"这一主题,就是探讨在这样的架构中,如何有效管理和共享session。 1. ...
Apache Shiro 是一个强大且易用的 Java 安全框架,它提供了认证、授权、加密和会话管理功能,简化了企业级应用的安全实施。在本文中,我们将深入探讨 Shiro 的授权机制,以及如何在实际的企业项目中与之整合,实现...
在提供的"kktest"压缩包文件中,很可能是包含了一个简单的示例项目,用于演示如何配置和使用Shiro与Redis进行Session管理。实际操作时,可以通过查看源代码,了解具体的配置细节和实现逻辑,这对于理解和学习这一...
Apache Shiro是一个强大的Java安全框架,它为应用程序提供了身份验证(认证)、授权(权限控制)、加密和会话管理功能。这个基于Shiro的项目旨在简化IT项目中的权限管理任务,帮助开发者快速构建安全的系统。 1. **...
在现代Web应用开发中,session共享是一个常见的需求,特别是在分布式系统中。Apache Shiro是一个强大且易用的Java安全框架,提供了认证、授权、加密和会话管理功能。结合Redis这个高效的键值存储系统,我们可以实现...
SSM和Shiro小项目是一个综合性的Java Web开发实践,主要涵盖了Spring、Spring MVC(SSM)和Apache Shiro这三个核心框架的集成与应用。这个项目旨在帮助开发者理解和掌握如何在实际开发中实现用户身份验证和授权功能...
本项目是一个基于Spring Boot和Shiro的权限管理系统,旨在帮助开发者快速搭建具有权限控制的后台系统。 Spring Boot 是一个用于简化Spring应用程序初始搭建以及开发过程的框架。它通过自动配置、起步依赖等特性,...
在分布式环境下,传统的 Session 数据通常只能在一个特定的服务器上访问,导致用户在切换服务器时会话丢失。`shiro-redisson` 提供的基于 Redis 的会话管理,可以将会话数据存储在 Redis 中,实现会话在集群中的共享...
SpringBoot整合Shiro示例实现动态权限加载更新+Session共享+单点登录 SpringBoot整合Shiro示例实现动态权限加载更新+Session共享+单点登录 SpringBoot整合Shiro示例实现动态权限加载更新+Session共享+单点登录 ...
在Java Web开发领域,Apache Shiro 是一个非常流行的权限管理框架。它提供了一套完整的认证、授权、加密和会话管理功能。本篇文章将详细介绍如何通过Shiro框架来获取用户的登录状态以及具体用户信息。 #### 一、...
采用SpringBoot2.0、MyBatis-Plus、Shiro框架,开发的一套权限系统 采用SpringBoot2.0、MyBatis-Plus、Shiro框架,开发的一套权限系统 采用SpringBoot2.0、MyBatis-Plus、Shiro框架,开发的一套权限系统 采用...
所以系统一旦引入shiro后,采用传统的tomcat session共享机制是无效的,必须采用面向shiro 的session共享。 网上针对“shiro session共享”的文章比较多,但是大同小异,基本是基于redis实现的。但是该套实现,代码...
Apache Shiro 是一个强大且易用的Java安全框架,提供了认证、授权、加密和会话管理功能,可以非常容易地开发出足够安全的应用。它易于理解,使得开发者可以专注于业务逻辑,而不用花费大量时间在安全实现上。 3. *...
在分布式系统或微服务架构中,会话管理成为一个关键问题,因为默认情况下,Shiro的session管理是基于单个服务器的,无法在多台服务器之间共享用户会话信息。 在传统的Web应用中,session信息通常存储在服务器的内存...