`
云中之海
  • 浏览: 31464 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

shiro 一个项目多个系统sessionid赋值 (getsession 重载)

阅读更多

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 自己实现登录后重新生成sessionid

我用了他的方法反正是没成功,系统还变的有点混乱。

仔细一看他的文章中有这么一段:

使用过程中发现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比较混乱。

shiro源码分析 

此时考虑到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进行我想要的值了。也能做逻辑控制了。运用场景还是挺广的。

 

 

 

0
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics