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

标签: shiro 项目 系统 | 发表时间:2017-08-04 16:25 | 作者:
出处:http://www.iteye.com

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 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [shiro 项目 系统] 推荐:

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

- - ITeye博客
Shiro Security是非常不错的Security框架. 最近在我的项目中进行相关整合,shiro不难,难就难在如何对已经成熟的系统进行整合. 作为相关切入点,我也考虑了很久,整体运用上了如张开涛大佬所说. 对于Subject我们一般这么使用:. 1、身份验证(login). 2、授权(hasRole*/isPermitted*或checkRole*/checkPermission*).

在 Web 项目中应用 Apache Shiro

- - 企业架构 - ITeye博客
Apache Shiro 是功能强大并且容易集成的开源权限框架,它能够完成认证、授权、加密、会话管理等功能. 认证和授权为权限控制的核心,简单来说,“认证”就是证明你是谁. Web 应用程序一般做法通过表单提交用户名及密码达到认证目的. “授权”即是否允许已认证用户访问受保护资源. 关于 Shiro 的一系列特征及优点,很多文章已有列举,这里不再逐一赘述,本文重点介绍 Shiro 在 Web Application 中如何实现验证码认证以及如何实现单点登录.

Oauth与Shiro整合的开源项目

- - zzm
Apache OLTU 是JAVA的OAUTH参考实现;. Shiro是轻量级的权限管理框架;. 二者整合, 提供一个轻量的OAUTH2应用框架, 并根据不同的应用场景提供不同的实现(WEB,移动端); 提供基于OAUTH的5类grant_type的实现;. 相比之前已经实现的  spring-oauth-server 项目, 该项目具有以下特点:.

Apache Shiro 介绍

- - CSDN博客推荐文章
什么是Apache Shiro?. Apache shiro 是一个强大而灵活的开源安全框架,可清晰地处理身份认证、授权、会话(session)和加密. Apache Shiro最主要的初衷是为了易用和易理解,处理安全问题可能非常复杂甚至非常痛苦,但并非一定要如此. 一个框架应该尽可能地将复杂的问题隐藏起来,提供清晰直观的API使开发者可以很轻松地开发自己的程序安全代码.

Shiro权限框架

- If you are thinking one year ahead, you plant rice. If you are thinking twenty years ahead, you plant trees. If you are thinking a hundred years ahead, you educate people. - BlogJava-首页技术区
开发系统中,少不了权限,目前java里的权限框架有SpringSecurity和Shiro(以前叫做jsecurity),对于SpringSecurity:功能太过强大以至于功能比较分散,使用起来也比较复杂,跟Spring结合的比较好. 对于初学Spring Security者来说,曲线还是较大,需要深入学习其源码和框架,配置起来也需要费比较大的力气,扩展性也不是特别强.

shiro-cas 单点退出

- - 互联网 - ITeye博客
shiro与CAS集成以后的单点退出. 效果任何一个应用退出以后 所有应用都要重新登录. 实现思路shiro退出系统以后重新定向到cas的退出. 1.重新配置shiro的登出跳转.   shiro退出以后跳转到cas的退出.   cas退出以后通过service参数跳转回应用界面. 2.覆盖shiro的默认退出实现 .

Shiro系列之Shiro+Mysql实现用户授权(Authorization)

- - CSDN博客推荐文章
昨天,我在《 Shiro系列之Shiro+Mysql实现用户认证(Authentication)》中简单介绍了使用Shiro+Mysql实现用户认证的功能,今天我们继续使用其中的示例,讲解一下如何实现用户授权. 所谓授权,就是判断当前用户具体哪些权限,能够执行哪些操作,或是访问哪些资源(Web中的URL,又或是页面上的一个按钮,一个编辑框等都可以视为资源).

Shiro系列之Shiro+Mysql实现用户认证(Authentication)

- - CSDN博客推荐文章
网上大多数介绍Apache Shiro的资料都是使用ini文件的简单配置为例,很少用讲到如何配合数据库来实现用户认证的. 我也是刚刚开始接触Shiro,在这里介绍一个入门级别的Shiro+Mysql的配置方法,这个方法仅仅是个开始,并没有和Web,Spring,Mybatis等框架进行整合,后续我还会继续和大家分享我的学习过程及心得.

[转载]Apache Shiro使用手册

- - 开源软件 - ITeye博客
第一部分 Shiro构架介绍. Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能: . 认证 - 用户身份识别,常被称为用户“登录”;. 密码加密 - 保护或隐藏数据防止被偷窥;. 会话管理 - 每用户相关的时间敏感的状态.       对于任何一个应用程序,Shiro都可以提供全面的安全管理服务.

CAS和Shiro在spring中集成

- - CSDN博客架构设计推荐文章
shiro是权限管理框架,现在已经会利用它如何控制权限. 为了能够为多个系统提供统一认证入口,又研究了单点登录框架cas. 因为二者都会涉及到对session的管理,所以需要进行集成. Shiro在1.2.0的时候提供了对cas的集成. 因此在项目中添加shiro-cas的依赖. Shiro对cas集成后,cas client的配置更加简单了.