shiro multiple system sessionid assignments for one project (getsession overloading)

Shiro Security is a very good Security framework

Recently, I have carried out related integration in my project. Shiro is not difficult, but the difficulty lies in how to integrate the mature system.

As a relevant entry point, I have also considered it for a long time, and the overall application is as Zhang Kaitao said.

 

For Subject we generally use it like this:

1. Authentication (login)

2. Authorization (hasRole*/isPermitted* or checkRole*/checkPermission*)

3. Store the corresponding data in the session (Session)

4. Switching Identity (RunAs) / Multi-threaded Identity Propagation

 

5. Exit

 

Return to the title, after the normal integration, you can basically log in and log out correctly

So let's get down to the details

Generally speaking, our system architecture is developed by springmvc. There are two systems in a project. The distinction between systems and systems is only represented by the difference in the url path. Now there is a situation where basic users of system 1 can log in, while system 2 can only log in to those with relevant permissions.

On the surface, it seems that it can be controlled on the login, for example, it can be done by judging the permissions when logging in. So what is considered at this time is if users are forced to enter through the url.

For example, when a system 1 user logs in, directly modify the url to enter system 2. This is illegal access.

Normally, we will do filtering in the filter, or it is easy to do, assign a mark to the session in the relevant login logic, and filter it in the filter, it will be over, which does not meet the direct logout.

Thinking of this, as a shiro framework user, do you feel that shiro does not seem to work.

So I thought about a more reasonable solution, and decided to assign a sessionid when the user logs in to the system, and system 1 marks the session as a system 1 user before the sessionid + the relevant string. System 2 marks the session as a system 2 user before the sessionid + the relevant string.

If the requirements are clear, then carry out feasibility points.

Then combined with the session management in shiro, we started to investigate.

The first thing to do is how shiro can modify the sessionid, which can be found but can't meet my needs. Because they are all written in a single system, or a dual-project dual-system on the market. Totally not what I want.

It's usually done

 

<!-- Session Cookie Template-->

<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">

<constructor-arg value="sid"/> **

<!--Set the cookie name, the default is JSESSIONID-->

<property name="name" value="WEBSID" />** </bean>

 

The above solution is to configure different jsessionid names for different projects

But how can there be two shiro in my project, which does not meet my requirements

So considering shiro's built-in session processing, I have to do something.

The first is to find the sessionid generator

Customized an id generator

 

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();
}

}

 

 

 

Mainly implement the method of SessionIdGenerator generateId. Here you can see that I added sysType to the front of uuid.

Then it is injected into shiro for use

 

Configuration needs to be added

 

<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">

<property name="sessionIdGenerator" ref="sessionIdGenerator"/>

</bean> 

 

<bean id="sessionIdGenerator" class="***.SysSessionIdGenerator"/> 

 

Also remember to inject sessionDAO into sessionManager. I won't write it here.

 

Then the question comes again, is it logically not to call when the session should be generated, then when is the session of shiro generated?

I looked through the source code

subject.getSession()

This get method implements

 

 

    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;
    }

 It can be seen that when you do not pass parameters, ture is called by default.

 

Simply put, when getsession(true), it will determine whether there is a session now. If not, a new one will be generated, and if there is one, the existing one will be used. False means that if there is none, it will not generate and return null.

 

I found a problem. When I just entered the login page, Shiro has already generated a session, so a new session will not be generated during login verification. So I considered various methods, such as logging out first when logging in, etc. Of course, these are all called crooked ways. Then I found this article

Shiro regenerates sessionid after logging in by himself

I used his method, but it didn't work, and the system became a little chaotic.

Take a closer look at this passage in his article:

During use, it was found that Shiro would not generate a new Jessionid after logging in. This will obviously appear Session_Fixation .

Shiro himself said that this problem will be fixed in the next version 1.3.

My shiro started with the version in Zhang Kaitao's great article, so it's 1.2.2. Trying to change the version, the version on the official website is 1.3.2

Change it first.

It is found that the sessionid has changed before and after logging in. It seems that the session will be regained in getsession in 1.3.2.

But I'm not sure about this, I can only speculate that this is the case.

But that's not my way. To clarify the technical details again, I found that I need to overload the getsession method and pass the sysTpye (system flag) string in when getsession.

Or just the above code has such a line

SessionContext sessionContext = createSessionContext();
 Session session = this.securityManager.start(sessionContext);

It passes the sessionContext into it and creates it. Then I seem to be able to make a fuss here, and after consulting the information, I found that SessionContext inherits Map. Then I can put it directly.

Then continue to dig down the source code.

You can take a look at this excavated article. Anyway, I have a clearer idea after reading it. After all, my debugging is quite confusing.

Shiro source code analysis 

At this point, considering that the sessionContext object is not the final target session, then the value I assign will either shiro output it all to the session, or do nothing.

 

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();
    }
}

  Finally, here, it just takes the host and assigns it to generate a simplesession object.

 

It seems that this is the data endpoint of the SessionContext, so my systpye has to operate here too.

After querying SessionFactory related information, we found that we can define our own SessionFactory
objects. So I customized a 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;
	}

}

  What is done here is to assign the value of sysType to the session, and then inject the configuration file.

 

 

   <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    <!-- Session expiration time, in milliseconds-->
   <property name="globalSessionTimeout" value="1800000"/>
   <!-- Delete invalid session -->
   <property name="deleteInvalidSessions" value="true"/>
   <property name="sessionFactory" ref="sessionFactory"/>
   <property name="sessionDAO" ref="sessionDAO"/>  
   </bean>
   <bean id="sessionFactory" class="***.HrsystemSessionFactory"/>  

 Then it is connected with the SysSessionIdGenerator just now. 

 

Next is the highlight, overloading getsession(); the above are all nonsense, long story short.

First expand the suject interface

 

public interface SysSubject extends Subject {
	 Session getSession(String sysType);
}

 Here I directly inherit the Subject interface; 

 

 

 

 

    public static Subject getSubject() {
        Subject subject = ThreadContext.getSubject();
        if (subject == null) {
            subject = (new Subject.Builder()).buildSubject();
            ThreadContext.bind(subject);
        }
        return subject;
    }

 

 

 The Subject instance is obtained using the ThreadLocal mode, if not, one is created and bound to the current thread. At this time, the creation is created using the Subject internal class Builder. The Builder will create an instance of the SubjectContext interface, DefaultSubjectContext, and finally delegate the securityManager to create a Subject based on the SubjectContext information.

 

The above code is what was mentioned in the previous article about the source code.

Then the main thing is to write by instantiation.

This is a bit back to the original point, from creating a session to how to create a subject object.

However, if you analyze the method structure carefully, you will find that the subject acquisition mode is the same as the session acquisition mode.

But rewriting the subjectFactory has not touched or rarely touched in the network and Zhang Kaitao. This may require reading the source code to understand.

code show as below

 

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);
	    }

}

 The main thing is to instantiate the HrsystemSubject object in the createSubject method and pass it out.

 

And this HrsystemSubject instantiates the SysSubject interface and inherits the WebDelegatingSubject object 

 

 

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 then it's not over yet, we need to inject HrsystemSubjectFactory into 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"/>  
  	

  Here I completely imitate the session routine and inject it, because there is no article to do so. (Or I didn't see it)

 

So at this time SecurityUtils.getSubject (); get out of the object is our HrsystemSubject.

However, the principle of parent-child inheritance is used here. The get object is actually Subject, and the instance of the internal object is actually HrsystemSubject

Then we are forcing it to be replaced by the one we just set ( SysSubject) SecurityUtils.getSubject();

So the overloading of getsession is completed.

 

Then logically enter getsession (string sysType) and then I can have the value I want for sessionid. It can also do logic control. The application scenarios are quite broad.

 

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326339395&siteId=291194637